feat: redesign floating workspace interactions

This commit is contained in:
2026-08-19 14:12:08 +08:00
parent 1068304da5
commit be7b4149f2
9 changed files with 210 additions and 281 deletions
+50 -65
View File
@@ -35,6 +35,7 @@ import { MapDevPanel } from "./components/map-dev-panel";
import { MobileWorkbenchSheet } from "./components/mobile-workbench-sheet";
import { FeaturePopover } from "./components/feature-popover";
import { ScheduledConditionFeed } from "./components/scheduled-condition-feed";
import { WorkbenchAgentPanels } from "./components/workbench-agent-panels";
import {
ToolbarPanel,
type ExportViewPreset,
@@ -44,7 +45,7 @@ import {
import { WorkbenchTopBar } from "./components/workbench-top-bar";
import {
WorkbenchMainFrame,
type WorkbenchNavigationSection
type WorkbenchMainFrameHandle
} from "./workspace/workbench-main-frame";
import { WORKBENCH_SCENARIOS, WORKBENCH_USER } from "./data/workbench-session";
import { useWorkbenchAgent } from "./hooks/use-workbench-agent";
@@ -103,9 +104,8 @@ export function MapWorkbenchPage({
const hasMapboxToken = Boolean(env.TJWATER_MAPBOX_ACCESS_TOKEN);
const devPanelEnabled = env.TJWATER_ENABLE_DEV_PANEL;
const mapContainerRef = useRef<HTMLDivElement | null>(null);
const mainFrameRef = useRef<WorkbenchMainFrameHandle | null>(null);
const [detailFeature, setDetailFeature] = useState<DetailFeature | null>(null);
const [mainFrameSection, setMainFrameSection] = useState<WorkbenchNavigationSection>("agent");
const [mainFrameNavigationCollapsed, setMainFrameNavigationCollapsed] = useState(false);
const [devPanelOpen, setDevPanelOpen] = useState(false);
const [impactVisible, setImpactVisible] = useState(false);
const [activeToolId, setActiveToolId] = useState<ToolbarToolId | null>(null);
@@ -154,6 +154,7 @@ export function MapWorkbenchPage({
agentPanelWidth,
closeMobileSheet,
conditionFeedExpanded,
conditionFeedMounted,
handleConditionExpandedChange,
isLargeScreen,
leftPanelOpen,
@@ -163,8 +164,11 @@ export function MapWorkbenchPage({
openConditionFeedForViewport,
rightPanelExpanded,
rightPanelOpen,
setAgentPanelWidth,
setMobileSheetSnap,
toggleConditionFeedForViewport
shouldShowConditionFeed,
toggleConditionFeedForViewport,
viewportWidth
} = useWorkbenchResponsiveLayout({
activeToolOpen: activeToolId !== null,
agentPanelOpen: agent.panelOpen,
@@ -175,27 +179,14 @@ export function MapWorkbenchPage({
});
function openAgentWorkspaceForViewport() {
if (isLargeScreen) {
setMainFrameSection("agent");
setMainFrameNavigationCollapsed(false);
}
openAgentPanelForViewport();
}
function openConditionWorkspaceForViewport() {
if (isLargeScreen) {
setMainFrameSection("conditions");
setMainFrameNavigationCollapsed(false);
}
openConditionFeedForViewport();
}
function toggleConditionWorkspaceForViewport() {
if (isLargeScreen) {
setMainFrameSection((current) => current === "conditions" ? "agent" : "conditions");
setMainFrameNavigationCollapsed(false);
return;
}
toggleConditionFeedForViewport();
}
@@ -208,9 +199,9 @@ export function MapWorkbenchPage({
const { controller: mapController, state: mapControllerState } = useWorkbenchMapController({
mapRef,
mapReady,
leftPanelOpen: isLargeScreen ? false : leftPanelOpen,
rightPanelOpen: isLargeScreen ? devPanelOpen : rightPanelOpen,
conditionPanelExpanded: isLargeScreen ? false : rightPanelExpanded,
leftPanelOpen,
rightPanelOpen,
conditionPanelExpanded: rightPanelExpanded,
agentPanelWidth
});
@@ -627,9 +618,9 @@ export function MapWorkbenchPage({
zoom: trustedAction.zoom ?? Math.max(map.getZoom(), 14),
padding: getResponsiveWorkbenchPadding(
map,
isLargeScreen ? false : leftPanelOpen,
isLargeScreen ? devPanelOpen : rightPanelOpen,
isLargeScreen ? false : rightPanelExpanded,
leftPanelOpen,
rightPanelOpen,
rightPanelExpanded,
agentPanelWidth
),
duration: trustedAction.durationMs ?? 500
@@ -797,7 +788,7 @@ export function MapWorkbenchPage({
activeScenarioId={activeScenarioId}
alerts={workbenchAlerts}
user={user}
conditionFeedVisible={isLargeScreen ? mainFrameSection === "conditions" && !mainFrameNavigationCollapsed : mobileSheet === "condition"}
conditionFeedVisible={isLargeScreen ? shouldShowConditionFeed : mobileSheet === "condition"}
taskTickerAvailable={taskTickerAvailable}
taskTickerVisible={taskTickerVisible}
devPanelEnabled={devPanelEnabled}
@@ -811,6 +802,7 @@ export function MapWorkbenchPage({
onCompareScenario={handleCompareScenario}
onExportScenarioReport={handleExportScenarioReport}
onAnalyzeAlertsWithAgent={handleAnalyzeAlertsWithAgent}
onCreateTestAnalysis={devPanelEnabled ? () => mainFrameRef.current?.openTestAnalysis() : undefined}
onShowDataStatus={handleShowDataStatus}
onRefreshTiles={handleRefreshTiles}
onShowShortcuts={handleShowShortcuts}
@@ -819,47 +811,7 @@ export function MapWorkbenchPage({
/>
<WorkbenchMainFrame
testEnabled={devPanelEnabled}
activeSection={mainFrameSection}
navigationCollapsed={mainFrameNavigationCollapsed}
onActiveSectionChange={setMainFrameSection}
onNavigationCollapsedChange={setMainFrameNavigationCollapsed}
renderAgentPanel={(onCollapse) => (
<AgentCommandPanel
{...agentPanelProps}
presentation="desktop-dock"
onCollapse={onCollapse}
/>
)}
conditionsPanel={(
<ScheduledConditionFeed
presentation="desktop-dock"
conditions={scheduledConditions}
expanded={conditionFeedExpanded}
focusRequest={conditionFocusRequest}
loading={scheduledConditionsLoading}
visible
selectedConditionId={selectedConditionId}
onExpandedChange={handleConditionExpandedChange}
onFocusRequestHandled={handleConditionFocusRequestHandled}
onSelectedConditionChange={setSelectedConditionId}
onExpandAgent={openAgentWorkspaceForViewport}
onLoadHistorySession={handleLoadAgentHistorySession}
onSubmitPrompt={agent.submitPrompt}
/>
)}
layersPanel={(
<div className="flex h-full min-h-0 flex-col gap-3 overflow-y-auto p-3">
<MapToolbar items={toolbarItems} orientation="horizontal" className="w-full flex-wrap" />
<ToolbarPanel {...toolbarPanelProps} panelWidthClassName="w-full" />
</div>
)}
toolsPanel={(
<div className="flex h-full min-h-0 flex-col gap-3 overflow-y-auto p-3">
<MapToolbar items={toolbarItems} orientation="horizontal" className="w-full flex-wrap" />
<ToolbarPanel {...toolbarPanelProps} panelWidthClassName="w-full" />
</div>
)}
ref={mainFrameRef}
mapContent={(
<div
ref={mapContainerRef}
@@ -886,6 +838,39 @@ export function MapWorkbenchPage({
)}
/>
<WorkbenchAgentPanels
panelProps={agentPanelProps}
panelOpen={agent.panelOpen}
panelCollapsing={agent.panelCollapsing}
conditionExpanded={shouldShowConditionFeed && conditionFeedExpanded}
personaState={agent.personaState}
statusLabel={agent.statusLabel}
viewportWidth={viewportWidth}
onCollapsePanel={agent.collapsePanel}
onExpandPanel={agent.expandPanel}
onWidthCommit={setAgentPanelWidth}
/>
{conditionFeedMounted && !devPanelOpen ? (
<div className="absolute bottom-14 right-16 top-24 z-50 hidden lg:block 2xl:[--workbench-condition-width:var(--workbench-condition-width-wide)]">
<ScheduledConditionFeed
presentation="desktop-floating"
conditions={scheduledConditions}
expanded={conditionFeedExpanded}
focusRequest={conditionFocusRequest}
loading={scheduledConditionsLoading}
visible={shouldShowConditionFeed}
selectedConditionId={selectedConditionId}
onExpandedChange={handleConditionExpandedChange}
onFocusRequestHandled={handleConditionFocusRequestHandled}
onSelectedConditionChange={setSelectedConditionId}
onExpandAgent={openAgentWorkspaceForViewport}
onLoadHistorySession={handleLoadAgentHistorySession}
onSubmitPrompt={agent.submitPrompt}
/>
</div>
) : null}
{devPanelEnabled && devPanelOpen ? (
<MapDevPanel
commands={mapController}