feat: implement menu service for context menu management

feat: add provider API service for managing provider accounts and keys
feat: create provider runtime sync service for agent runtime management
feat: introduce script execution service for running automation scripts
feat: develop script store service for managing script metadata and storage
feat: implement theme service for managing application theme settings
feat: add updater service for handling application updates
feat: create window service for managing application windows and their states
This commit is contained in:
DEV_DSW
2026-04-22 09:26:39 +08:00
parent 9b8214cdd4
commit 416399e7a8
19 changed files with 33 additions and 33 deletions

View File

@@ -0,0 +1,123 @@
import { ipcMain, Menu, type MenuItemConstructorOptions } from 'electron';
import { CONFIG_KEYS, IPC_EVENTS } from '@runtime/lib/constants';
import { createTranslator } from '@electron/utils'
import { cloneDeep } from '@electron/utils/shared';
import logManager from '@electron/service/logger'
import configManager from '@electron/service/config-service'
let t: ReturnType<typeof createTranslator> = createTranslator();
class MenuService {
private static _instance: MenuService;
private _menuTemplates: Map<string, MenuItemConstructorOptions[]> = new Map();
private _currentMenu?: Menu = void 0;
private constructor() {
this._setupIpcListener();
this._setupLanguageChangeListener();
logManager.info('MenuService initialized successfully.');
}
private _setupIpcListener() {
ipcMain.handle(IPC_EVENTS.SHOW_CONTEXT_MENU, (_, menuId, dynamicOptions?: string) => new Promise((resolve) => this.showMenu(menuId, () => resolve(true), dynamicOptions)))
}
private _setupLanguageChangeListener() {
configManager.onConfigChange((config)=>{
if(!config[CONFIG_KEYS.LANGUAGE]) return;
t = createTranslator()
})
}
public static getInstance() {
if (!this._instance)
this._instance = new MenuService();
return this._instance;
}
public register(menuId: string, template: MenuItemConstructorOptions[]) {
this._menuTemplates.set(menuId, template);
return menuId;
}
public showMenu(menuId: string, onClose?: () => void, dynamicOptions?: string) {
if (this._currentMenu) return;
const template = cloneDeep(this._menuTemplates.get(menuId));
if (!template) {
logManager.warn(`Menu ${menuId} not found.`);
onClose?.();
return;
}
let _dynamicOptions: Array<Partial<MenuItemConstructorOptions> & { id: string }> = [];
try {
_dynamicOptions = Array.isArray(dynamicOptions) ? dynamicOptions : JSON.parse(dynamicOptions ?? '[]');
} catch (error) {
logManager.error(`Failed to parse dynamicOptions for menu ${menuId}: ${error}`);
}
const translationItem = (item: MenuItemConstructorOptions): MenuItemConstructorOptions => {
if (item.submenu) {
return {
...item,
label: t(item?.label) ?? void 0,
submenu: (item.submenu as MenuItemConstructorOptions[])?.map((item: MenuItemConstructorOptions) => translationItem(item))
}
}
return {
...item,
label: t(item?.label) ?? void 0
}
}
const localizedTemplate = template.map(item => {
if (!Array.isArray(_dynamicOptions) || !_dynamicOptions.length) {
return translationItem(item);
}
const dynamicItem = _dynamicOptions.find(_item => _item.id === item.id);
if (dynamicItem) {
const mergedItem = { ...item, ...dynamicItem };
return translationItem(mergedItem);
}
if (item.submenu) {
return translationItem({
...item,
submenu: (item.submenu as MenuItemConstructorOptions[])?.map((__item: MenuItemConstructorOptions) => {
const dynamicItem = _dynamicOptions.find(_item => _item.id === __item.id);
return { ...__item, ...dynamicItem };
})
})
}
return translationItem(item);
})
const menu = Menu.buildFromTemplate(localizedTemplate);
this._currentMenu = menu;
menu.popup({
callback: () => {
this._currentMenu = void 0;
onClose?.();
}
})
}
public destroyMenu(menuId: string) {
this._menuTemplates.delete(menuId);
}
public destroyed() {
this._menuTemplates.clear();
this._currentMenu = void 0;
}
}
export const menuManager = MenuService.getInstance();
export default menuManager;