feat(web): replace flat lists with tree sidebar and object inspector

Add a containment-tree sidebar with class tags and display labels, plus
an inspector pane backed by a viewer registry with a default viewer.
Custom per-class viewers can be registered later.
This commit is contained in:
2026-08-08 12:19:43 +08:00
parent d8a698986a
commit a8e84c8b9d
3 changed files with 171 additions and 40 deletions
+68
View File
@@ -0,0 +1,68 @@
import type { ObjectTreeNode } from '@tts/extract';
interface Props {
nodes: ObjectTreeNode[];
selectedGuid: string | null;
onSelect: (guid: string) => void;
}
export default function ObjectTree({ nodes, selectedGuid, onSelect }: Props) {
return (
<ul className="space-y-0.5">
{nodes.map((node) => (
<TreeNode
key={node.object.GUID}
node={node}
depth={0}
selectedGuid={selectedGuid}
onSelect={onSelect}
/>
))}
</ul>
);
}
function TreeNode({
node,
depth,
selectedGuid,
onSelect,
}: {
node: ObjectTreeNode;
depth: number;
selectedGuid: string | null;
onSelect: (guid: string) => void;
}) {
const selected = node.object.GUID === selectedGuid;
return (
<li>
<button
onClick={() => onSelect(node.object.GUID)}
style={{ paddingLeft: `${depth * 16 + 8}px` }}
className={`block w-full truncate rounded px-2 py-1 text-left text-sm ${
selected
? 'bg-zinc-700 text-zinc-100'
: 'text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100'
}`}
>
<span className="mr-1.5 rounded bg-zinc-800 px-1 py-0.5 text-xs uppercase text-zinc-500">
{node.object.Name}
</span>
<span className="truncate">{node.label}</span>
</button>
{node.children.length > 0 && (
<ul>
{node.children.map((child) => (
<TreeNode
key={child.object.GUID}
node={child}
depth={depth + 1}
selectedGuid={selectedGuid}
onSelect={onSelect}
/>
))}
</ul>
)}
</li>
);
}