Enhance main window controls and introduce application menu
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Updated `mainWindow.js` to include a 'quit' action in the IPC handlers, allowing the application to close gracefully. - Added a new `WindowAppMenu` component to manage application menu items, including options for 'About', 'Check For Updates', and 'Quit'. - Refactored `DashboardNavigation` to integrate the new `WindowAppMenu`, improving the user interface for non-macOS platforms. - Adjusted window control handling in `window.js` to support the new menu actions, ensuring consistent behavior across the application.
This commit is contained in:
parent
d7a7e798cb
commit
a0ea239aa6
@ -1,4 +1,4 @@
|
|||||||
import { BrowserWindow, ipcMain, Menu } from 'electron'
|
import { app, BrowserWindow, ipcMain, Menu } from 'electron'
|
||||||
import path, { dirname } from 'path'
|
import path, { dirname } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
@ -256,7 +256,7 @@ export function setupMainWindowIPC() {
|
|||||||
|
|
||||||
// IPC handlers for window controls
|
// IPC handlers for window controls
|
||||||
ipcMain.on('window-control', (event, action) => {
|
ipcMain.on('window-control', (event, action) => {
|
||||||
if (!win) return
|
if (!win && action !== 'quit') return
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'minimize':
|
case 'minimize':
|
||||||
win.minimize()
|
win.minimize()
|
||||||
@ -274,6 +274,14 @@ export function setupMainWindowIPC() {
|
|||||||
case 'fullscreen':
|
case 'fullscreen':
|
||||||
win.setFullScreen(!win.isFullScreen())
|
win.setFullScreen(!win.isFullScreen())
|
||||||
break
|
break
|
||||||
|
case 'quit':
|
||||||
|
app.quit()
|
||||||
|
break
|
||||||
|
case 'toggle-devtools':
|
||||||
|
if (win && !win.isDestroyed()) {
|
||||||
|
win.webContents.toggleDevTools()
|
||||||
|
}
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import SettingsIcon from '../../Icons/SettingsIcon'
|
|||||||
import DeveloperIcon from '../../Icons/DeveloperIcon'
|
import DeveloperIcon from '../../Icons/DeveloperIcon'
|
||||||
import { ElectronContext } from '../context/ElectronContext'
|
import { ElectronContext } from '../context/ElectronContext'
|
||||||
import DashboardWindowButtons from './DashboardWindowButtons'
|
import DashboardWindowButtons from './DashboardWindowButtons'
|
||||||
|
import WindowAppMenu from './WindowAppMenu'
|
||||||
import WebAppSwitcher from './WebAppSwitcher'
|
import WebAppSwitcher from './WebAppSwitcher'
|
||||||
import {
|
import {
|
||||||
getSidebarDefaultPath,
|
getSidebarDefaultPath,
|
||||||
@ -141,7 +142,6 @@ const DashboardNavigation = () => {
|
|||||||
setSidebarViewMenu(sections)
|
setSidebarViewMenu(sections)
|
||||||
}, [isElectron, setSidebarViewMenu])
|
}, [isElectron, setSidebarViewMenu])
|
||||||
|
|
||||||
const showAppLogo = isElectron && platform != 'darwin'
|
|
||||||
const isMacOSApp = isElectron && platform == 'darwin'
|
const isMacOSApp = isElectron && platform == 'darwin'
|
||||||
const isOtherApp = isElectron && platform != 'darwin'
|
const isOtherApp = isElectron && platform != 'darwin'
|
||||||
|
|
||||||
@ -153,22 +153,15 @@ const DashboardNavigation = () => {
|
|||||||
const navigationContents = (
|
const navigationContents = (
|
||||||
<Flex style={{ width: '100%' }} align='center'>
|
<Flex style={{ width: '100%' }} align='center'>
|
||||||
{isMacOSApp ? <DashboardWindowButtons /> : null}
|
{isMacOSApp ? <DashboardWindowButtons /> : null}
|
||||||
{showAppLogo == true && (
|
{isOtherApp ? (
|
||||||
<>
|
<>
|
||||||
<FarmControlLogoSmall
|
<WindowAppMenu />{' '}
|
||||||
style={{
|
|
||||||
fontSize: '46px',
|
|
||||||
height: '16px',
|
|
||||||
marginLeft: '13px',
|
|
||||||
marginRight: '18px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Divider
|
<Divider
|
||||||
type='vertical'
|
type='vertical'
|
||||||
style={{ height: '14px', margin: '3px 3px 0 0' }}
|
style={{ height: '14px', margin: '3px 3px 0 1.5px' }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
{showDesktopLogo == true ? (
|
{showDesktopLogo == true ? (
|
||||||
<FarmControlLogo
|
<FarmControlLogo
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
256
src/components/Dashboard/common/WindowAppMenu.jsx
Normal file
256
src/components/Dashboard/common/WindowAppMenu.jsx
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
import { useContext, useMemo } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Button, Dropdown, Flex } from 'antd'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { ElectronContext } from '../context/ElectronContext'
|
||||||
|
import { useAppUpdateContext } from '../context/AppUpdateContext'
|
||||||
|
import { getSidebarMenuSections } from '../../../database/Sidebars'
|
||||||
|
import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall'
|
||||||
|
|
||||||
|
const runEditCommand = (command) => {
|
||||||
|
try {
|
||||||
|
document.execCommand(command)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
`[WindowAppMenu] Failed to run edit command: ${command}`,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapSidebarItemsToMenuItems = (items = [], navigate) =>
|
||||||
|
items
|
||||||
|
.map((item, index) => {
|
||||||
|
if (item?.type === 'divider') {
|
||||||
|
return { type: 'divider', key: `divider-${index}` }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item?.children?.length) {
|
||||||
|
return {
|
||||||
|
key: item.key || `group-${item.label}-${index}`,
|
||||||
|
label: item.label,
|
||||||
|
children: mapSidebarItemsToMenuItems(item.children, navigate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item?.path) {
|
||||||
|
return {
|
||||||
|
key: item.path,
|
||||||
|
label: item.label,
|
||||||
|
onClick: () => navigate(item.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: item.key || `disabled-${item.label}-${index}`,
|
||||||
|
label: item.label,
|
||||||
|
disabled: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
const MenuButton = ({ label, items }) => (
|
||||||
|
<Dropdown menu={{ items }} trigger={['click']} placement='bottomLeft'>
|
||||||
|
<Button
|
||||||
|
type='text'
|
||||||
|
size='small'
|
||||||
|
className='electrobun-webkit-app-region-no-drag'
|
||||||
|
style={{
|
||||||
|
height: '28px',
|
||||||
|
paddingInline: '8px',
|
||||||
|
fontWeight: 500
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
|
||||||
|
MenuButton.propTypes = {
|
||||||
|
label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
|
||||||
|
items: PropTypes.array.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const WindowAppMenu = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { handleWindowControl } = useContext(ElectronContext)
|
||||||
|
const { checkForUpdates } = useAppUpdateContext()
|
||||||
|
const includeDev = import.meta.env.DEV
|
||||||
|
|
||||||
|
const viewSections = useMemo(
|
||||||
|
() => getSidebarMenuSections({ includeDev }),
|
||||||
|
[includeDev]
|
||||||
|
)
|
||||||
|
|
||||||
|
const appMenuItems = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: 'about',
|
||||||
|
label: 'About Farm Control',
|
||||||
|
onClick: () => navigate('/dashboard/management/about')
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: 'check-for-updates',
|
||||||
|
label: 'Check For Updates...',
|
||||||
|
onClick: () => checkForUpdates()
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: 'quit',
|
||||||
|
label: 'Quit Farm Control',
|
||||||
|
onClick: () => handleWindowControl('quit')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[checkForUpdates, handleWindowControl, navigate]
|
||||||
|
)
|
||||||
|
|
||||||
|
const fileMenuItems = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: 'close',
|
||||||
|
label: 'Close Window',
|
||||||
|
onClick: () => handleWindowControl('close')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[handleWindowControl]
|
||||||
|
)
|
||||||
|
|
||||||
|
const editMenuItems = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: 'undo',
|
||||||
|
label: 'Undo',
|
||||||
|
onClick: () => runEditCommand('undo')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'redo',
|
||||||
|
label: 'Redo',
|
||||||
|
onClick: () => runEditCommand('redo')
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: 'cut',
|
||||||
|
label: 'Cut',
|
||||||
|
onClick: () => runEditCommand('cut')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'copy',
|
||||||
|
label: 'Copy',
|
||||||
|
onClick: () => runEditCommand('copy')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'paste',
|
||||||
|
label: 'Paste',
|
||||||
|
onClick: () => runEditCommand('paste')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'pasteAndMatchStyle',
|
||||||
|
label: 'Paste and Match Style',
|
||||||
|
onClick: () => runEditCommand('paste')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
label: 'Delete',
|
||||||
|
onClick: () => runEditCommand('delete')
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: 'selectAll',
|
||||||
|
label: 'Select All',
|
||||||
|
onClick: () => runEditCommand('selectAll')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const viewMenuItems = useMemo(() => {
|
||||||
|
const sectionItems =
|
||||||
|
viewSections.length > 0
|
||||||
|
? viewSections.map((section) => ({
|
||||||
|
key: `view-section-${section.key}`,
|
||||||
|
label: section.label,
|
||||||
|
children: mapSidebarItemsToMenuItems(section.items || [], navigate)
|
||||||
|
}))
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
key: 'no-sidebar-items',
|
||||||
|
label: 'No sidebar items available',
|
||||||
|
disabled: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (!includeDev) {
|
||||||
|
return sectionItems
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...sectionItems,
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: 'toggle-devtools',
|
||||||
|
label: 'Toggle Developer Tools',
|
||||||
|
onClick: () => handleWindowControl('toggle-devtools')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [handleWindowControl, includeDev, navigate, viewSections])
|
||||||
|
|
||||||
|
const windowMenuItems = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: 'minimize',
|
||||||
|
label: 'Minimize',
|
||||||
|
onClick: () => handleWindowControl('minimize')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'zoom',
|
||||||
|
label: 'Zoom',
|
||||||
|
onClick: () => handleWindowControl('maximize')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[handleWindowControl]
|
||||||
|
)
|
||||||
|
|
||||||
|
const menus = useMemo(
|
||||||
|
() => [
|
||||||
|
{ key: 'app', label: 'Farm Control', items: appMenuItems },
|
||||||
|
{ key: 'file', label: 'File', items: fileMenuItems },
|
||||||
|
{ key: 'edit', label: 'Edit', items: editMenuItems },
|
||||||
|
{ key: 'view', label: 'View', items: viewMenuItems },
|
||||||
|
{ key: 'window', label: 'Window', items: windowMenuItems }
|
||||||
|
],
|
||||||
|
[appMenuItems, editMenuItems, fileMenuItems, viewMenuItems, windowMenuItems]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
align='center'
|
||||||
|
className='electrobun-webkit-app-region-no-drag'
|
||||||
|
style={{ marginRight: '8px', flexShrink: 0, marginLeft: '4px' }}
|
||||||
|
>
|
||||||
|
{menus.map((menu) => {
|
||||||
|
if (menu.key === 'app') {
|
||||||
|
return (
|
||||||
|
<MenuButton
|
||||||
|
key={menu.key}
|
||||||
|
label={
|
||||||
|
<FarmControlLogoSmall
|
||||||
|
style={{
|
||||||
|
fontSize: '46px',
|
||||||
|
height: '16px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
items={menu.items}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<MenuButton key={menu.key} label={menu.label} items={menu.items} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WindowAppMenu
|
||||||
@ -341,7 +341,7 @@ export function getWindowState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function handleWindowControl(action) {
|
export function handleWindowControl(action) {
|
||||||
if (!mainWindow) return
|
if (!mainWindow && action !== 'quit') return
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'minimize':
|
case 'minimize':
|
||||||
@ -375,6 +375,12 @@ export function handleWindowControl(action) {
|
|||||||
case 'close':
|
case 'close':
|
||||||
mainWindow.close?.()
|
mainWindow.close?.()
|
||||||
break
|
break
|
||||||
|
case 'quit':
|
||||||
|
Utils.quit()
|
||||||
|
break
|
||||||
|
case 'toggle-devtools':
|
||||||
|
mainWindow?.webview?.toggleDevTools?.()
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user