| 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 |