返回 DeepSeek-Reasonix
dockNavigation.ts
根目录 / desktop / frontend / src / app-shell / dockNavigation.ts
1 import type { ComponentProps } from "react";
2 import type { WorkspacePanel } from "../components/WorkspacePanel";
3
4 export const requestKeys = ["changeRevealRequest", "verificationRevealRequest", "fileListRequest", "changeListRequest"] as const;
5 export type DockRequests = Pick<ComponentProps<typeof WorkspacePanel>, typeof requestKeys[number]> & { navigationSignal?: AbortSignal };
6 export const emptyDockRequests: DockRequests = Object.fromEntries(requestKeys.map(key => [key, null]));
7 type Occurrence = { controller: AbortController; revision: number; accepted: Partial<Record<typeof requestKeys[number], number>>; snapshot: DockRequests };
8
9 /** Committed navigation belongs to an open view, independently of its React body. */
10 export class DockNavigation {
11 private scope = "";
12 private view: string | null = null;
13 private source: Partial<Record<typeof requestKeys[number], string>> = {};
14 private occurrences = new Map<string, Occurrence>();
15 private listeners = new Set<() => void>();
16 private snapshot = emptyDockRequests;
17 private attachment = 0;
18 // StrictMode reconnects effects synchronously without ending the runtime.
19 attach(): () => void {
20 const attachment = ++this.attachment;
21 return () => queueMicrotask(() => { if (this.attachment === attachment) this.dispose(); });
22 }
23 matches(scope: string, view: string | null): boolean { return this.scope === scope && this.view === view; }
24 restoredView(scope: string, view: string | null): DockRequests {
25 const occurrence = this.scope === scope && view ? this.occurrences.get(view) : undefined;
26 return occurrence ? { ...emptyDockRequests, navigationSignal: occurrence.controller.signal } : emptyDockRequests;
27 }
28 subscribe = (listener: () => void) => { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; };
29 getSnapshot = () => this.snapshot;
30
31 reconcile(openViews: readonly string[]): void {
32 for (const [id, occurrence] of this.occurrences) {
33 if (openViews.includes(id)) continue;
34 occurrence.controller.abort();
35 this.occurrences.delete(id);
36 if (this.view === id) { this.view = null; this.publish(emptyDockRequests); }
37 }
38 }
39
40 commit(scope: string, view: string | null, incoming: DockRequests, openViews?: readonly string[]): void {
41 if (this.scope !== scope) { this.clear(); this.scope = scope; }
42 if (openViews) this.reconcile(openViews);
43 const switched = this.view !== view;
44 this.view = view;
45 if (!view) { this.publish(emptyDockRequests); return; }
46 let occurrence = this.occurrences.get(view);
47 if (!occurrence) {
48 const controller = new AbortController();
49 occurrence = { controller, revision: 0, accepted: {}, snapshot: { ...emptyDockRequests, navigationSignal: controller.signal } };
50 this.occurrences.set(view, occurrence);
51 }
52 const identity = (key: typeof requestKeys[number]) => {
53 const value = incoming[key];
54 return value ? JSON.stringify([value.navigationSource ?? key, value.id, "tabId" in value ? value.tabId : null]) : undefined;
55 };
56 const changed = requestKeys.filter(key => this.source[key] !== identity(key));
57 if (changed.length) {
58 const next = { ...(switched ? emptyDockRequests : occurrence.snapshot), navigationSignal: occurrence.controller.signal };
59 const target = occurrence;
60 for (const key of changed) {
61 const value = incoming[key];
62 const revision = ++target.revision;
63 const acceptNavigation = () => {
64 if (value?.navigationCancellation?.aborted || target.controller.signal.aborted || target.accepted[key] === revision || this.view !== view || this.scope !== scope
65 || target.snapshot[key]?.acceptNavigation !== acceptNavigation) return false;
66 target.accepted[key] = revision;
67 return true;
68 };
69 Object.assign(next, { [key]: value ? { ...value, acceptNavigation } : null });
70 this.source[key] = identity(key);
71 }
72 occurrence.snapshot = next;
73 } else if (switched) occurrence.snapshot = { ...emptyDockRequests, navigationSignal: occurrence.controller.signal };
74 this.publish(occurrence.snapshot);
75 }
76
77 private publish(snapshot: DockRequests): void {
78 if (this.snapshot === snapshot) return;
79 this.snapshot = snapshot;
80 for (const listener of this.listeners) listener();
81 }
82
83 private clear(): void {
84 for (const occurrence of this.occurrences.values()) occurrence.controller.abort();
85 this.occurrences.clear();
86 this.view = null;
87 }
88
89 dispose(): void { this.clear(); this.publish(emptyDockRequests); }
90 }
91
91 lines TYPESCRIPT