2025-12-22 09:18:21 +08:00
import {
app ,
BrowserWindow ,
dialog ,
Menu ,
type MenuItemConstructorOptions ,
shell ,
} from "electron"
import {
applyPresetToEnv ,
getAllPresets ,
getCurrentPresetId ,
setCurrentPreset ,
} from "./config-manager"
2026-01-17 23:10:57 +05:30
import { getMenuTranslations , getPreferredLocale } from "./menu-i18n"
2025-12-22 09:18:21 +08:00
import { restartNextServer } from "./next-server"
import { showSettingsWindow } from "./settings-window"
/**
2026-01-16 18:04:26 +05:30
* Build and set the application menu with i18n support
2025-12-22 09:18:21 +08:00
*/
export function buildAppMenu () : void {
const template = getMenuTemplate ()
const menu = Menu . buildFromTemplate ( template )
Menu . setApplicationMenu ( menu )
}
/**
2026-01-16 18:04:26 +05:30
* Rebuild the menu (call this when presets change or language changes)
2025-12-22 09:18:21 +08:00
*/
export function rebuildAppMenu () : void {
buildAppMenu ()
}
/**
2026-01-16 18:04:26 +05:30
* Get the menu template with translations
2025-12-22 09:18:21 +08:00
*/
function getMenuTemplate () : MenuItemConstructorOptions [] {
const isMac = process . platform === "darwin"
2026-01-17 23:10:57 +05:30
// Get translations for preferred locale (saved preference or system default)
const locale = getPreferredLocale ( app . getLocale ())
2026-01-16 18:04:26 +05:30
const t = getMenuTranslations ( locale )
2025-12-22 09:18:21 +08:00
const template : MenuItemConstructorOptions [] = []
// macOS app menu
if ( isMac ) {
template . push ({
label : app.name ,
submenu : [
2026-01-16 18:04:26 +05:30
{ role : "about" }, // System-translated
2025-12-22 09:18:21 +08:00
{ type : "separator" },
{
2026-01-16 18:04:26 +05:30
label : t.settings ,
2025-12-22 09:18:21 +08:00
accelerator : "CmdOrCtrl+," ,
click : () => {
const win = BrowserWindow . getFocusedWindow ()
showSettingsWindow ( win || undefined )
},
},
{ type : "separator" },
2026-01-16 18:04:26 +05:30
{ role : "services" }, // System-translated
2025-12-22 09:18:21 +08:00
{ type : "separator" },
2026-01-16 18:04:26 +05:30
{ role : "hide" }, // System-translated
{ role : "hideOthers" }, // System-translated
{ role : "unhide" }, // System-translated
2025-12-22 09:18:21 +08:00
{ type : "separator" },
2026-01-16 18:04:26 +05:30
{ role : "quit" }, // System-translated
2025-12-22 09:18:21 +08:00
],
})
}
// File menu
template . push ({
2026-01-16 18:04:26 +05:30
label : t.file ,
2025-12-22 09:18:21 +08:00
submenu : [
...( isMac
? []
: [
{
2026-01-16 18:04:26 +05:30
label : t.settings ,
2025-12-22 09:18:21 +08:00
accelerator : "CmdOrCtrl+," ,
click : () => {
const win = BrowserWindow . getFocusedWindow ()
showSettingsWindow ( win || undefined )
},
},
{ type : "separator" } as MenuItemConstructorOptions ,
]),
2026-01-16 18:04:26 +05:30
isMac ? { role : "close" } : { role : "quit" }, // System-translated
2025-12-22 09:18:21 +08:00
],
})
// Edit menu
template . push ({
2026-01-16 18:04:26 +05:30
label : t.edit ,
2025-12-22 09:18:21 +08:00
submenu : [
2026-01-16 18:04:26 +05:30
{ role : "undo" }, // System-translated
{ role : "redo" }, // System-translated
2025-12-22 09:18:21 +08:00
{ type : "separator" },
2026-01-16 18:04:26 +05:30
{ role : "cut" }, // System-translated
{ role : "copy" }, // System-translated
{ role : "paste" }, // System-translated
2025-12-22 09:18:21 +08:00
...( isMac
? [
{
role : "pasteAndMatchStyle" ,
2026-01-16 18:04:26 +05:30
} as MenuItemConstructorOptions , // System-translated
{ role : "delete" } as MenuItemConstructorOptions , // System-translated
{ role : "selectAll" } as MenuItemConstructorOptions , // System-translated
2025-12-22 09:18:21 +08:00
]
: [
2026-01-16 18:04:26 +05:30
{ role : "delete" } as MenuItemConstructorOptions , // System-translated
2025-12-22 09:18:21 +08:00
{ type : "separator" } as MenuItemConstructorOptions ,
2026-01-16 18:04:26 +05:30
{ role : "selectAll" } as MenuItemConstructorOptions , // System-translated
2025-12-22 09:18:21 +08:00
]),
],
})
// View menu
template . push ({
2026-01-16 18:04:26 +05:30
label : t.view ,
2025-12-22 09:18:21 +08:00
submenu : [
2026-01-16 18:04:26 +05:30
{ role : "reload" }, // System-translated
{ role : "forceReload" }, // System-translated
{ role : "toggleDevTools" }, // System-translated
2025-12-22 09:18:21 +08:00
{ type : "separator" },
2026-01-16 18:04:26 +05:30
{ role : "resetZoom" }, // System-translated
{ role : "zoomIn" }, // System-translated
{ role : "zoomOut" }, // System-translated
2025-12-22 09:18:21 +08:00
{ type : "separator" },
2026-01-16 18:04:26 +05:30
{ role : "togglefullscreen" }, // System-translated
2025-12-22 09:18:21 +08:00
],
})
// Configuration menu with presets
2026-01-16 18:04:26 +05:30
template . push ( buildConfigMenu ( t ))
2025-12-22 09:18:21 +08:00
// Window menu
template . push ({
2026-01-16 18:04:26 +05:30
label : t.window ,
2025-12-22 09:18:21 +08:00
submenu : [
2026-01-16 18:04:26 +05:30
{ role : "minimize" }, // System-translated
{ role : "zoom" }, // System-translated
2025-12-22 09:18:21 +08:00
...( isMac
? [
{ type : "separator" } as MenuItemConstructorOptions ,
2026-01-16 18:04:26 +05:30
{ role : "front" } as MenuItemConstructorOptions , // System-translated
2025-12-22 09:18:21 +08:00
]
2026-01-16 18:04:26 +05:30
: [{ role : "close" } as MenuItemConstructorOptions ]), // System-translated
2025-12-22 09:18:21 +08:00
],
})
// Help menu
template . push ({
2026-01-16 18:04:26 +05:30
label : t.help ,
2025-12-22 09:18:21 +08:00
submenu : [
{
2026-01-16 18:04:26 +05:30
label : t.documentation ,
2025-12-22 09:18:21 +08:00
click : async () => {
await shell . openExternal (
"https://github.com/dayuanjiang/next-ai-draw-io" ,
)
},
},
{
2026-01-16 18:04:26 +05:30
label : t.reportIssue ,
2025-12-22 09:18:21 +08:00
click : async () => {
await shell . openExternal (
"https://github.com/dayuanjiang/next-ai-draw-io/issues" ,
)
},
},
],
})
return template
}
/**
* Build the Configuration menu with presets
*/
2026-01-16 18:04:26 +05:30
function buildConfigMenu (
t : ReturnType < typeof getMenuTranslations >,
) : MenuItemConstructorOptions {
2025-12-22 09:18:21 +08:00
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 {
2026-01-16 18:04:26 +05:30
label : t.configuration ,
2025-12-22 09:18:21 +08:00
submenu : [
...( presetItems . length > 0
? [
2026-01-16 18:04:26 +05:30
{ label : t.switchPreset , enabled : false },
2025-12-22 09:18:21 +08:00
{ type : "separator" } as MenuItemConstructorOptions ,
... presetItems ,
{ type : "separator" } as MenuItemConstructorOptions ,
]
: []),
{
label :
presetItems.length > 0
2026-01-16 18:04:26 +05:30
? t.managePresets
: t.addConfigurationPreset ,
2025-12-22 09:18:21 +08:00
click : () => {
const win = BrowserWindow . getFocusedWindow ()
showSettingsWindow ( win || undefined )
},
},
],
}
}