import { reactive, markRaw, computed } from 'vue' import { ApplicationController } from '../core/controllers/ApplicationController' import { normalizeApplicationConfig } from '../utils/utilApp' import { debugLog } from '../utils/utilDebug' import * as shellwords from 'shellwords' import getopts from "getopts" const apps = reactive(new Map()) /** * Application Manager Composable (Singleton) */ export function useApplicationManager() { /** * Define new application * * @param id * @param config */ async function defineApp(id: string, config: ApplicationConfig) { if (isAppDefined(id)) { debugLog(`App "${id}" is already defined`) return getAppById(id)! } const normalizedConfig = normalizeApplicationConfig(config) const applicationConfig = markRaw(normalizedConfig) const applicationController: IApplicationController = new ApplicationController(id, applicationConfig) await applicationController.initApplication() apps.set(id, markRaw(applicationController)) return applicationController } /** * Check if app has been defined * * @param {string} id */ function isAppDefined(id: string) { return getAppById(id) } /** * Retrieves an app instance by its unique identifier * * @param {string} id */ function getAppById(id: string) { return apps.get(id) } /** * Check if app is running * * @param {string} id */ function isAppRunning(id: string) { if (!isAppDefined(id)) { throw Error(`App "${id}" is not defined`) } const applicationController = getAppById(id)! return applicationController.isRunning } /** * Launch app entry * * @param id * @param entryKey * @param argument */ async function launchAppEntry( id: string, entryKey: string, argument: string = '' ): Promise { if (!isAppDefined(id)) { throw Error(`App "${id}" is not defined`) } const applicationController = getAppById(id)! if ( applicationController.config.entries && !applicationController.config.entries.hasOwnProperty(entryKey) ) { throw Error(`App entry "${entryKey}" is not defined in ${id} application`) } const entry: ApplicationEntry = applicationController.config.entries[entryKey]! if (argument) argument = ` ${argument}` await execAppCommand(applicationController.id, entry.command + argument) } /** * Run app command * * @param id * @param rawCommand */ async function execAppCommand( id: string, rawCommand: string, ): Promise { if (!isAppDefined(id)) { throw Error(`App "${id}" is not defined`) } const applicationController = getAppById(id)! const args = shellwords.split(rawCommand) const parsed = getopts(args) const command: string = parsed._[0] if ( applicationController.config.commands && !applicationController.config.commands.hasOwnProperty(command) ) { throw Error( `App command "${command}" is not defined in ${id} application`, ) } const commandFn: any = applicationController.config.commands![command] const commandOutput = await commandFn(applicationController, parsed) applicationController.setRunning(true) return commandOutput } /** * Close application * * @param id */ function closeApp(id: string) { if (!isAppDefined(id)) { throw Error(`App "${id}" is not defined`) } const applicationController = getAppById(id)! applicationController.closeAllWindows() applicationController.setRunning(false) } /** * Array of available menu entries for system bars, docks */ const appsEntries = computed(() => { const entries: ApplicationEntryWithInherited[] = [] for (const applicationController of apps.values()) { if (!applicationController.config.entries) { continue } for (const entryKey of Object.keys(applicationController.config.entries)) { const entry = applicationController.config.entries[entryKey]! entries.push({ application: applicationController, title: entry.title ?? applicationController.config.title, icon: entry.icon ?? applicationController.config.icon, category: entry.category ?? applicationController.config.category, visibility: entry.visibility ?? 'primary', command: entry.command, }) } } return entries }) /** * Array of opened windows for system bars, docks */ const windowsOpened = computed(() => { const windows = new Map() for (const applicationController of apps.values()) { if ( !applicationController.isRunning && applicationController.openWindowCount.value === 0 ) { continue } for (const [windowId, window] of applicationController.windows) { windows.set(windowId, window) } } return windows }) /** * Array of running applications */ const appsRunning = computed(() => { const runningApps: IApplicationController[] = [] for (const applicationController of apps.values()) { const openCount = applicationController.openWindowCount.value if (applicationController.isRunning || openCount > 0) { runningApps.push(applicationController) } } return runningApps }) function getWindowOpenedId(windowId: string) { for (const window of windowsOpened.value.values()) { if (window.state.id === windowId) { return window } } } /** * Gets all unique categories from the installed apps */ const appCategories = computed(() => { const categories = new Set() for (const applicationController of apps.values()) { if (applicationController.config.category) { categories.add(applicationController.config.category) } } return Array.from(categories).sort() }) /** * Gets the apps ordered by category */ const appsByCategory = computed(() => { const categorizedApps: { [category: string]: IApplicationController[] } = {} for (const applicationController of apps.values()) { const category = applicationController.config.category || 'other' if (!categorizedApps[category]) { categorizedApps[category] = [] } categorizedApps[category].push(applicationController) } for (const category in categorizedApps) { categorizedApps[category].sort((a, b) => a.config.title.localeCompare(b.config.title), ) } return categorizedApps }) return { defineApp, isAppDefined, getAppById, isAppRunning, launchAppEntry, execAppCommand, closeApp, getWindowOpenedId, apps, appsEntries, appsRunning, windowsOpened, appCategories, appsByCategory, } }