import type { TreeNode } from "@yaakapp-internal/ui"; /** Flags by node id, as the tree reads them: true is collapsed, and a missing id is open */ export type CollapsedMap = Record; /** * Every app's tree, and every site, host and path, starts collapsed, as does System; Apps and * Domains start open. */ export function startsCollapsed(id: string): boolean { return ( id === "system" || id.startsWith("app:") || id.startsWith("site:") || id.startsWith("host:") || id.startsWith("path:") ); } /** * Ids of the nodes the tree treats as folders, those with a children list, empty or not. The * tree collapses an empty one before moving to its parent on the left arrow, so it needs a flag. */ export function folderIds(root: TreeNode<{ id: string }>): string[] { const ids: string[] = []; const walk = (node: TreeNode<{ id: string }>) => { if (node.children == null) return; ids.push(node.item.id); for (const child of node.children ?? []) walk(child); }; walk(root); return ids; } /** * The tree's collapsed flags: every domain and path collapsed unless the user expanded it, and * other nodes as the user left them. */ export function collapsedMap( folders: string[], expanded: Record, others: CollapsedMap, ): CollapsedMap { const map = { ...others }; for (const id of folders) { if (startsCollapsed(id)) map[id] = !expanded[id]; } return map; } /** * Splits the flags the tree sets into the domains and paths now expanded, which are kept, and * the other nodes' flags. Only the nodes whose flag changed from `prev` count. */ export function applyCollapsed( prev: CollapsedMap, next: CollapsedMap, expanded: Record, others: CollapsedMap, ): { expanded: Record; others: CollapsedMap } { const nextExpanded = { ...expanded }; const nextOthers = { ...others }; for (const id of new Set([...Object.keys(prev), ...Object.keys(next)])) { const collapsed = next[id] === true; if (collapsed === (prev[id] === true)) continue; if (!startsCollapsed(id)) nextOthers[id] = collapsed; else if (collapsed) delete nextExpanded[id]; else nextExpanded[id] = true; } return { expanded: nextExpanded, others: nextOthers }; }