diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/AppSearchService.qml | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/AppSearchService.qml')
| -rw-r--r-- | raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/AppSearchService.qml | 985 |
1 files changed, 985 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/AppSearchService.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/AppSearchService.qml new file mode 100644 index 0000000..1c8413f --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/AppSearchService.qml @@ -0,0 +1,985 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import qs.Common + +Singleton { + id: root + + property var applications: [] + property var _cachedCategories: null + property var _cachedVisibleApps: null + property var _hiddenAppsSet: new Set() + + property var _transformCache: ({}) + property var _cachedDefaultSections: [] + property var _cachedDefaultFlatModel: [] + property bool _defaultCacheValid: false + property int cacheVersion: 0 + + readonly property int maxResults: 10 + readonly property int frecencySampleSize: 10 + + readonly property var timeBuckets: [ + { + "maxDays": 4, + "weight": 100 + }, + { + "maxDays": 14, + "weight": 70 + }, + { + "maxDays": 31, + "weight": 50 + }, + { + "maxDays": 90, + "weight": 30 + }, + { + "maxDays": 99999, + "weight": 10 + } + ] + + function refreshApplications() { + applications = DesktopEntries.applications.values; + _cachedCategories = null; + _cachedVisibleApps = null; + invalidateLauncherCache(); + } + + function invalidateLauncherCache() { + _transformCache = {}; + _defaultCacheValid = false; + _cachedDefaultSections = []; + _cachedDefaultFlatModel = []; + cacheVersion++; + } + + function getOrTransformApp(app, transformFn) { + const id = app.id || app.execString || app.exec || ""; + if (!id) + return transformFn(app); + const cached = _transformCache[id]; + if (cached) { + const currentIcon = app.icon || ""; + const cachedSourceIcon = cached._sourceIcon || ""; + if (currentIcon === cachedSourceIcon) + return cached; + } + const transformed = transformFn(app); + transformed._sourceIcon = app.icon || ""; + _transformCache[id] = transformed; + return transformed; + } + + function getCachedDefaultSections() { + if (!_defaultCacheValid) + return null; + return _cachedDefaultSections; + } + + function setCachedDefaultSections(sections, flatModel) { + _cachedDefaultSections = sections.map(function (s) { + return Object.assign({}, s, { + items: s.items ? s.items.slice() : [] + }); + }); + _cachedDefaultFlatModel = flatModel.slice(); + _defaultCacheValid = true; + } + + function isCacheValid() { + return _defaultCacheValid; + } + + function _rebuildHiddenSet() { + _hiddenAppsSet = new Set(SessionData.hiddenApps || []); + _cachedVisibleApps = null; + } + + function isAppHidden(app) { + if (!app) + return false; + const appId = app.id || app.execString || app.exec || ""; + return _hiddenAppsSet.has(appId); + } + + function getVisibleApplications() { + if (_cachedVisibleApps === null) { + const seen = new Set(); + _cachedVisibleApps = applications.filter(app => { + if (isAppHidden(app)) + return false; + const id = app.id; + if (id && seen.has(id)) + return false; + if (id) + seen.add(id); + return true; + }); + } + return _cachedVisibleApps.map(app => applyAppOverride(app)); + } + + Connections { + target: SessionData + function onHiddenAppsChanged() { + root._rebuildHiddenSet(); + root.invalidateLauncherCache(); + } + function onAppOverridesChanged() { + root._cachedVisibleApps = null; + root.invalidateLauncherCache(); + } + } + + Connections { + target: AppUsageHistoryData + function onAppUsageRankingChanged() { + root.invalidateLauncherCache(); + } + } + + function applyAppOverride(app) { + if (!app) + return app; + const appId = app.id || app.execString || app.exec || ""; + const override = SessionData.getAppOverride(appId); + if (!override) + return app; + return Object.assign({}, app, { + name: override.name || app.name, + icon: override.icon || app.icon, + comment: override.comment || app.comment, + _override: override + }); + } + + readonly property string dmsLogoPath: Qt.resolvedUrl("../assets/danklogo2.svg") + + readonly property var builtInPlugins: ({ + "dms_settings": { + id: "dms_settings", + name: I18n.tr("Settings", "settings window title"), + icon: "svg+corner:" + dmsLogoPath + "|settings", + cornerIcon: "settings", + comment: "DMS", + action: "ipc:settings", + categories: ["Settings", "System"], + defaultTrigger: "", + isLauncher: false + }, + "dms_notepad": { + id: "dms_notepad", + name: I18n.tr("Notepad", "Notepad"), + icon: "svg+corner:" + dmsLogoPath + "|description", + cornerIcon: "description", + comment: "DMS", + action: "ipc:notepad", + categories: ["Office", "Utility"], + defaultTrigger: "", + isLauncher: false + }, + "dms_sysmon": { + id: "dms_sysmon", + name: I18n.tr("System Monitor", "sysmon window title"), + icon: "svg+corner:" + dmsLogoPath + "|monitor_heart", + cornerIcon: "monitor_heart", + comment: "DMS", + action: "ipc:processlist", + categories: ["System", "Monitor"], + defaultTrigger: "", + isLauncher: false + }, + "dms_colorpicker": { + id: "dms_colorpicker", + name: I18n.tr("Color Picker"), + icon: "svg+corner:" + dmsLogoPath + "|palette", + cornerIcon: "palette", + comment: "DMS", + action: "ipc:color-picker", + categories: ["Graphics", "Utility"], + defaultTrigger: "", + isLauncher: false + }, + "dms_settings_search": { + id: "dms_settings_search", + name: I18n.tr("Settings", "settings window title"), + cornerIcon: "search", + comment: "DMS", + defaultTrigger: "?", + isLauncher: true + } + }) + + function getBuiltInPluginTrigger(pluginId) { + const plugin = builtInPlugins[pluginId]; + if (!plugin) + return null; + return SettingsData.getBuiltInPluginSetting(pluginId, "trigger", plugin.defaultTrigger); + } + + readonly property var coreApps: { + SettingsData.builtInPluginSettings; + const apps = []; + for (const pluginId in builtInPlugins) { + if (!SettingsData.getBuiltInPluginSetting(pluginId, "enabled", true)) + continue; + const plugin = builtInPlugins[pluginId]; + if (plugin.isLauncher) + continue; + apps.push({ + name: plugin.name, + icon: plugin.icon, + comment: plugin.comment, + action: plugin.action, + categories: plugin.categories, + isCore: true, + builtInPluginId: pluginId, + cornerIcon: plugin.cornerIcon + }); + } + return apps; + } + + function getBuiltInLauncherPlugins() { + const result = {}; + for (const pluginId in builtInPlugins) { + const plugin = builtInPlugins[pluginId]; + if (!plugin.isLauncher) + continue; + if (!SettingsData.getBuiltInPluginSetting(pluginId, "enabled", true)) + continue; + result[pluginId] = plugin; + } + return result; + } + + function getBuiltInLauncherTriggers() { + const triggers = {}; + const launchers = getBuiltInLauncherPlugins(); + for (const pluginId in launchers) { + const trigger = getBuiltInPluginTrigger(pluginId); + if (trigger && trigger.trim() !== "") + triggers[trigger] = pluginId; + } + return triggers; + } + + function getBuiltInLauncherPluginsWithEmptyTrigger() { + const result = []; + const launchers = getBuiltInLauncherPlugins(); + for (const pluginId in launchers) { + const trigger = getBuiltInPluginTrigger(pluginId); + if (!trigger || trigger.trim() === "") + result.push(pluginId); + } + return result; + } + + function getBuiltInLauncherItems(pluginId, query) { + if (pluginId !== "dms_settings_search") + return []; + + SettingsSearchService.search(query); + const results = SettingsSearchService.results; + const items = []; + for (let i = 0; i < results.length; i++) { + const r = results[i]; + items.push({ + name: r.label, + icon: "material:" + r.icon, + comment: r.category, + action: "settings_nav:" + r.tabIndex + ":" + r.section, + categories: ["Settings"], + isCore: true, + isBuiltInLauncher: true, + builtInPluginId: pluginId + }); + } + return items; + } + + function executeBuiltInLauncherItem(item) { + if (!item?.action) + return false; + + const parts = item.action.split(":"); + if (parts[0] !== "settings_nav") + return false; + + const tabIndex = parseInt(parts[1]); + const section = parts.slice(2).join(":"); + SettingsSearchService.navigateToSection(section); + PopoutService.openSettingsWithTabIndex(tabIndex); + return true; + } + + function getCoreApps(query) { + if (!query || query.length === 0) + return coreApps; + const lowerQuery = query.toLowerCase(); + return coreApps.filter(app => app.name.toLowerCase().includes(lowerQuery) || app.comment.toLowerCase().includes(lowerQuery)); + } + + function executeCoreApp(app) { + if (!app?.action) + return false; + + const parts = app.action.split(":"); + if (parts[0] !== "ipc") + return false; + + switch (parts[1]) { + case "settings": + PopoutService.focusOrToggleSettings(); + return true; + case "notepad": + PopoutService.toggleNotepad(); + return true; + case "processlist": + PopoutService.toggleProcessListModal(); + return true; + case "color-picker": + PopoutService.showColorPicker(); + return true; + } + return false; + } + + Connections { + target: DesktopEntries + function onApplicationsChanged() { + root.refreshApplications(); + } + } + + Component.onCompleted: { + _rebuildHiddenSet(); + refreshApplications(); + } + + function tokenize(text) { + return text.toLowerCase().trim().split(/[\s\-_]+/).filter(w => w.length > 0); + } + + function wordBoundaryMatch(text, query) { + const textWords = tokenize(text); + const queryWords = tokenize(query); + + if (queryWords.length === 0) + return false; + if (queryWords.length > textWords.length) + return false; + + for (var i = 0; i <= textWords.length - queryWords.length; i++) { + let allMatch = true; + for (var j = 0; j < queryWords.length; j++) { + if (!textWords[i + j].startsWith(queryWords[j])) { + allMatch = false; + break; + } + } + if (allMatch) + return true; + } + return false; + } + + function levenshteinDistance(s1, s2) { + const len1 = s1.length; + const len2 = s2.length; + const matrix = []; + + for (var i = 0; i <= len1; i++) { + matrix[i] = [i]; + } + for (var j = 0; j <= len2; j++) { + matrix[0][j] = j; + } + + for (var i = 1; i <= len1; i++) { + for (var j = 1; j <= len2; j++) { + const cost = s1[i - 1] === s2[j - 1] ? 0 : 1; + matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost); + } + } + return matrix[len1][len2]; + } + + function fuzzyMatchScore(text, query) { + const queryLower = query.toLowerCase(); + const maxDistance = query.length <= 2 ? 0 : query.length === 3 ? 1 : query.length <= 6 ? 2 : 3; + + let bestScore = 0; + + const distance = levenshteinDistance(text.toLowerCase(), queryLower); + if (distance <= maxDistance) { + const maxLen = Math.max(text.length, query.length); + bestScore = 1 - (distance / maxLen); + } + + const words = tokenize(text); + for (const word of words) { + const wordDistance = levenshteinDistance(word, queryLower); + if (wordDistance <= maxDistance) { + const maxLen = Math.max(word.length, query.length); + const score = 1 - (wordDistance / maxLen); + bestScore = Math.max(bestScore, score); + } + } + + return bestScore; + } + + function calculateFrecency(app) { + const usageRanking = AppUsageHistoryData.appUsageRanking || {}; + const appId = app.id || (app.execString || app.exec || ""); + const idVariants = [appId, appId.replace(".desktop", ""), app.id, app.id ? app.id.replace(".desktop", "") : null].filter(id => id); + + let usageData = null; + for (const variant of idVariants) { + if (usageRanking[variant]) { + usageData = usageRanking[variant]; + break; + } + } + + if (!usageData || !usageData.usageCount) { + return { + "frecency": 0, + "daysSinceUsed": 999999 + }; + } + + const usageCount = usageData.usageCount || 0; + const lastUsed = usageData.lastUsed || 0; + const now = Date.now(); + const daysSinceUsed = (now - lastUsed) / (1000 * 60 * 60 * 24); + + let timeBucketWeight = 10; + for (const bucket of timeBuckets) { + if (daysSinceUsed <= bucket.maxDays) { + timeBucketWeight = bucket.weight; + break; + } + } + + const contextBonus = 100; + const sampleSize = Math.min(usageCount, frecencySampleSize); + const frecency = (timeBucketWeight * contextBonus * sampleSize) / 100; + + return { + "frecency": frecency, + "daysSinceUsed": daysSinceUsed + }; + } + + function searchApplications(query) { + if (!query || query.length === 0) + return getVisibleApplications(); + if (applications.length === 0) + return []; + + const queryLower = query.toLowerCase().trim(); + const scoredApps = []; + const results = []; + const visibleApps = getVisibleApplications(); + + for (const app of visibleApps) { + const name = (app.name || "").toLowerCase(); + const genericName = (app.genericName || "").toLowerCase(); + const comment = (app.comment || "").toLowerCase(); + const id = (app.id || "").toLowerCase(); + const keywords = app.keywords ? app.keywords.map(k => k.toLowerCase()) : []; + + let textScore = 0; + let matchType = "none"; + + if (name === queryLower) { + textScore = 10000; + matchType = "exact"; + } else if (name.startsWith(queryLower)) { + textScore = 5000; + matchType = "prefix"; + } else if (wordBoundaryMatch(name, queryLower)) { + textScore = 3000; + matchType = "word_boundary"; + } else if (name.includes(queryLower)) { + textScore = 500; + matchType = "substring"; + } else if (genericName && genericName.startsWith(queryLower)) { + textScore = 800; + matchType = "generic_prefix"; + } else if (genericName && genericName.includes(queryLower)) { + textScore = 400; + matchType = "generic"; + } else if (id && id.includes(queryLower)) { + textScore = 350; + matchType = "id"; + } + + if (matchType === "none" && keywords.length > 0) { + for (const keyword of keywords) { + if (keyword.startsWith(queryLower)) { + textScore = 300; + matchType = "keyword_prefix"; + break; + } else if (keyword.includes(queryLower)) { + textScore = 150; + matchType = "keyword"; + break; + } + } + } + + if (matchType === "none" && comment && comment.includes(queryLower)) { + textScore = 50; + matchType = "comment"; + } + + if (matchType === "none") { + const fuzzyScore = fuzzyMatchScore(name, queryLower); + if (fuzzyScore > 0) { + textScore = fuzzyScore * 100; + matchType = "fuzzy"; + } + } + + if (matchType !== "none") { + const frecencyData = calculateFrecency(app); + + results.push({ + "app": app, + "textScore": textScore, + "frecency": frecencyData.frecency, + "daysSinceUsed": frecencyData.daysSinceUsed, + "matchType": matchType + }); + } + } + + for (const result of results) { + const frecencyBonus = result.frecency > 0 ? Math.min(result.frecency, 2000) : 0; + const recencyBonus = result.daysSinceUsed < 1 ? 1500 : result.daysSinceUsed < 7 ? 1000 : result.daysSinceUsed < 30 ? 500 : 0; + + const finalScore = result.textScore + frecencyBonus + recencyBonus; + + scoredApps.push({ + "app": result.app, + "score": finalScore + }); + } + + if (SessionData.searchAppActions) { + const actionResults = searchAppActions(queryLower, visibleApps); + for (const actionResult of actionResults) { + scoredApps.push({ + app: actionResult.app, + score: actionResult.score + }); + } + } + + scoredApps.sort((a, b) => b.score - a.score); + return scoredApps.slice(0, maxResults).map(item => item.app); + } + + function searchAppActions(query, apps) { + const results = []; + for (const app of apps) { + if (!app.actions || app.actions.length === 0) + continue; + for (const action of app.actions) { + const actionName = (action.name || "").toLowerCase(); + if (!actionName) + continue; + + let score = 0; + if (actionName === query) { + score = 8000; + } else if (actionName.startsWith(query)) { + score = 4000; + } else if (actionName.includes(query)) { + score = 400; + } + + if (score > 0) { + results.push({ + app: { + name: action.name, + icon: action.icon || app.icon, + comment: app.name, + categories: app.categories || [], + isAction: true, + parentApp: app, + actionData: action + }, + score: score + }); + } + } + } + return results; + } + + function getCategoriesForApp(app) { + if (!app?.categories) + return []; + + const categoryMap = { + "AudioVideo": I18n.tr("Media"), + "Audio": I18n.tr("Media"), + "Video": I18n.tr("Media"), + "Development": I18n.tr("Development"), + "TextEditor": I18n.tr("Development"), + "IDE": I18n.tr("Development"), + "Education": I18n.tr("Education"), + "Game": I18n.tr("Games"), + "Graphics": I18n.tr("Graphics"), + "Photography": I18n.tr("Graphics"), + "Network": I18n.tr("Internet"), + "WebBrowser": I18n.tr("Internet"), + "Email": I18n.tr("Internet"), + "Office": I18n.tr("Office"), + "WordProcessor": I18n.tr("Office"), + "Spreadsheet": I18n.tr("Office"), + "Presentation": I18n.tr("Office"), + "Science": I18n.tr("Science"), + "Settings": I18n.tr("Settings"), + "System": I18n.tr("System"), + "Utility": I18n.tr("Utilities"), + "Accessories": I18n.tr("Utilities"), + "FileManager": I18n.tr("Utilities"), + "TerminalEmulator": I18n.tr("Utilities") + }; + + const mappedCategories = new Set(); + + for (const cat of app.categories) { + if (categoryMap[cat]) + mappedCategories.add(categoryMap[cat]); + } + + return Array.from(mappedCategories); + } + + property var categoryIcons: ({ + "All": "apps", + "Media": "music_video", + "Development": "code", + "Games": "sports_esports", + "Graphics": "photo_library", + "Internet": "web", + "Office": "content_paste", + "Settings": "settings", + "System": "host", + "Utilities": "build" + }) + + function getCategoryIcon(category) { + // Check if it's a plugin category + const pluginIcon = getPluginCategoryIcon(category); + if (pluginIcon) { + return pluginIcon; + } + return categoryIcons[category] || "folder"; + } + + function getAllCategories() { + if (_cachedCategories) + return _cachedCategories; + + const categories = new Set([I18n.tr("All")]); + for (const app of applications) { + const appCategories = getCategoriesForApp(app); + appCategories.forEach(cat => categories.add(cat)); + } + + // Include categories from core apps (e.g. DMS Settings) + for (const app of coreApps) { + const appCategories = getCategoriesForApp(app); + appCategories.forEach(cat => categories.add(cat)); + } + + const pluginCategories = getPluginCategories(); + pluginCategories.forEach(cat => categories.add(cat)); + + _cachedCategories = Array.from(categories).sort(); + return _cachedCategories; + } + + function getAppsInCategory(category) { + const visibleApps = getVisibleApplications(); + if (category === I18n.tr("All")) + return visibleApps; + + const pluginItems = getPluginItems(category, ""); + if (pluginItems.length > 0) + return pluginItems; + + return visibleApps.filter(app => { + const appCategories = getCategoriesForApp(app); + return appCategories.includes(category); + }); + } + + // Plugin launcher support functions + function getPluginCategories() { + if (typeof PluginService === "undefined") { + return []; + } + + const categories = []; + const launchers = PluginService.getLauncherPlugins(); + + for (const pluginId in launchers) { + const plugin = launchers[pluginId]; + const categoryName = plugin.name || pluginId; + categories.push(categoryName); + } + + return categories; + } + + function getPluginCategoryIcon(category) { + if (typeof PluginService === "undefined") + return null; + + const launchers = PluginService.getLauncherPlugins(); + for (const pluginId in launchers) { + const plugin = launchers[pluginId]; + if ((plugin.name || pluginId) === category) { + return plugin.icon || "extension"; + } + } + return null; + } + + function getAllPluginItems() { + if (typeof PluginService === "undefined") { + return []; + } + + let allItems = []; + const launchers = PluginService.getLauncherPlugins(); + + for (const pluginId in launchers) { + const categoryName = launchers[pluginId].name || pluginId; + const items = getPluginItems(categoryName, ""); + allItems = allItems.concat(items); + } + + return allItems; + } + + function getPluginItems(category, query) { + if (typeof PluginService === "undefined") + return []; + + const launchers = PluginService.getLauncherPlugins(); + for (const pluginId in launchers) { + const plugin = launchers[pluginId]; + if ((plugin.name || pluginId) === category) { + return getPluginItemsForPlugin(pluginId, query); + } + } + return []; + } + + function getPluginItemsForPlugin(pluginId, query) { + if (typeof PluginService === "undefined") { + return []; + } + + let instance = PluginService.pluginInstances[pluginId]; + let isPersistent = true; + + if (!instance) { + const component = PluginService.pluginLauncherComponents[pluginId]; + if (!component) + return []; + + try { + instance = component.createObject(root, { + "pluginService": PluginService + }); + isPersistent = false; + } catch (e) { + console.warn("AppSearchService: Error creating temporary plugin instance", pluginId, ":", e); + return []; + } + } + + if (!instance) + return []; + + try { + if (typeof instance.getItems === "function") { + const items = instance.getItems(query || ""); + if (!isPersistent) + instance.destroy(); + return items || []; + } + + if (!isPersistent) { + instance.destroy(); + } + } catch (e) { + console.warn("AppSearchService: Error getting items from plugin", pluginId, ":", e); + if (!isPersistent) + instance.destroy(); + } + + return []; + } + + function executePluginItem(item, pluginId) { + if (typeof PluginService === "undefined") + return false; + + let instance = PluginService.pluginInstances[pluginId]; + let isPersistent = true; + + if (!instance) { + const component = PluginService.pluginLauncherComponents[pluginId]; + if (!component) + return false; + + try { + instance = component.createObject(root, { + "pluginService": PluginService + }); + isPersistent = false; + } catch (e) { + console.warn("AppSearchService: Error creating temporary plugin instance for execution", pluginId, ":", e); + return false; + } + } + + if (!instance) + return false; + + try { + if (typeof instance.executeItem === "function") { + instance.executeItem(item); + if (!isPersistent) + instance.destroy(); + return true; + } + + if (!isPersistent) { + instance.destroy(); + } + } catch (e) { + console.warn("AppSearchService: Error executing item from plugin", pluginId, ":", e); + if (!isPersistent) + instance.destroy(); + } + + return false; + } + + function getPluginPasteText(pluginId, item) { + if (typeof PluginService === "undefined") + return null; + + const instance = PluginService.pluginInstances[pluginId]; + if (!instance) + return null; + + if (typeof instance.getPasteText === "function") { + return instance.getPasteText(item); + } + + return null; + } + + function getPluginPasteArgs(pluginId, item) { + if (typeof PluginService === "undefined") + return null; + + const instance = PluginService.pluginInstances[pluginId]; + if (!instance) + return null; + + if (typeof instance.getPasteArgs === "function") + return instance.getPasteArgs(item); + + if (typeof instance.getPasteText === "function") { + const text = instance.getPasteText(item); + if (text) + return ["dms", "cl", "copy", text]; + } + + return null; + } + + function searchPluginItems(query) { + if (typeof PluginService === "undefined") + return []; + + let allItems = []; + const launchers = PluginService.getLauncherPlugins(); + + for (const pluginId in launchers) { + const items = getPluginItemsForPlugin(pluginId, query); + allItems = allItems.concat(items); + } + + return allItems; + } + + function getPluginLauncherCategories(pluginId) { + if (typeof PluginService === "undefined") + return []; + + const instance = PluginService.pluginInstances[pluginId]; + if (!instance) + return []; + + if (typeof instance.getCategories !== "function") + return []; + + try { + return instance.getCategories() || []; + } catch (e) { + console.warn("AppSearchService: Error getting categories from plugin", pluginId, ":", e); + return []; + } + } + + function setPluginLauncherCategory(pluginId, categoryId) { + if (typeof PluginService === "undefined") + return; + + const instance = PluginService.pluginInstances[pluginId]; + if (!instance) + return; + + if (typeof instance.setCategory !== "function") + return; + + try { + instance.setCategory(categoryId); + } catch (e) { + console.warn("AppSearchService: Error setting category on plugin", pluginId, ":", e); + } + } + + function pluginHasCategories(pluginId) { + if (typeof PluginService === "undefined") + return false; + + const instance = PluginService.pluginInstances[pluginId]; + if (!instance) + return false; + + return typeof instance.getCategories === "function"; + } +} |