返回 DeepSeek-Reasonix
WorkspaceDockRegion.tsx
根目录 / desktop / frontend / src / app-shell / WorkspaceDockRegion.tsx
1 import { lazy, Suspense, useState, type ComponentProps, type KeyboardEvent, type PointerEvent, type ReactNode } from "react";
2 import type { DockNavigation } from "./dockNavigation";
3 import type { FileNavigationOwner } from "../lib/fileNavigationOwner";
4 import { fileNavigationOwner } from "../lib/fileNavigationCommands";
5 import type { Translator } from "../lib/i18n";
6 import type { RightDockMode } from "../store/layout";
7 import type { TabItem } from "../store/activityBar";
8 import { useActivityBarStore } from "../store/activityBar";
9 import { readWorkspaceTreeMemory, workspaceViewMemoryKey } from "../lib/workspaceViewMemory";
10 import { useDockViewRequests } from "./useDockViewRequests";
11
12 // The tab strip, its drag state machine and the add menu are a deferred
13 // surface: the dock is closed on most launches, so keep them out of the
14 // initial bundle.
15 const TabContainer = lazy(() => import("../components/TabContainer/TabContainer").then((module) => ({ default: module.TabContainer })));
16
17 const ContextPanel = lazy(() => import("../components/ContextPanel").then((module) => ({ default: module.ContextPanel })));
18 const RemotePanel = lazy(() => import("../components/RemotePanel").then((module) => ({ default: module.RemotePanel })));
19 const BrowserSurface = lazy(() => import("../components/BrowserPanelEntry"));
20 const WorkspacePanel = lazy(async () => {
21 const [module] = await Promise.all([
22 import("../components/WorkspacePanel"),
23 import("../components/WorkspacePanelStability.css"),
24 ]);
25 return { default: module.WorkspacePanel };
26 });
27
28 export type WorkspaceDockRegionProps = {
29 navigation?: DockNavigation;
30 /** Navigation state each dock instance reads; the runtime passes the one it holds. */
31 fileNavigation?: FileNavigationOwner;
32 visible: boolean;
33 overlay: boolean;
34 mode: RightDockMode;
35 showContext: boolean;
36 t: Translator;
37 /** Opens (or activates) the dock view a tab-picker entry stands for. */
38 onPickEntry: (entryId: string) => void;
39 remote: ComponentProps<typeof RemotePanel>;
40 context: ComponentProps<typeof ContextPanel>;
41 workspace: ComponentProps<typeof WorkspacePanel>;
42 workspaceKey: string;
43 workspaceRoot?: string;
44 resizer?: {
45 min: number;
46 max: number;
47 value: number;
48 onPointerDown: (event: PointerEvent<HTMLButtonElement>) => void;
49 onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;
50 onReset: () => void;
51 };
52 };
53
54 /** The right-hand dock shared by every workspace tab. */
55 export function WorkspaceDockRegion(props: WorkspaceDockRegionProps) {
56 const { visible, overlay, mode, showContext, t } = props;
57 const firstFileTabId = useActivityBarStore(state => state.tabs.find(tab => tab.type === "file")?.id);
58 const loadedRoot = useActivityBarStore(state => state.workspaceRoot);
59 const activeTabId = useActivityBarStore(state => state.activeTabId);
60 const tabs = useActivityBarStore(state => state.tabs);
61 const { fileNavigation: fileNavigationProp } = props;
62 // A standalone region keeps one instance for its whole life; the app shell
63 // always passes the instance the runtime holds.
64 const [fallbackFileNavigation] = useState(fileNavigationOwner);
65 const fileNavigation = fileNavigationProp ?? fallbackFileNavigation;
66 const projectReady = loadedRoot === (props.workspaceRoot ?? props.workspace.cwd ?? "");
67 const requests = useDockViewRequests(`${props.workspaceKey}::${props.workspace.tabId ?? ""}`, visible && projectReady ? activeTabId : null, props.workspace, props.navigation, tabs.map(tab => tab.id));
68
69 const renderTab = (tab: TabItem): ReactNode => {
70 if (!projectReady) return null;
71 if (firstFileTabId) readWorkspaceTreeMemory(workspaceViewMemoryKey(props.workspaceKey, firstFileTabId, true));
72 switch (tab.type) {
73 case "context":
74 if (showContext) return <ContextPanel {...props.context} />;
75 return <WorkspacePanel key={`${props.workspaceKey}::${tab.id}`} {...props.workspace} {...requests}
76 dockTabId={tab.id} fileNavigation={fileNavigation}
77 workspaceMemoryKey={workspaceViewMemoryKey(props.workspaceKey, tab.id)} workspaceMemoryVisitId={0} />;
78 case "remote":
79 return <RemotePanel key={`${props.workspaceKey}::${props.workspace.tabId}::${tab.id}`} {...props.remote} tabId={props.workspace.tabId} dockTabId={tab.id} fileNavigation={fileNavigation} navigationSignal={requests.navigationSignal} />;
80 case "browser":
81 return <BrowserSurface surface="panel" taskId={props.workspace.tabId} />;
82 default:
83 return (
84 <WorkspacePanel
85 key={`${props.workspaceKey}::${tab.id}`}
86 {...props.workspace}
87 {...requests}
88 dockTabId={tab.id}
89 fileNavigation={fileNavigation}
90 workspaceMemoryKey={workspaceViewMemoryKey(props.workspaceKey, tab.id, tab.id === firstFileTabId)}
91 workspaceMemoryVisitId={0}
92 initialViewMode={tab.type === "changed" ? "changed" : "files"}
93 />
94 );
95 }
96 };
97
98 return (
99 <>
100 {props.resizer && (
101 <button
102 className="workspace-panel-resizer" type="button" role="separator" aria-orientation="vertical"
103 aria-label={t("rightDock.resize")} aria-valuemin={props.resizer.min}
104 aria-valuemax={props.resizer.max} aria-valuenow={props.resizer.value}
105 onPointerDown={props.resizer.onPointerDown} onKeyDown={props.resizer.onKeyDown}
106 onDoubleClick={props.resizer.onReset}
107 />
108 )}
109 {visible && (
110 <aside className={["workbench-dock", `workbench-dock--${mode}`, overlay ? "workbench-dock--overlay" : ""].join(" ")} aria-label={t("rightDock.workbench")}>
111 <div className="workbench-dock__panel">
112 <Suspense fallback={null}>
113 <TabContainer key={loadedRoot} renderTab={renderTab} onPickEntry={props.onPickEntry} />
114 </Suspense>
115 </div>
116 </aside>
117 )}
118 </>
119 );
120 }
121
121 lines Plain Text