From d16aa373be5826b631642fb04536fe67e475059a Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Thu, 17 Sep 2026 20:39:50 +0100 Subject: [PATCH] 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. --- assets/stylesheets/App.css | 73 ++ src/App.jsx | 3 + src/bun/index.js | 6 +- src/components/Dashboard/Dashboard.jsx | 11 +- src/components/Dashboard/Layout.jsx | 2 +- src/components/Dashboard/Management/About.jsx | 2 + .../Dashboard/Management/Notes/NoteInfo.jsx | 2 + .../Dashboard/Management/Settings.jsx | 2 + .../Dashboard/common/DashboardBreadcrumb.jsx | 9 +- .../Dashboard/common/DashboardChildRoutes.jsx | 21 + .../Dashboard/common/DashboardNavigation.jsx | 32 +- .../common/DashboardOverviewPage.jsx | 5 + .../Dashboard/common/DashboardTabPanes.jsx | 126 ++++ .../Dashboard/common/DashboardTabs.jsx | 139 ++++ .../common/DashboardWindowButtons.jsx | 7 +- src/components/Dashboard/common/ModelPage.jsx | 14 +- .../Dashboard/common/SegmentedNav.jsx | 94 ++- .../Dashboard/common/WindowAppMenu.jsx | 29 +- src/components/Dashboard/common/tabDrag.js | 59 ++ .../context/DashboardObjectToolsContext.jsx | 5 +- .../Dashboard/context/ElectronContext.jsx | 100 ++- .../context/NavigationTabsContext.jsx | 528 +++++++++++++++ .../context/ObjectListViewContext.jsx | 7 + src/database/Settings.js | 14 +- src/desktop/menu.js | 27 +- src/desktop/notify.js | 34 +- src/desktop/rpc.js | 61 +- src/desktop/session.js | 70 ++ src/desktop/window.js | 637 ++++++++++++++---- src/electrobun-bridge.js | 29 +- 30 files changed, 1963 insertions(+), 185 deletions(-) create mode 100644 src/components/Dashboard/common/DashboardChildRoutes.jsx create mode 100644 src/components/Dashboard/common/DashboardTabPanes.jsx create mode 100644 src/components/Dashboard/common/DashboardTabs.jsx create mode 100644 src/components/Dashboard/common/tabDrag.js create mode 100644 src/components/Dashboard/context/NavigationTabsContext.jsx create mode 100644 src/desktop/session.js diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index 833e47f3..a01191ad 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -3346,6 +3346,79 @@ body.objectKanbanColumnResizing * { min-width: 20px; } +.dashboard-tabs { + flex: 1; + min-width: 0; +} + +.dashboard-tabs .segmented-nav-item-label { + padding-inline: 0px; + padding-left: 8px; +} + +.dashboard-tabs-segmented-wrap { + flex: 1; + min-width: 0; +} + +.dashboard-tabs-segmented-wrap .simplebar-track.simplebar-vertical { + display: none; +} + +.dashboard-tabs-segmented-inner { + width: max-content; + align-items: center; +} + +.dashboard-tabs-label { + max-width: 200px; + margin-top: -2px; +} + +.dashboard-tabs-close { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + margin-left: 2px; + border-radius: 8px; + color: var(--color-text-secondary, inherit); + opacity: 0.7; +} + +.dashboard-tabs-close:hover { + opacity: 1; + background: var(--color-button-background); +} + +.dashboard-tabs-add-button { + padding-inline: 4px; + min-width: 20px; + margin-bottom: 2px; +} + +.dashboard-tab-panes { + position: relative; + flex: 1 1 auto; + min-height: 0; + height: 100%; +} + +.dashboard-tab-pane { + height: 100%; + min-height: 0; +} + +.dashboard-tab-pane-active { + display: flex; + flex-direction: column; +} + +.dashboard-tab-pane-inactive { + display: none; +} + .state-tag-loading svg path { stroke: currentColor; stroke-width: 12px; diff --git a/src/App.jsx b/src/App.jsx index abfdb534..d84eec42 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -30,6 +30,7 @@ import AppError from './components/App/AppError' import { ApiServerProvider } from './components/Dashboard/context/ApiServerContext.jsx' import { NotificationProvider } from './components/Dashboard/context/NotificationContext.jsx' import { ElectronProvider } from './components/Dashboard/context/ElectronContext.jsx' +import { NavigationTabsProvider } from './components/Dashboard/context/NavigationTabsContext.jsx' import { MessageProvider } from './components/Dashboard/context/MessageContext.jsx' import { TooltipProvider } from './components/Dashboard/context/TooltipContext.jsx' import { AppUpdateProvider } from './components/Dashboard/context/AppUpdateContext.jsx' @@ -84,6 +85,7 @@ const AppContent = () => { + @@ -183,6 +185,7 @@ const AppContent = () => { + diff --git a/src/bun/index.js b/src/bun/index.js index 847d1499..75d0aa73 100644 --- a/src/bun/index.js +++ b/src/bun/index.js @@ -22,22 +22,22 @@ const { const { createMainWindow, handleDeepLinkFromArgv, + persistSessionNow, setupDevAuthServer, - setupNavigationGestures, setupWindowsDeepLinkHandling } = await import('../desktop/window.js') setupWindowsDeepLinkHandling() const rpc = createAppRpc() -const mainWindow = await createMainWindow(rpc) +await createMainWindow(rpc) -setupNavigationGestures(mainWindow) registerGlobalShortcuts(rpc) setupDevAuthServer() handleDeepLinkFromArgv(launchUrl) process.on('exit', () => { + persistSessionNow() unregisterGlobalShortcuts() closeSingleInstanceServer() }) diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index cd85661d..30556356 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -1,13 +1,12 @@ -// Dashboard.js import Layout from './Layout' import { Outlet } from 'react-router-dom' +import { useNavigationTabs } from './context/NavigationTabsContext' +import DashboardTabPanes from './common/DashboardTabPanes' const Dashboard = () => { - return ( - - - - ) + const { isElectron } = useNavigationTabs() + + return {isElectron ? : } } export default Dashboard diff --git a/src/components/Dashboard/Layout.jsx b/src/components/Dashboard/Layout.jsx index 4516ab36..5373f6ff 100644 --- a/src/components/Dashboard/Layout.jsx +++ b/src/components/Dashboard/Layout.jsx @@ -66,7 +66,7 @@ const DashboardLayout = ({ children }) => { {currentObjectTools} - {children} +
{children}
diff --git a/src/components/Dashboard/Management/About.jsx b/src/components/Dashboard/Management/About.jsx index a2ca7d53..da6c0d60 100644 --- a/src/components/Dashboard/Management/About.jsx +++ b/src/components/Dashboard/Management/About.jsx @@ -22,9 +22,11 @@ import { useMediaQuery } from 'react-responsive' import FarmControlAppIcon from '../../Logos/FarmControlAppIcon' import SoftwareUpdateIcon from '../../Icons/SoftwareUpdateIcon' import ExternalLink from '../common/ExternalLink' +import { useNavigationTabPage } from '../context/NavigationTabsContext' const { Title, Text } = Typography const About = () => { + useNavigationTabPage({ title: 'About' }) const [collapseState, updateCollapseState] = useCollapseState('About', { updater: true }) diff --git a/src/components/Dashboard/Management/Notes/NoteInfo.jsx b/src/components/Dashboard/Management/Notes/NoteInfo.jsx index ef653a79..52dce1da 100644 --- a/src/components/Dashboard/Management/Notes/NoteInfo.jsx +++ b/src/components/Dashboard/Management/Notes/NoteInfo.jsx @@ -21,6 +21,7 @@ import ActivityIndicator from '../../common/ActivityIndicator.jsx' import ActionHandler from '../../common/ActionHandler.jsx' import ObjectActions from '../../common/ObjectActions.jsx' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' +import { useNavigationTabPage } from '../../context/NavigationTabsContext' import ObjectTable from '../../common/ObjectTable.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx' @@ -32,6 +33,7 @@ const log = loglevel.getLogger('NoteInfo') log.setLevel(config.logLevel) const NoteInfo = () => { + useNavigationTabPage({ title: 'Info - Note', modelName: 'note' }) const location = useLocation() const objectFormRef = useRef(null) const actionHandlerRef = useRef(null) diff --git a/src/components/Dashboard/Management/Settings.jsx b/src/components/Dashboard/Management/Settings.jsx index 4b594525..faa7e6bd 100644 --- a/src/components/Dashboard/Management/Settings.jsx +++ b/src/components/Dashboard/Management/Settings.jsx @@ -23,6 +23,7 @@ import settingsSchema, { getVisibleSettingsSections, pickSettings } from '../../../database/Settings' +import { useNavigationTabPage } from '../context/NavigationTabsContext' const DEFAULT_UPDATE_BRANCH = 'main' const DEFAULT_UPDATE_ENGINE = 'native' @@ -35,6 +36,7 @@ const LEGACY_ELECTRON_USER_KEYS = [ ] const Settings = () => { + useNavigationTabPage({ title: 'Settings' }) const { isDarkMode, isCompact, diff --git a/src/components/Dashboard/common/DashboardBreadcrumb.jsx b/src/components/Dashboard/common/DashboardBreadcrumb.jsx index c9e5f393..fc7a5845 100644 --- a/src/components/Dashboard/common/DashboardBreadcrumb.jsx +++ b/src/components/Dashboard/common/DashboardBreadcrumb.jsx @@ -1,11 +1,14 @@ // DashboardBreadcrumb.js +import { useContext } from 'react' import { Breadcrumb, Button, Flex, Space } from 'antd' import { Link, useLocation, useNavigate } from 'react-router-dom' import ArrowLeftIcon from '../../Icons/ArrowLeftIcon' import ArrowRightIcon from '../../Icons/ArrowRightIcon' import FilterIcon from '../../Icons/FilterIcon' import { getModelByPluralName } from '../../../database/ObjectModels' +import { ElectronContext } from '../context/ElectronContext' import { useTableState } from '../context/TableStateContext' +import { useNavigationTabs } from '../context/NavigationTabsContext' const breadcrumbNameMap = { production: 'Production', @@ -28,6 +31,8 @@ const mainSections = ['production', 'inventory', 'management', 'developer'] const DashboardBreadcrumb = () => { const location = useLocation() const navigate = useNavigate() + const { isElectron } = useContext(ElectronContext) + const { goBack, goForward } = useNavigationTabs() const { hasPageFilter, hasStoredFilter, getObjectListView } = useTableState() const pathSnippets = location.pathname.split('/').filter((i) => i) @@ -97,13 +102,13 @@ const DashboardBreadcrumb = () => {