diff --git a/src/features/workbench/components/workbench-agent-panels.tsx b/src/features/workbench/components/workbench-agent-panels.tsx index 7b20efa..560983e 100644 --- a/src/features/workbench/components/workbench-agent-panels.tsx +++ b/src/features/workbench/components/workbench-agent-panels.tsx @@ -42,7 +42,7 @@ export function WorkbenchAgentPanels({ return (
{panelOpen ? ( diff --git a/src/features/workbench/components/workbench-top-bar.tsx b/src/features/workbench/components/workbench-top-bar.tsx index 3af4924..cd66487 100644 --- a/src/features/workbench/components/workbench-top-bar.tsx +++ b/src/features/workbench/components/workbench-top-bar.tsx @@ -7,6 +7,7 @@ import { Eye, EyeOff, FlaskConical, + Sparkles, type LucideIcon } from "lucide-react"; import { MAP_ICON_CELL_RADIUS_CLASS_NAME } from "@/features/map/core"; @@ -40,6 +41,7 @@ export type WorkbenchTopBarProps = { onCompareScenario: () => void; onExportScenarioReport: () => void; onAnalyzeAlertsWithAgent: () => void | Promise; + onCreateTestAnalysis?: () => void; onShowDataStatus: () => void; onRefreshTiles: () => void; onShowShortcuts: () => void; @@ -70,6 +72,7 @@ export function WorkbenchTopBar({ onCompareScenario, onExportScenarioReport, onAnalyzeAlertsWithAgent, + onCreateTestAnalysis, onShowDataStatus, onRefreshTiles, onShowShortcuts, @@ -141,6 +144,23 @@ export function WorkbenchTopBar({
+ {onCreateTestAnalysis ? ( +
+ +
+ ) : null} {devPanelEnabled ? (
- ); - })} -
- {testEnabled ? ( - - ) : null} - -
- - -
-
-
-
- {testEnabled ? ( - - ) : null} -
-
- {desktop ? ( - <> - - {renderAgentPanel(() => onNavigationCollapsedChange(true))} - - {conditionsPanel} - {layersPanel} - {toolsPanel} - - ) : null} -
-
- -
@@ -410,7 +289,7 @@ export function WorkbenchMainFrame({ {!desktop ? 移动端保持地图与抽屉工作台 : null}
); -} +}); function TaskbarButton({ active, minimized, icon, label, onClick }: { active: boolean; minimized: boolean; icon: ReactNode; label: string; onClick: () => void }) { return ( @@ -520,11 +399,3 @@ function getWorkspaceWindowTitle(state: WorkspaceState, windowId: string) { if (windowId === state.map.id) return state.map.title; return state.analyses.find((window) => window.id === windowId)?.title ?? "工作区窗口"; } - -function NavigationPanel({ active, children }: { active: boolean; children: ReactNode }) { - return ( - - ); -} diff --git a/src/features/workbench/workspace/workspace-model.test.ts b/src/features/workbench/workspace/workspace-model.test.ts index 8ced898..f0b434d 100644 --- a/src/features/workbench/workspace/workspace-model.test.ts +++ b/src/features/workbench/workspace/workspace-model.test.ts @@ -42,11 +42,11 @@ function visibleIds(state: WorkspaceState) { } describe("workspace window lifecycle", () => { - it("minimizes the map for the first analysis and restores it after the last window closes", () => { + it("keeps the map visible under the first analysis and restores it after the last window closes", () => { const initial = createInitialWorkspaceState(bounds); const opened = openAnalysisDocument(initial, documentAt(1), bounds); - expect(opened.map.mode).toBe("minimized"); + expect(opened.map.mode).toBe("docked"); expect(opened.analyses).toHaveLength(1); const closed = closeAnalysisWindow(opened, "analysis-1"); diff --git a/src/features/workbench/workspace/workspace-model.ts b/src/features/workbench/workspace/workspace-model.ts index 96d7978..388242c 100644 --- a/src/features/workbench/workspace/workspace-model.ts +++ b/src/features/workbench/workspace/workspace-model.ts @@ -174,7 +174,7 @@ export function openAnalysisDocument( }; if (state.layout.mode !== "free") return arrangeWorkspaceLayout(opened, bounds); - return { ...opened, map: { ...opened.map, mode: "minimized", minimizedByLayout: false } }; + return opened; } export function closeAnalysisWindow( diff --git a/src/features/workbench/workspace/workspace-window.tsx b/src/features/workbench/workspace/workspace-window.tsx index 7fbf5c3..39ffe6d 100644 --- a/src/features/workbench/workspace/workspace-window.tsx +++ b/src/features/workbench/workspace/workspace-window.tsx @@ -1,9 +1,8 @@ import { - Copy, + Grip, Maximize2, Minimize2, Minus, - PanelTopClose, X } from "lucide-react"; import { @@ -14,6 +13,13 @@ import { type ReactNode } from "react"; import { cn } from "@/shared/ui/cn"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger +} from "@/shared/ui/dropdown-menu"; import type { WorkspaceRect, WorkspaceWindowMode } from "./workspace-model"; type ResizeDirection = "n" | "ne" | "e" | "se" | "s" | "sw" | "w" | "nw"; @@ -85,9 +91,20 @@ export function WorkspaceWindow({ action: "move" | ResizeDirection ) => { const moving = action === "move"; - if ((!moving && !floating) || (moving && mode !== "floating" && mode !== "docked") || event.button !== 0) return; - if (event.target instanceof Element && event.target.closest("button")) return; + if ( + (!moving && !floating) + || (moving && mode !== "floating" && mode !== "docked" && mode !== "maximized") + || event.button !== 0 + ) return; + if ( + event.target instanceof Element + && ( + (!moving && event.target.closest("button")) + || (moving && event.target.closest("[data-window-operation]")) + ) + ) return; event.preventDefault(); + event.stopPropagation(); onFocus(); const startX = event.clientX; const startY = event.clientY; @@ -105,7 +122,7 @@ export function WorkspaceWindow({ if (!moved && Math.hypot(deltaX, deltaY) < 3) return; moved = true; if (moving) { - const nextRect = mode === "docked" && workspaceBounds + const nextRect = mode !== "floating" && workspaceBounds ? { ...startRect, x: pointerEvent.clientX - workspaceBounds.left - Math.min(pointerOffsetX, startRect.width - 40), @@ -132,7 +149,7 @@ export function WorkspaceWindow({ setDragRect(null); return; } - const finalRect = mode === "docked" && workspaceBounds + const finalRect = mode !== "floating" && workspaceBounds ? { ...startRect, x: pointerEvent.clientX - workspaceBounds.left - Math.min(pointerOffsetX, startRect.width - 40), @@ -160,7 +177,7 @@ export function WorkspaceWindow({ if (moving) window.addEventListener("keydown", handleKeyDown); }; - const handleTitleKeyDown = (event: React.KeyboardEvent) => { + const handleOperationKeyDown = (event: React.KeyboardEvent) => { if (!floating || !event.key.startsWith("Arrow")) return; event.preventDefault(); const step = event.altKey ? 2 : 12; @@ -173,6 +190,11 @@ export function WorkspaceWindow({ ); }; + const handleWindowPointerDownCapture = (event: ReactPointerEvent) => { + onFocus(); + if (event.altKey) beginPointerAction(event, "move"); + }; + return (
-
{children}
+ {active ? ( + + + + + + + + {mode === "maximized" || mode === "docked" ? ( + + + ) : ( + + + )} + {onClose ? ( + <> + + + + + ) : null} + + + ) : null} {swapTarget ? (