/** * app Provider:扫描开始菜单应用。 * 1 分钟缓存减少重复 IPC;图标按需加载(前端 Map 缓存,避免重复请求)。 */ import { invoke } from '@tauri-apps/api/core' import { bestScore } from '../engine' import type { QPItem, QPProvider } from './types' import { buildItemForms, makeAppLaunch, makeAppSubActions } from './utils' interface AppRecord { name: string path: string } let appCache: AppRecord[] | null = null let appCacheTime = 0 const APP_CACHE_TTL = 60_000 // 1 分钟缓存 async function loadApps(): Promise { if (appCache && Date.now() - appCacheTime < APP_CACHE_TTL) { return appCache } try { const apps = await invoke('quickpanel_scan_apps') appCache = apps appCacheTime = Date.now() return apps } catch (e) { console.error('[quickpanel] 扫描应用失败:', e) return [] } } export class AppProvider implements QPProvider { id = 'app' label = '应用' priority = 95 async search(query: string): Promise { if (!query.trim()) { // 空查询:不显示应用(避免列表过长),也跳过应用扫描 IPC(不阻塞默认视图首屏) return [] } const apps = await loadApps() const results: Array<{ item: QPItem; score: number }> = [] for (const app of apps) { const forms = buildItemForms(app.name) const score = bestScore(query, forms) if (score >= 0) { results.push({ item: { // 稳定 id(基于路径):扫描结果顺序变化时历史记录仍能恢复原应用 id: `app-${app.path}`, title: app.name, subtitle: app.path, group: '应用', iconPath: app.path, action: makeAppLaunch(app.path), subActions: makeAppSubActions(app.path), appRank: 0, // 开始菜单:最可靠来源 }, score, }) } } results.sort((a, b) => b.score - a.score) return results.slice(0, 15).map(r => ({ ...r.item, score: r.score })) } } // ===== 应用图标按需加载 ===== // 前端缓存(path -> dataUrl)。Rust 侧另有内存 + 磁盘缓存,此处仅避免重复 IPC。 const appIconCache = new Map() // path -> dataUrl('' = 无图标) /** 为搜索结果中带 iconPath 的项(应用、历史中的应用)按需加载图标(data URL), * 并写入 item.iconUrl 触发响应式更新。 * 命中前端缓存时同步返回;否则异步调用 Rust 命令(命中 Rust 缓存则零开销)。 */ export async function loadAppIconsForResults(items: QPItem[]): Promise { const toLoad: QPItem[] = [] for (const item of items) { if (!item.iconPath) continue if (item.iconUrl !== undefined) continue // 已设置(含加载中) const cached = appIconCache.get(item.iconPath) if (cached !== undefined) { item.iconUrl = cached } else { item.iconUrl = '' // 标记加载中,避免重复请求 toLoad.push(item) } } if (!toLoad.length) return await Promise.all( toLoad.map(async item => { const path = item.iconPath! try { const url = await invoke('quickpanel_get_app_icon', { path }) const u = url ?? '' appIconCache.set(path, u) item.iconUrl = u } catch { appIconCache.set(path, '') item.iconUrl = '' } }), ) } /** 清空前端图标缓存(Rust 端清理命令 quickpanel_clear_app_icon_cache 调用后可一并清空) */ export function invalidateAppIconCache() { appIconCache.clear() }