import { BrowserWindow, ipcMain, Menu } from 'electron' import path, { dirname } from 'path' import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) let win let sidebarViewMenuSections = [] const PROTOCOL_PREFIX = 'farmcontrol://' function findProtocolUrl(args) { return args.find( (arg) => typeof arg === 'string' && arg.startsWith(PROTOCOL_PREFIX) ) } function sendNavigateToRenderer(redirectPath) { const deliver = () => { win.webContents.send('navigate', redirectPath) win.show() win.focus() } if (!win || win.isDestroyed()) { createWindow() win.webContents.once('did-finish-load', () => { setTimeout(deliver, 100) }) return } if (win.webContents.isLoading()) { win.webContents.once('did-finish-load', () => { setTimeout(deliver, 100) }) return } deliver() } function toElectronSidebarMenuItems(items = []) { return items .map((item) => { if (item?.type === 'divider') { return { type: 'separator' } } const menuItem = { label: item.label } if (item?.children && Array.isArray(item.children) && item.children.length) { menuItem.submenu = toElectronSidebarMenuItems(item.children) } else if (item?.path) { menuItem.click = () => sendNavigateToRenderer(item.path) } else { menuItem.enabled = false } return menuItem }) .filter(Boolean) } function buildApplicationMenuTemplate() { const env = (process.env.NODE_ENV || 'development').trim() const viewSubmenu = sidebarViewMenuSections.map((section) => ({ label: section.label, submenu: toElectronSidebarMenuItems(section.items || []) })) if (viewSubmenu.length === 0) { viewSubmenu.push({ label: 'No sidebar items available', enabled: false }) } if (env === 'development') { viewSubmenu.push( { type: 'separator' }, { label: 'Toggle Developer Tools', accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', click: () => { if (win && !win.isDestroyed()) { win.webContents.toggleDevTools() } } } ) } const template = [ { role: 'fileMenu' }, { role: 'editMenu' }, { label: 'View', submenu: viewSubmenu }, { role: 'windowMenu' } ] if (process.platform === 'darwin') { template.unshift({ role: 'appMenu' }) } return template } function applyApplicationMenu() { const menu = Menu.buildFromTemplate(buildApplicationMenuTemplate()) Menu.setApplicationMenu(menu) } export function handleDeepLink(url) { if (!url?.startsWith(`${PROTOCOL_PREFIX}app`)) return const redirectPath = url.replace(`${PROTOCOL_PREFIX}app`, '') || '/' sendNavigateToRenderer(redirectPath) } export function handleDeepLinkFromArgv() { if (process.platform === 'darwin') return const url = findProtocolUrl(process.argv) if (url) handleDeepLink(url) } export function setupSingleInstanceLock(app) { const gotTheLock = app.requestSingleInstanceLock() if (!gotTheLock) { app.quit() return false } app.on('second-instance', (_event, commandLine) => { const url = findProtocolUrl(commandLine) if (url) { handleDeepLink(url) } else if (win && !win.isDestroyed()) { if (win.isMinimized()) win.restore() win.show() win.focus() } }) return true } function attachKeyboardShortcuts(browserWindow) { if (!browserWindow) return // Keyboard shortcuts for the main window can be added here if needed } function setupWindowEvents() { if (!win) return win.on('maximize', () => { win.webContents.send('window-state', { isMaximized: true }) }) win.on('enter-full-screen', () => { console.log('Entered fullscreen') win.webContents.send('window-state', { isFullScreen: true }) }) win.on('leave-full-screen', () => { win.webContents.send('window-state', { isFullScreen: false }) }) win.on('unmaximize', () => { win.webContents.send('window-state', { isMaximized: false }) }) } export function createWindow() { win = new BrowserWindow({ width: 1200, height: 800, frame: false, titleBarStyle: 'hiddenInset', trafficLightPosition: { x: 14, y: 12 }, backgroundColor: '#141414', icon: path.join(__dirname, './logo512.png'), webPreferences: { nodeIntegration: true, contextIsolation: false } }) applyApplicationMenu() // For development, load from localhost; for production, load the built index.html if (process.env.ELECTRON_START_URL) { win.loadURL(process.env.ELECTRON_START_URL) } else { win.loadFile(path.join(__dirname, '../build/index.html')) } setupWindowEvents() attachKeyboardShortcuts(win) } export function getWindow() { return win } export function getElectronVersion() { return process.versions.electron } export function setupMainWindowIPC() { // IPC handler to get window state ipcMain.handle('window-state', () => { if (!win || win.isDestroyed()) return { isFullScreen: false, isMaximized: false } return { isFullScreen: win ? win.isFullScreen() : false, isMaximized: win ? win.isMaximized() : false } }) // IPC handlers for window controls ipcMain.on('window-control', (event, action) => { if (!win) return switch (action) { case 'minimize': win.minimize() break case 'maximize': if (win.isMaximized()) { win.unmaximize() } else { win.maximize() } break case 'close': win.close() break default: break } }) ipcMain.handle('open-internal-url', (event, url) => { if (!win || win.isDestroyed()) { createWindow() // Wait for window to finish loading before sending navigate event win.webContents.once('did-finish-load', () => { setTimeout(() => { win.webContents.send('navigate', url) win.show() win.focus() }, 100) }) } else { win.webContents.send('navigate', url) win.show() win.focus() } return true }) ipcMain.handle('set-sidebar-view-menu', (event, sidebarSections) => { if (!Array.isArray(sidebarSections)) { return false } sidebarViewMenuSections = sidebarSections applyApplicationMenu() return true }) ipcMain.handle('electron-version', () => getElectronVersion()) } export function setupMainWindowAppEvents(app) { app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit() }) app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow() }) app.on('open-url', (event, url) => { event.preventDefault() handleDeepLink(url) }) } export function setupDevAuthServer() { const env = (process.env.NODE_ENV || 'development').trim() if (env == 'development') { console.log('Starting development auth web server...') import('express').then(({ default: express }) => { const app = express() const port = 3500 app.use((req, res) => { const redirectPath = req.originalUrl res.send( `Open Farmcontrol to continue... (Redirect path: ${redirectPath})` ) if (win && win.webContents) { win.webContents.send('navigate', redirectPath) win.show() win.focus() } }) app.listen(port, () => { console.log(`Dev auth server running on http://localhost:${port}`) }) }) } else { console.log('Will use url scheme instead of auth server.') } }