Parent directory

tree-view.ts

2686 bytes
 1import type { BranchCandidate } from "./tree-model.ts";
 2
 3export interface PruneTreeItem {
 4  readonly candidate: BranchCandidate;
 5  readonly label: string | undefined;
 6  readonly treePrefix: string;
 7}
 8
 9export function buildPruneTreeItems(
10  candidates: readonly BranchCandidate[],
11  getLabel: (entryId: string) => string | undefined,
12): PruneTreeItem[] {
13  const parentByRootId = new Map<string, string>();
14
15  for (const candidate of candidates) {
16    const parent = findParentCandidate(candidate, candidates);
17    if (parent) parentByRootId.set(candidate.rootId, parent.rootId);
18  }
19
20  const childrenByRootId = new Map<string | undefined, BranchCandidate[]>();
21  for (const candidate of candidates) {
22    const parentRootId = parentByRootId.get(candidate.rootId);
23    const children = childrenByRootId.get(parentRootId) ?? [];
24    children.push(candidate);
25    childrenByRootId.set(parentRootId, children);
26  }
27
28  const items: PruneTreeItem[] = [];
29  const visit = (candidate: BranchCandidate, ancestorIsLast: readonly boolean[], isLast: boolean): void => {
30    const parentRootId = parentByRootId.get(candidate.rootId);
31    const treePrefix = parentRootId
32      ? `${ancestorIsLast.map((last) => (last ? "   " : "│  ")).join("")}${isLast ? "└─ " : "├─ "}`
33      : "";
34
35    items.push({ candidate, label: getLabel(candidate.rootId), treePrefix });
36
37    const children = childrenByRootId.get(candidate.rootId) ?? [];
38    for (let index = 0; index < children.length; index += 1) {
39      const child = children[index];
40      if (!child) continue;
41      visit(child, [...ancestorIsLast, isLast], index === children.length - 1);
42    }
43  };
44
45  const roots = childrenByRootId.get(undefined) ?? [];
46  for (let index = 0; index < roots.length; index += 1) {
47    const root = roots[index];
48    if (!root) continue;
49    visit(root, [], index === roots.length - 1);
50  }
51
52  return items;
53}
54
55export function formatPruneTreeItem(item: PruneTreeItem): string {
56  const label = item.label?.replace(/\s+/g, " ").trim();
57  const preview = label ? `[${label}] ${item.candidate.preview}` : item.candidate.preview;
58  return `${preview} · ${formatEntryCount(item.candidate.entryCount)}`;
59}
60
61export function formatEntryCount(count: number): string {
62  return `${count} ${count === 1 ? "entry" : "entries"}`;
63}
64
65function findParentCandidate(
66  candidate: BranchCandidate,
67  candidates: readonly BranchCandidate[],
68): BranchCandidate | undefined {
69  return candidates
70    .filter(
71      (possibleParent) =>
72        possibleParent.rootId !== candidate.rootId &&
73        possibleParent.entryIds.includes(candidate.forkPointId),
74    )
75    .sort((left, right) => left.entryCount - right.entryCount)[0];
76}