返回 DeepSeek-Reasonix
projectTreeExpansion.ts
根目录 / desktop / frontend / src / lib / projectTreeExpansion.ts
1 import { asArray } from "./array";
2 import { projectSessionRowKey } from "./projectSessionIdentity";
3 import { topicIsActive } from "./projectTreeTopic";
4 import type { ProjectNode } from "./types";
5
6 export function projectTreeNodeKey(node: ProjectNode, depth: number): string {
7 if (node.session || node.sessionPath || node.source || node.remoteSession || node.tabId) return projectSessionRowKey(node);
8 return node.key || `${node.kind}-${node.root ?? ""}-${node.topicId ?? ""}-${node.sessionPath ?? ""}-${depth}`;
9 }
10
11 export function collapsibleProjectTreeFolderKeys(nodes: ProjectNode[], depth = 0): string[] {
12 const keys: string[] = [];
13 for (const node of nodes) {
14 if (!node) continue;
15 const children = asArray(node.children);
16 if ((node.kind === "project" || node.kind === "global_folder") && children.length > 0) keys.push(projectTreeNodeKey(node, depth));
17 keys.push(...collapsibleProjectTreeFolderKeys(children, depth + 1));
18 }
19 return keys;
20 }
21
22 export function activeSessionAncestorKeys(
23 nodes: ProjectNode[],
24 activeScope?: string,
25 activeWorkspaceRoot?: string,
26 activeTopicId?: string,
27 activeSessionPath?: string,
28 ): string[] {
29 const walk = (nodeList: ProjectNode[], ancestors: string[]): string[] | null => {
30 for (const node of nodeList) {
31 if (!node) continue;
32 if (topicIsActive(node, activeScope, activeWorkspaceRoot, activeTopicId, activeSessionPath)) return ancestors;
33 const children = asArray(node.children);
34 if (children.length > 0) {
35 const next = walk(children, [...ancestors, projectTreeNodeKey(node, ancestors.length)]);
36 if (next) return next;
37 }
38 }
39 return null;
40 };
41 const found = walk(nodes, []);
42 if (found) return found;
43 // Shell-only snapshots no longer embed topics. Expand the matching project or
44 // Global folder by workspace identity so the first lazy page can load.
45 const scope = (activeScope ?? "").trim();
46 const root = (activeWorkspaceRoot ?? "").trim();
47 for (const node of nodes) {
48 if (!node) continue;
49 if (scope === "global" && node.kind === "global_folder") return [projectTreeNodeKey(node, 0)];
50 if (node.kind === "project" && root && (node.root === root || node.root === activeWorkspaceRoot)) return [projectTreeNodeKey(node, 0)];
51 if (!scope && !root && activeTopicId && (node.kind === "project" || node.kind === "global_folder")) return [projectTreeNodeKey(node, 0)];
52 }
53 return [];
54 }
55
56 export function defaultExpandedProjectTreeKeys(
57 nodes: ProjectNode[],
58 activeScope?: string,
59 activeWorkspaceRoot?: string,
60 activeTopicId?: string,
61 activeSessionPath?: string,
62 ): string[] {
63 const keys = activeSessionAncestorKeys(nodes, activeScope, activeWorkspaceRoot, activeTopicId, activeSessionPath);
64 if (keys.length > 0 || [activeScope, activeWorkspaceRoot, activeTopicId, activeSessionPath].some((v) => (v ?? "").trim())) return keys;
65 // With nothing open, a collapsed Global folder never requests its first page
66 // and reads as an empty history, so it starts expanded.
67 const global = nodes.find((node) => node?.kind === "global_folder");
68 return global ? [projectTreeNodeKey(global, 0)] : [];
69 }
70
70 lines TYPESCRIPT