/ concept-collection / numbl-figure-viewer
Sign in
concept-collection / numbl-figure-viewer
numbl-figure-viewer / src / ObjectTree.tsx
103 lines · 2.4 KBCodeBlameHistory
6ab510bVS Code-style layout: object tree + figure + details panelJeremy Magland 1import { useState } from "react";
2import type { TreeNode, NodeIcon } from "./tree";
4const ICONS: Record<NodeIcon, string> = {
5 figure: "▦",
6 axes: "▢",
7 trace: "∿",
8 dataset: "⋯",
9};
11function NodeRow({
12 node,
13 depth,
14 expanded,
15 toggle,
16 selectedId,
17 onSelect,
18}: {
19 node: TreeNode;
20 depth: number;
21 expanded: Set<string>;
22 toggle: (id: string) => void;
23 selectedId: string | null;
24 onSelect: (id: string) => void;
25}) {
26 const hasChildren = node.children.length > 0;
27 const isOpen = expanded.has(node.id);
28 const isSelected = selectedId === node.id;
29 return (
30 <>
31 <div
32 className={"tree-row" + (isSelected ? " selected" : "")}
33 style={{ paddingLeft: 4 + depth * 14 }}
34 onClick={() => onSelect(node.id)}
35 role="treeitem"
36 aria-selected={isSelected}
37 aria-expanded={hasChildren ? isOpen : undefined}
38 >
39 <span
40 className={"twisty" + (hasChildren ? "" : " leaf")}
41 onClick={e => {
42 e.stopPropagation();
43 if (hasChildren) toggle(node.id);
44 }}
45 >
46 {hasChildren ? (isOpen ? "▾" : "▸") : ""}
47 </span>
48 <span className={"node-icon icon-" + node.icon}>{ICONS[node.icon]}</span>
49 <span className="node-label">{node.label}</span>
50 </div>
51 {hasChildren &&
52 isOpen &&
53 node.children.map(child => (
54 <NodeRow
55 key={child.id}
56 node={child}
57 depth={depth + 1}
58 expanded={expanded}
59 toggle={toggle}
60 selectedId={selectedId}
61 onSelect={onSelect}
62 />
63 ))}
64 </>
65 );
68export function ObjectTree({
69 root,
70 selectedId,
71 onSelect,
72}: {
73 root: TreeNode;
74 selectedId: string | null;
75 onSelect: (id: string) => void;
76}) {
77 // Expand the figure and its axes by default.
78 const [expanded, setExpanded] = useState<Set<string>>(() => {
79 const s = new Set<string>([root.id]);
80 for (const axes of root.children) s.add(axes.id);
81 return s;
82 });
83 const toggle = (id: string) =>
84 setExpanded(prev => {
85 const next = new Set(prev);
86 if (next.has(id)) next.delete(id);
87 else next.add(id);
88 return next;
89 });
91 return (
92 <div className="tree" role="tree">
93 <NodeRow
94 node={root}
95 depth={0}
96 expanded={expanded}
97 toggle={toggle}
98 selectedId={selectedId}
99 onSelect={onSelect}
100 />
101 </div>
102 );
moveopenescclose