import { app, BrowserWindow, dialog, Menu, type MenuItemConstructorOptions, shell, } from "electron" import { applyPresetToEnv, getAllPresets, getCurrentPresetId, setCurrentPreset, } from "./config-manager" import { getMenuTranslations, getPreferredLocale } from "./menu-i18n" import { restartNextServer } from "./next-server" import { showSettingsWindow } from "./settings-window" /** * Build and set the application menu with i18n support */ export function buildAppMenu(): void { const template = getMenuTemplate() const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu) } /** * Rebuild the menu (call this when presets change or language changes) */ export function rebuildAppMenu(): void { buildAppMenu() } /** * Get the menu template with translations */ function getMenuTemplate(): MenuItemConstructorOptions[] { const isMac = process.platform === "darwin" // Get translations for preferred locale (saved preference or system default) const locale = getPreferredLocale(app.getLocale()) const t = getMenuTranslations(locale) const template: MenuItemConstructorOptions[] = [] // macOS app menu if (isMac) { template.push({ label: app.name, submenu: [ { role: "about" }, // System-translated { type: "separator" }, { label: t.settings, accelerator: "CmdOrCtrl+,", click: () => { const win = BrowserWindow.getFocusedWindow() showSettingsWindow(win || undefined) }, }, { type: "separator" }, { role: "services" }, // System-translated { type: "separator" }, { role: "hide" }, // System-translated { role: "hideOthers" }, // System-translated { role: "unhide" }, // System-translated { type: "separator" }, { role: "quit" }, // System-translated ], }) } // File menu template.push({ label: t.file, submenu: [ ...(isMac ? [] : [ { label: t.settings, accelerator: "CmdOrCtrl+,", click: () => { const win = BrowserWindow.getFocusedWindow() showSettingsWindow(win || undefined) }, }, { type: "separator" } as MenuItemConstructorOptions, ]), isMac ? { role: "close" } : { role: "quit" }, // System-translated ], }) // Edit menu template.push({ label: t.edit, submenu: [ { role: "undo" }, // System-translated { role: "redo" }, // System-translated { type: "separator" }, { role: "cut" }, // System-translated { role: "copy" }, // System-translated { role: "paste" }, // System-translated ...(isMac ? [ { role: "pasteAndMatchStyle", } as MenuItemConstructorOptions, // System-translated { role: "delete" } as MenuItemConstructorOptions, // System-translated { role: "selectAll" } as MenuItemConstructorOptions, // System-translated ] : [ { role: "delete" } as MenuItemConstructorOptions, // System-translated { type: "separator" } as MenuItemConstructorOptions, { role: "selectAll" } as MenuItemConstructorOptions, // System-translated ]), ], }) // View menu template.push({ label: t.view, submenu: [ { role: "reload" }, // System-translated { role: "forceReload" }, // System-translated { role: "toggleDevTools" }, // System-translated { type: "separator" }, { role: "resetZoom" }, // System-translated { role: "zoomIn" }, // System-translated { role: "zoomOut" }, // System-translated { type: "separator" }, { role: "togglefullscreen" }, // System-translated ], }) // Configuration menu with presets template.push(buildConfigMenu(t)) // Window menu template.push({ label: t.window, submenu: [ { role: "minimize" }, // System-translated { role: "zoom" }, // System-translated ...(isMac ? [ { type: "separator" } as MenuItemConstructorOptions, { role: "front" } as MenuItemConstructorOptions, // System-translated ] : [{ role: "close" } as MenuItemConstructorOptions]), // System-translated ], }) // Help menu template.push({ label: t.help, submenu: [ { label: t.documentation, click: async () => { await shell.openExternal( "https://github.com/dayuanjiang/next-ai-draw-io", ) }, }, { label: t.reportIssue, click: async () => { await shell.openExternal( "https://github.com/dayuanjiang/next-ai-draw-io/issues", ) }, }, ], }) return template } /** * Build the Configuration menu with presets */ function buildConfigMenu( t: ReturnType, ): MenuItemConstructorOptions { const presets = getAllPresets() const currentPresetId = getCurrentPresetId() const presetItems: MenuItemConstructorOptions[] = presets.map((preset) => ({ label: preset.name, type: "radio", checked: preset.id === currentPresetId, click: async () => { const previousPresetId = getCurrentPresetId() const env = applyPresetToEnv(preset.id) if (env) { try { await restartNextServer() rebuildAppMenu() // Rebuild menu to update checkmarks } catch (error) { console.error("Failed to restart server:", error) // Revert to previous preset on failure if (previousPresetId) { applyPresetToEnv(previousPresetId) } else { setCurrentPreset(null) } // Rebuild menu to restore previous checkmark state rebuildAppMenu() // Show error dialog to notify user dialog.showErrorBox( "Configuration Error", `Failed to apply preset "${preset.name}". The server could not be restarted.\n\nThe previous configuration has been restored.\n\nError: ${error instanceof Error ? error.message : String(error)}`, ) } } }, })) return { label: t.configuration, submenu: [ ...(presetItems.length > 0 ? [ { label: t.switchPreset, enabled: false }, { type: "separator" } as MenuItemConstructorOptions, ...presetItems, { type: "separator" } as MenuItemConstructorOptions, ] : []), { label: presetItems.length > 0 ? t.managePresets : t.addConfigurationPreset, click: () => { const win = BrowserWindow.getFocusedWindow() showSettingsWindow(win || undefined) }, }, ], } }