add synchronized dual-display workbench
GeoIntel release gates / Compile, test, contracts and builds (push) Failing after 31s
GeoIntel release gates / Python and npm vulnerability policy (push) Failing after 1m2s
GeoIntel release gates / GIS image, SBOM and container scan (push) Failing after 9m48s

This commit is contained in:
Jens
2026-08-21 21:50:40 +02:00
parent f085891630
commit 178892fc66
10 changed files with 567 additions and 22 deletions
+26 -2
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { CircleAlert, LogOut, ShieldCheck, UserRound } from 'lucide-react'
import { CircleAlert, LogOut, MonitorUp, ShieldCheck, UserRound } from 'lucide-react'
import '@fontsource/manrope/latin-500.css'
import '@fontsource/manrope/latin-600.css'
import '@fontsource/manrope/latin-700.css'
@@ -37,6 +37,7 @@ import {
type WorkspaceNavigationItem,
} from './components/shell/WorkbenchNavigation'
import { WorkspaceSignal } from './components/shell/WorkspaceSignal'
import { useSecondaryDisplay } from './components/shell/SecondaryDisplay'
import { useChangeDetectionWorkflow } from './hooks/useChangeDetectionWorkflow'
import { useDemoWorkflow } from './hooks/useDemoWorkflow'
import { useCoverageResolver } from './hooks/useCoverageResolver'
@@ -119,6 +120,7 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
const [activeWorkspace, setActiveWorkspace] = useState<WorkspaceKey>('map')
const [inspectorOpen, setInspectorOpen] = useState(false)
const [guestDemoReady, setGuestDemoReady] = useState(!isGuest)
const secondaryDisplay = useSecondaryDisplay()
const guestDemoStartedRef = useRef(false)
const visibleWorkspaceItems = useMemo(
() => isGuest ? workspaceNavItems.filter((item) => guestWorkspaceKeys.has(item.key)) : workspaceNavItems,
@@ -830,7 +832,7 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
: 'Geen actieve laag'
return (
<div className={isGuest ? "app-shell workbench-shell workbench-shell-guest" : "app-shell workbench-shell"}>
<div className={`${isGuest ? 'app-shell workbench-shell workbench-shell-guest' : 'app-shell workbench-shell'}${secondaryDisplay.isOpen ? ' workbench-shell-dual-display' : ''}`}>
<a
className="skip-link"
href="#workspace-main"
@@ -887,6 +889,20 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
<span aria-hidden="true" />
<strong>{workspaceDataLoading ? 'Laden' : errorMessage ? 'Aandacht' : 'Gereed'}</strong>
</div>
<button
type="button"
className={secondaryDisplay.isOpen ? 'secondary-display-launch secondary-display-launch-active' : 'secondary-display-launch'}
aria-pressed={secondaryDisplay.isOpen}
aria-describedby={secondaryDisplay.error ? 'secondary-display-error' : undefined}
onClick={() => {
setActiveWorkspace('map')
if (secondaryDisplay.isOpen) secondaryDisplay.close()
else secondaryDisplay.open()
}}
>
<MonitorUp aria-hidden="true" />
<span>{secondaryDisplay.isOpen ? 'Sluit tweede scherm' : 'Open tweede scherm'}</span>
</button>
{username || isGuest ? (
<div
className={isGuest ? 'context-account context-account-guest' : 'context-account'}
@@ -902,6 +918,13 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
) : null}
</header>
{secondaryDisplay.error ? (
<div className="secondary-display-error" id="secondary-display-error" role="alert">
<CircleAlert aria-hidden="true" />
<span>{secondaryDisplay.error}</span>
</div>
) : null}
{isGuest ? (
<div className="guest-mode-banner" role="status">
<ShieldCheck aria-hidden="true" />
@@ -1031,6 +1054,7 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
<div className="workspace-persistent-map" hidden={activeWorkspace !== 'map'}>
<MapWorkspace
readOnly={false}
secondaryResultsContainer={secondaryDisplay.container}
selectedProjectId={selectedProjectId}
projects={projects}
areas={areas}