diff --git a/src/frontend/src/components/nav/BreadcrumbList.tsx b/src/frontend/src/components/nav/BreadcrumbList.tsx index dbd5ca0f30..15c7374975 100644 --- a/src/frontend/src/components/nav/BreadcrumbList.tsx +++ b/src/frontend/src/components/nav/BreadcrumbList.tsx @@ -10,6 +10,8 @@ import { IconMenu2 } from '@tabler/icons-react'; import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; +import { navigateToLink } from '../../functions/navigation'; + export type Breadcrumb = { name: string; url: string; @@ -57,7 +59,10 @@ export function BreadcrumbList({ return ( breadcrumb.url && navigate(breadcrumb.url)} + onClick={(event: any) => + breadcrumb.url && + navigateToLink(breadcrumb.url, navigate, event) + } > {breadcrumb.name} diff --git a/src/frontend/src/components/nav/Header.tsx b/src/frontend/src/components/nav/Header.tsx index f5cdd07865..af8c8b1126 100644 --- a/src/frontend/src/components/nav/Header.tsx +++ b/src/frontend/src/components/nav/Header.tsx @@ -8,6 +8,7 @@ import { useMatch, useNavigate } from 'react-router-dom'; import { api } from '../../App'; import { navTabs as mainNavTabs } from '../../defaults/links'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; +import { navigateToLink } from '../../functions/navigation'; import * as classes from '../../main.css'; import { apiUrl } from '../../states/ApiState'; import { useLocalState } from '../../states/LocalState'; @@ -141,13 +142,16 @@ function NavTabs() { tab: classes.tab }} value={tabValue} - onChange={(value) => - value == '/' ? navigate('/') : navigate(`/${value}`) - } > {mainNavTabs.map((tab) => ( - + + navigateToLink(`/${tab.name}`, navigate, event) + } + > {tab.text} ))} diff --git a/src/frontend/src/components/nav/PanelGroup.tsx b/src/frontend/src/components/nav/PanelGroup.tsx index afc9be16d6..5523de022c 100644 --- a/src/frontend/src/components/nav/PanelGroup.tsx +++ b/src/frontend/src/components/nav/PanelGroup.tsx @@ -10,15 +10,17 @@ import { IconLayoutSidebarLeftCollapse, IconLayoutSidebarRightCollapse } from '@tabler/icons-react'; -import { ReactNode, useEffect, useMemo, useState } from 'react'; +import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; import { Navigate, Route, Routes, + useLocation, useNavigate, useParams } from 'react-router-dom'; +import { navigateToLink } from '../../functions/navigation'; import { useLocalState } from '../../states/LocalState'; import { Boundary } from '../Boundary'; import { PlaceholderPanel } from '../items/Placeholder'; @@ -52,6 +54,7 @@ function BasePanelGroup({ selectedPanel, collapsible = true }: Readonly): ReactNode { + const location = useLocation(); const navigate = useNavigate(); const { panel } = useParams(); @@ -72,19 +75,27 @@ function BasePanelGroup({ }, [setLastUsedPanel]); // Callback when the active panel changes - function handlePanelChange(panel: string | null) { - if (activePanels.findIndex((p) => p.name === panel) === -1) { - setLastUsedPanel(''); - return navigate('../'); - } + const handlePanelChange = useCallback( + (panel: string | null, event?: any) => { + if (activePanels.findIndex((p) => p.name === panel) === -1) { + setLastUsedPanel(''); + return navigate('../'); + } - navigate(`../${panel}`); + if (event && (event?.ctrlKey || event?.shiftKey)) { + const url = `${location.pathname}/../${panel}`; + navigateToLink(url, navigate, event); + } else { + navigate(`../${panel}`); + } - // Optionally call external callback hook - if (panel && onPanelChange) { - onPanelChange(panel); - } - } + // Optionally call external callback hook + if (panel && onPanelChange) { + onPanelChange(panel); + } + }, + [activePanels, setLastUsedPanel, navigate, location, onPanelChange] + ); // if the selected panel state changes update the current panel useEffect(() => { @@ -129,6 +140,9 @@ function BasePanelGroup({ hidden={panel.hidden} disabled={panel.disabled} style={{ cursor: panel.disabled ? 'unset' : 'pointer' }} + onClick={(event: any) => + handlePanelChange(panel.name, event) + } > {expanded && panel.label}