import { app, BrowserWindow, Menu, shell } from 'electron' import type { BrowserWindow as BrowserWindowInstance, Event, MenuItemConstructorOptions } from 'electron' import path from 'node:path' const isDev = !app.isPackaged const devServerUrl = process.env.VITE_DEV_SERVER_URL ?? 'http://localhost:5200' const appIconPath = isDev ? path.join(process.cwd(), 'build', 'icon.png') : path.join(process.resourcesPath, 'build', 'icon.png') let mainWindow: BrowserWindowInstance | null = null function sendAppCommand(command: string): void { if (!mainWindow || mainWindow.isDestroyed()) return mainWindow.webContents.send('ruwio:menu-command', command) } function createMainWindow(): void { mainWindow = new BrowserWindow({ width: 1280, height: 860, minWidth: 1024, minHeight: 700, title: 'RUWIO', icon: appIconPath, show: false, backgroundColor: '#14171f', webPreferences: { preload: path.join(__dirname, 'preload.cjs'), contextIsolation: true, nodeIntegration: false, sandbox: true, }, }) mainWindow.once('ready-to-show', () => { mainWindow?.show() }) mainWindow.webContents.setWindowOpenHandler(({ url }: { url: string }) => { if (url.startsWith('https://') || url.startsWith('http://')) { void shell.openExternal(url) } return { action: 'deny' } }) mainWindow.webContents.on('will-navigate', (event: Event, url: string) => { const allowedUrl = isDev ? devServerUrl : `file://${path.join(app.getAppPath(), 'dist', 'index.html')}` if (!url.startsWith(allowedUrl)) { event.preventDefault() } }) if (isDev) { void mainWindow.loadURL(devServerUrl) mainWindow.webContents.openDevTools({ mode: 'detach' }) } else { void mainWindow.loadFile(path.join(app.getAppPath(), 'dist', 'index.html')) } } function createMenu(): void { const template: MenuItemConstructorOptions[] = [ { label: 'File', submenu: [ { label: 'New Project', accelerator: 'CmdOrCtrl+N', click: () => sendAppCommand('new-project'), }, { label: 'Open...', accelerator: 'CmdOrCtrl+O', click: () => sendAppCommand('open-project'), }, { label: 'Recent Projects', accelerator: 'CmdOrCtrl+Shift+O', click: () => sendAppCommand('recent-projects'), }, { type: 'separator' }, { label: 'Save', accelerator: 'CmdOrCtrl+S', click: () => sendAppCommand('save-project'), }, { label: 'Save As...', accelerator: 'CmdOrCtrl+Shift+S', click: () => sendAppCommand('save-project-as'), }, { type: 'separator' }, { label: 'Export...', accelerator: 'CmdOrCtrl+E', click: () => sendAppCommand('export-project'), }, { type: 'separator' }, process.platform === 'darwin' ? { role: 'close' } : { role: 'quit' }, ], }, { label: 'Edit', submenu: [ { label: 'Undo', accelerator: 'CmdOrCtrl+Z', click: () => sendAppCommand('undo'), }, { label: 'Redo', accelerator: 'CmdOrCtrl+Shift+Z', click: () => sendAppCommand('redo'), }, { type: 'separator' }, { role: 'cut' }, { role: 'copy' }, { role: 'paste' }, { role: 'selectAll' }, { type: 'separator' }, { label: 'Copy Current Frame', accelerator: 'CmdOrCtrl+Alt+C', click: () => sendAppCommand('copy-current-frame'), }, ], }, { label: 'View', submenu: [ { role: 'reload' }, { role: 'forceReload' }, { role: 'toggleDevTools' }, { type: 'separator' }, { label: 'Play / Pause', accelerator: 'Space', click: () => sendAppCommand('toggle-playback'), }, { label: 'Stop', accelerator: 'Escape', click: () => sendAppCommand('stop-playback'), }, { label: 'Jump to Start', accelerator: 'R', click: () => sendAppCommand('rewind'), }, { label: 'Fullscreen Preview', accelerator: 'Shift+Space', click: () => sendAppCommand('fullscreen-preview'), }, { type: 'separator' }, { label: 'Toggle Side Panels', accelerator: 'Tab', click: () => sendAppCommand('toggle-panels'), }, { label: 'Media Library', accelerator: 'CmdOrCtrl+M', click: () => sendAppCommand('media-library'), }, { label: 'Document Settings', accelerator: 'CmdOrCtrl+,', click: () => sendAppCommand('document-settings'), }, { type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }, { type: 'separator' }, { role: 'togglefullscreen' }, ], }, { label: 'Window', submenu: [ { role: 'minimize' }, { role: 'zoom' }, ], }, { label: 'Help', submenu: [ { label: 'RUWIO Help', accelerator: 'F1', click: () => sendAppCommand('help'), }, { label: 'About RUWIO', click: () => sendAppCommand('about'), }, ...(isDev ? [ { type: 'separator' as const }, { label: 'Open Test Runner', accelerator: 'CmdOrCtrl+Shift+Alt+T', click: () => sendAppCommand('test-runner'), }, ] : []), ], }, ] if (process.platform === 'darwin') { template.unshift({ label: app.name, submenu: [ { role: 'about' }, { type: 'separator' }, { role: 'services' }, { type: 'separator' }, { role: 'hide' }, { role: 'hideOthers' }, { role: 'unhide' }, { type: 'separator' }, { role: 'quit' }, ], }) } Menu.setApplicationMenu(Menu.buildFromTemplate(template)) } app.setName('RUWIO') app.whenReady().then(() => { createMenu() createMainWindow() app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createMainWindow() }) }) app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit() })