From a0c15941ed077d56ccaaafb41249e1b2fc34df6c Mon Sep 17 00:00:00 2001 From: Ry2X <45420571+ry2x@users.noreply.github.com> Date: Wed, 19 Aug 2026 01:01:55 +0900 Subject: [PATCH 1/2] refactor: add comments and clear purpose --- src/stores/application/appLauncher.ts | 10 ++- src/stores/application/applicationCatalog.ts | 24 ++++++- src/stores/application/applicationHistory.ts | 23 ++++++ src/widget/app-launcher/index.tsx | 72 ++++++------------- src/widget/app-launcher/widget/AppItem.tsx | 23 +++--- src/widget/app-launcher/widget/AppList.tsx | 26 ++++--- .../widget/LauncherBackground.tsx | 22 ++++++ .../app-launcher/widget/SearchGoogleBtn.tsx | 13 ++-- .../app-launcher/widget/SearchInput.tsx | 8 ++- 9 files changed, 142 insertions(+), 79 deletions(-) create mode 100644 src/widget/app-launcher/widget/LauncherBackground.tsx diff --git a/src/stores/application/appLauncher.ts b/src/stores/application/appLauncher.ts index 170cfb9..a5ec3d9 100644 --- a/src/stores/application/appLauncher.ts +++ b/src/stores/application/appLauncher.ts @@ -4,15 +4,21 @@ import Apps from 'gi://AstalApps'; import {searchApps} from '@/stores/application/applicationCatalog'; -export interface AppLauncherState { +/** Reactive state used by the application launcher's search UI. */ +export interface AppSearchTextState { + /** Current search text. */ text: Accessor; + /** Updates the search text. */ setText: Setter; + /** Index of the currently selected result. */ selectedIndex: Accessor; + /** Updates the selected result index. */ setSelectedIndex: Setter; + /** Applications matching the current search text. */ results: Accessor; } -export function createAppLauncherState(): AppLauncherState { +export function createAppSearchTextState(): AppSearchTextState { const [text, setText] = createState(''); const [selectedIndex, setSelectedIndex] = createState(0); const results = createComputed(() => searchApps(text().trim().toLowerCase())); diff --git a/src/stores/application/applicationCatalog.ts b/src/stores/application/applicationCatalog.ts index f72008f..5b7de3b 100644 --- a/src/stores/application/applicationCatalog.ts +++ b/src/stores/application/applicationCatalog.ts @@ -10,6 +10,13 @@ function getResultKey(application: Apps.Application) { return application.name + (application.description || '') + (application.iconName || ''); } +/** + * Filters the provided list of applications to ensure that only unique applications are returned, + * based on their name, description, and icon name. + * The results are limited to a maximum number of entries defined by MAX_APP_RESULTS. + * @param applicationList The list of applications to filter for uniqueness. + * @returns A list of unique applications, limited to MAX_APP_RESULTS. + */ function getUniqueResults(applicationList: Apps.Application[]) { const results: Apps.Application[] = []; const seen = new Set(); @@ -26,7 +33,7 @@ function getUniqueResults(applicationList: Apps.Application[]) { return results; } -function getApplicationList() { +function getSortedAppList() { return applications.get_list().sort((applicationA, applicationB) => { const scoreA = history.getScore(applicationA); const scoreB = history.getScore(applicationB); @@ -35,13 +42,26 @@ function getApplicationList() { }); } +/** + * Records the launch of an application, updating its score in the history. + * @param application The application that was launched. + * @returns void + */ export function recordAppLaunch(application: Apps.Application) { history.recordLaunch(application); } +/** + * Searches for applications based on the provided query string, + * returning a list of matching applications sorted by relevance and launch history. + * @param query The search query string. + * @returns A list of applications that match the search query. + */ export function searchApps(query: string) { - const allApplications = getApplicationList(); + const allApplications = getSortedAppList(); + if (query === '') return getUniqueResults(allApplications); + const keywords = query.split(/\s+/); const results = allApplications diff --git a/src/stores/application/applicationHistory.ts b/src/stores/application/applicationHistory.ts index c819d1f..125f71d 100644 --- a/src/stores/application/applicationHistory.ts +++ b/src/stores/application/applicationHistory.ts @@ -18,6 +18,11 @@ function getApplicationKey(application: Apps.Application) { return application.entry || application.executable || application.name; } +/** + * Manages the history of application launches, + * allowing for scoring and ranking of applications based on launch frequency. + * The history is persisted to a JSON file in the user's state directory. + */ export class ApplicationHistory { private scores: Record = {}; @@ -29,6 +34,11 @@ export class ApplicationHistory { return this.scores[getApplicationKey(application)] || 0; } + /** + * Records the launch of an application, updating its score in the history. + * @param application The application that was launched. + * @returns void + */ recordLaunch(application: Apps.Application) { const key = getApplicationKey(application); if (!key) return; @@ -41,6 +51,10 @@ export class ApplicationHistory { this.save(); } + /** + * Trims the history to ensure that only the top MAX_HISTORY_ENTRIES are kept, + * based on their scores. + */ private trim() { const entries = Object.entries(this.scores) .filter(([, score]) => Number.isFinite(score) && score > 0) @@ -49,6 +63,12 @@ export class ApplicationHistory { this.scores = Object.fromEntries(entries); } + /** + * Migrates legacy scores from an older format to the current format, + * ensuring that only valid applications are retained. + * @param legacy A record of legacy application keys and their associated scores. + * @returns A new record of application keys and their migrated scores. + */ private migrateLegacyScores(legacy: Record) { const migrated: Record = {}; for (const [legacyKey, score] of Object.entries(legacy)) { @@ -65,6 +85,9 @@ export class ApplicationHistory { return migrated; } + /** + * Loads the application launch history from the JSON file. + */ private load() { try { const currentFile = Gio.File.new_for_path(HISTORY_FILE); diff --git a/src/widget/app-launcher/index.tsx b/src/widget/app-launcher/index.tsx index 4dc3a91..08c0a94 100644 --- a/src/widget/app-launcher/index.tsx +++ b/src/widget/app-launcher/index.tsx @@ -3,34 +3,14 @@ import {Astal, Gdk, Gtk} from 'ags/gtk4'; import app from 'ags/gtk4/app'; import {type Timer, idle} from 'ags/time'; -import {createAppLauncherState} from '@/stores/application/appLauncher'; -import { - ensureLauncherBackground, - registerLauncherBackground, -} from '@/stores/application/launcherBackground'; -import {AppList} from '@/widget/app-launcher/widget/AppList'; -import {SearchInput} from '@/widget/app-launcher/widget/SearchInput'; - -function createLauncherBackground() { - const picture = new Gtk.Picture({ - contentFit: Gtk.ContentFit.COVER, - canTarget: false, - canShrink: true, - hexpand: true, - vexpand: true, - halign: Gtk.Align.FILL, - valign: Gtk.Align.FILL, - widthRequest: 1, - heightRequest: 1, - }); - - const unregister = registerLauncherBackground(picture); - picture.connect('destroy', unregister); +import {createAppSearchTextState} from '@/stores/application/appLauncher'; +import {ensureLauncherBackground} from '@/stores/application/launcherBackground'; - return picture; -} +import {AppList} from './widget/AppList'; +import {createLauncherBackground} from './widget/LauncherBackground'; +import {SearchInputBar} from './widget/SearchInput'; -function resetLauncherState( +function resetAppSearchTextState( searchInput: Gtk.Entry, setText: (text: string) => void, setSelectedIndex: (index: number) => void @@ -64,13 +44,15 @@ export interface AppLauncherProps { } export default function AppLauncher({monitor}: AppLauncherProps) { - const {text, setText, selectedIndex, setSelectedIndex, results} = createAppLauncherState(); const monitorConnector = monitor.get_connector(); - let focusTimer: Timer | null = null; + + const {text, setText, selectedIndex, setSelectedIndex, results} = createAppSearchTextState(); const launcherBackground = createLauncherBackground(); - const searchInput = SearchInput({ + let focusTimer: Timer | null = null; + + const searchInputBar = SearchInputBar({ text, setText, selectedIndex, @@ -79,17 +61,8 @@ export default function AppLauncher({monitor}: AppLauncherProps) { monitorConnector, }); - const launcherContent = ( - - - - {searchInput} - - - ) as Gtk.Box; - const appList = AppList({ - text, + searchText: text, selectedIndex, results, monitorConnector, @@ -110,9 +83,9 @@ export default function AppLauncher({monitor}: AppLauncherProps) { focusTimer = null; if (!self.visible) { - resetLauncherState(searchInput, setText, setSelectedIndex); + resetAppSearchTextState(searchInputBar, setText, setSelectedIndex); } else { - focusTimer = focusLauncher(searchInput, appList); + focusTimer = focusLauncher(searchInputBar, appList); } }} > @@ -125,14 +98,15 @@ export default function AppLauncher({monitor}: AppLauncherProps) { vexpand overflow={Gtk.Overflow.HIDDEN} > - { - self.set_child(launcherBackground); - self.add_overlay(launcherContent); - }} - /> + + {launcherBackground} + + + + {searchInputBar} + + + {/* Right Panel */} diff --git a/src/widget/app-launcher/widget/AppItem.tsx b/src/widget/app-launcher/widget/AppItem.tsx index a539105..2863b01 100644 --- a/src/widget/app-launcher/widget/AppItem.tsx +++ b/src/widget/app-launcher/widget/AppItem.tsx @@ -7,10 +7,17 @@ import {recordAppLaunch} from '@/stores/application/applicationCatalog'; import {toggleAppLauncher} from '@/stores/shell/windowManager'; export interface AppItemProps { - res: Apps.Application; + app: Apps.Application; monitorConnector: string | null; } +/** + * Creates a set of properties for a Gtk.Image widget based on the provided icon string. + * If the icon string is a file path, + * it sets the 'file' property; otherwise, it sets the 'iconName' property. + * @param iconStr The icon string, either a file path or an icon name. + * @returns An object containing the appropriate Gtk.Image properties. + */ function createImageProp(iconStr: string): Partial { const iconProps: Partial = { cssClasses: ['applauncher-item-icon'], @@ -25,29 +32,29 @@ function createImageProp(iconStr: string): Partial { return iconProps; } -export function AppItem({res, monitorConnector}: AppItemProps): Gtk.Button { +export function AppItem({app, monitorConnector}: AppItemProps): Gtk.Button { return (