All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Updated `createWindow` and `createMainWindow` functions to conditionally set title bar styles and traffic light positions based on the operating system. - Enhanced window layout synchronization by introducing `syncMaximizedWindowFrame` and improving the logic in `isWindowWorkAreaMaximized` to account for maximized window frames. - Added functions to calculate DWM border padding and adjusted frame handling to ensure proper window sizing and positioning across different platforms.
356 lines
8.5 KiB
JavaScript
356 lines
8.5 KiB
JavaScript
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 sendNavigationGesture(browserWindow, direction) {
|
|
if (!browserWindow || browserWindow.isDestroyed()) return
|
|
browserWindow.webContents.send('navigation-gesture', direction)
|
|
}
|
|
|
|
function setupNavigationGestures(browserWindow) {
|
|
if (!browserWindow) return
|
|
|
|
if (process.platform === 'darwin') {
|
|
browserWindow.on('swipe', (_event, direction) => {
|
|
if (direction === 'left') {
|
|
sendNavigationGesture(browserWindow, 'back')
|
|
} else if (direction === 'right') {
|
|
sendNavigationGesture(browserWindow, 'forward')
|
|
}
|
|
})
|
|
}
|
|
|
|
browserWindow.on('app-command', (_event, command) => {
|
|
if (command === 'browser-backward') {
|
|
sendNavigationGesture(browserWindow, 'back')
|
|
} else if (command === 'browser-forward') {
|
|
sendNavigationGesture(browserWindow, 'forward')
|
|
}
|
|
})
|
|
}
|
|
|
|
function setupWindowEvents() {
|
|
if (!win) return
|
|
win.on('maximize', () => {
|
|
win.webContents.send('window-state', {
|
|
isMaximized: true
|
|
})
|
|
})
|
|
win.on('enter-full-screen', () => {
|
|
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,
|
|
...(process.platform === 'darwin'
|
|
? {
|
|
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)
|
|
setupNavigationGestures(win)
|
|
|
|
if (process.platform === 'darwin') {
|
|
win.maximize()
|
|
} else if (process.platform === 'win32') {
|
|
win.maximize()
|
|
}
|
|
}
|
|
|
|
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
|
|
case 'fullscreen':
|
|
win.setFullScreen(!win.isFullScreen())
|
|
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') {
|
|
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, () => {})
|
|
})
|
|
}
|
|
}
|