返回 DeepSeek-Reasonix
mockProjectTreeOrganization.ts
根目录 / desktop / frontend / src / lib / mockProjectTreeOrganization.ts
1 import { asArray } from "./array";
2 import type { ProjectNode, ProjectTreeOrganizationBindings, SessionGroup } from "./types";
3 import { projectSessionIdentity } from "./projectSessionIdentity";
4
5 type OrganizationBindings = ProjectTreeOrganizationBindings;
6
7 const groupsByKey: Record<string, SessionGroup[]> = {};
8 const groupRevisionsByKey: Record<string, number> = {};
9
10 // Match the native project-tree:changed notification across mounted browsers.
11 const treeListeners = new Set<() => void>();
12 export function subscribeMockProjectTreeChanged(listener: () => void): () => void {
13 treeListeners.add(listener);
14 return () => { treeListeners.delete(listener); };
15 }
16 export function notifyMockProjectTreeChanged(): void {
17 treeListeners.forEach(listener => listener());
18 }
19
20 function organizationKey(scope: string, workspaceRoot: string): string {
21 return scope === "global" ? "global|" : `project|${workspaceRoot}`;
22 }
23
24 export function mockProjectGroups(scope: string, workspaceRoot: string): SessionGroup[] {
25 return structuredClone(groupsByKey[organizationKey(scope, workspaceRoot)] ?? []);
26 }
27
28 export function makeMockProjectTreeOrganizationBindings(tree: ProjectNode[]): OrganizationBindings {
29 return {
30 async ReorderTopics(scope, workspaceRoot, orderedTopicIDs) {
31 const parent = tree.find((node) => scope === "global"
32 ? node.kind === "global_folder"
33 : node.kind === "project" && node.root === workspaceRoot);
34 if (!parent) return;
35 const children = asArray(parent.children), byID = new Map(children.filter((node) => node.topicId).map((node) => [node.topicId!, node]));
36 const ordered = orderedTopicIDs.map((id) => byID.get(id)).filter((node): node is ProjectNode => Boolean(node));
37 const seen = new Set(ordered.map((node) => node.topicId));
38 let orderedIndex = 0;
39 parent.children = children.map((node) => seen.has(node.topicId) ? ordered[orderedIndex++] : node);
40 },
41 async ReorderSessions(scope, workspaceRoot, orderedSessionKeys) {
42 const parent = tree.find((node) => scope === "global"
43 ? node.kind === "global_folder"
44 : node.kind === "project" && node.root === workspaceRoot);
45 if (!parent) return;
46 const children = asArray(parent.children);
47 const byKey = new Map(children.map((node) => [projectSessionIdentity(node), node]));
48 const ordered = orderedSessionKeys.map((key) => byKey.get(key)).filter((node): node is ProjectNode => Boolean(node));
49 const seen = new Set(orderedSessionKeys);
50 let orderedIndex = 0;
51 parent.children = children.map((node) => seen.has(projectSessionIdentity(node)) ? ordered[orderedIndex++] : node);
52 },
53 async ListProjectGroups(scope, workspaceRoot) {
54 return mockProjectGroups(scope, workspaceRoot);
55 },
56 async SaveSessionGroups(scope, workspaceRoot, groups) {
57 const key = organizationKey(scope, workspaceRoot);
58 groupsByKey[key] = structuredClone(groups);
59 groupRevisionsByKey[key] = (groupRevisionsByKey[key] ?? 0) + 1;
60 },
61 async GetProjectGroups(scope, workspaceRoot) {
62 const key = organizationKey(scope, workspaceRoot);
63 return { groups: structuredClone(groupsByKey[key] ?? []), revision: groupRevisionsByKey[key] ?? 0, applied: true };
64 },
65 async SaveSessionGroupsVersioned(scope, workspaceRoot, expectedRevision, groups) {
66 const key = organizationKey(scope, workspaceRoot), revision = groupRevisionsByKey[key] ?? 0;
67 if (revision !== expectedRevision) {
68 return { groups: structuredClone(groupsByKey[key] ?? []), revision, applied: false };
69 }
70 groupsByKey[key] = structuredClone(groups);
71 groupRevisionsByKey[key] = revision + 1;
72 return { groups: structuredClone(groups), revision: revision + 1, applied: true };
73 },
74 };
75 }
76
76 lines TYPESCRIPT