Tom Butcher d16aa373be
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
Implement Navigation Tabs and Dashboard Enhancements
- Introduced NavigationTabsContext to manage tab state and navigation within the dashboard.
- Added DashboardTabs component for improved tabbed navigation, allowing users to add, close, and reorder tabs.
- Enhanced Dashboard layout to conditionally render tab panes based on the active tab, improving user experience.
- Updated various components to utilize navigation tab context, ensuring consistent title management and tab interactions.
- Implemented drag-and-drop functionality for tabs, enabling users to rearrange their workspace effectively.
- Refactored CSS styles for dashboard tabs and panes to ensure proper layout and responsiveness across devices.
2026-09-17 20:39:50 +01:00

289 lines
7.3 KiB
JavaScript

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 { AuthContext } from '../context/AuthContext'
import { useNavigationTabs } from '../context/NavigationTabsContext'
import { useAppUpdateContext } from '../context/AppUpdateContext'
import { getSidebarMenuSections } from '../../../database/Sidebars'
import KeyboardShortcut from './KeyboardShortcut'
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, logo }) => (
<Dropdown menu={{ items }} trigger={['click']} placement='bottomLeft'>
<Button
type='text'
size='small'
className='electrobun-webkit-app-region-no-drag'
style={{
height: '28px',
paddingInline: logo ? '6px' : '8px',
marginRight: logo ? '1px' : '0',
fontWeight: 500
}}
>
{label}
</Button>
</Dropdown>
)
MenuButton.propTypes = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
items: PropTypes.array.isRequired,
logo: PropTypes.bool
}
const WindowAppMenu = () => {
const navigate = useNavigate()
const { userProfile } = useContext(AuthContext)
const { handleWindowControl } = useContext(ElectronContext)
const { addTab, createNewWindow } = useNavigationTabs()
const { checkForUpdates } = useAppUpdateContext()
const includeDev = import.meta.env.DEV
const viewSections = useMemo(
() => getSidebarMenuSections({ includeDev, userProfile }),
[includeDev, userProfile]
)
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: 'new-window',
label: 'New Window',
onClick: () => createNewWindow()
},
{
key: 'new-tab',
label: 'New Tab',
onClick: () => addTab()
},
{ type: 'divider' },
{
key: 'close',
label: 'Close Window',
onClick: () => handleWindowControl('close')
}
],
[addTab, createNewWindow, 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}
logo={true}
label={
<FarmControlLogoSmall
style={{
fontSize: '46px',
height: '16px'
}}
/>
}
items={menu.items}
/>
)
}
if (menu.key === 'file') {
return (
<KeyboardShortcut
key={menu.key}
shortcut='ctrl+t'
onTrigger={addTab}
>
<KeyboardShortcut shortcut='ctrl+n' onTrigger={createNewWindow}>
<MenuButton label={menu.label} items={menu.items} />
</KeyboardShortcut>
</KeyboardShortcut>
)
}
return (
<MenuButton key={menu.key} label={menu.label} items={menu.items} />
)
})}
</Flex>
)
}
export default WindowAppMenu