fix(web): select tree nodes by index path instead of GUID

Cards in a deck share the deck's GUID, so GUID-based selection highlighted
every card with that GUID and rendered the first match. Key selection by the
node's unique index path instead.
This commit is contained in:
2026-08-08 17:42:16 +08:00
parent 835250abdd
commit 55e0351c1d
2 changed files with 28 additions and 24 deletions
+14 -11
View File
@@ -5,11 +5,11 @@ import { iconsForObject } from './objectIcons';
interface Props {
nodes: ObjectTreeNode[];
selectedGuid: string | null;
onSelect: (guid: string) => void;
selectedPath: string | null;
onSelect: (path: string) => void;
}
export default function ObjectTree({ nodes, selectedGuid, onSelect }: Props) {
export default function ObjectTree({ nodes, selectedPath, onSelect }: Props) {
return (
<ul className="space-y-0.5">
{nodes.map((node, index) => (
@@ -18,7 +18,7 @@ export default function ObjectTree({ nodes, selectedGuid, onSelect }: Props) {
node={node}
depth={0}
path={`${index}`}
selectedGuid={selectedGuid}
selectedPath={selectedPath}
onSelect={onSelect}
/>
))}
@@ -30,17 +30,20 @@ function TreeNode({
node,
depth,
path,
selectedGuid,
selectedPath,
onSelect,
}: {
node: ObjectTreeNode;
depth: number;
/** Index path from the root, used as a stable unique key. */
/** Index path from the root, used as a stable unique key and selection id. */
path: string;
selectedGuid: string | null;
onSelect: (guid: string) => void;
selectedPath: string | null;
onSelect: (path: string) => void;
}) {
const selected = node.object.GUID === selectedGuid;
// Selection is keyed by the node's unique index path, not its GUID: cards in
// a deck frequently share a GUID (the deck's), so GUID-based selection would
// highlight and render the wrong card.
const selected = path === selectedPath;
const hasChildren = node.children.length > 0;
const [expanded, setExpanded] = useState(false);
return (
@@ -69,7 +72,7 @@ function TreeNode({
<span className="h-6 w-6 shrink-0" />
)}
<button
onClick={() => onSelect(node.object.GUID)}
onClick={() => onSelect(path)}
className="block min-w-0 flex-1 truncate py-1 text-left text-sm"
>
<span
@@ -91,7 +94,7 @@ function TreeNode({
node={child}
depth={depth + 1}
path={`${path}-${index}`}
selectedGuid={selectedGuid}
selectedPath={selectedPath}
onSelect={onSelect}
/>
))}
+14 -13
View File
@@ -16,7 +16,10 @@ export default function ModPage() {
const { id } = useParams<{ id: string }>();
const { mod, loading, error, load } = useModStore();
const item = useSearchStore((s) => s.items.find((i) => i.id === id));
const [selectedGuid, setSelectedGuid] = useState<string | null>(null);
// Selection is keyed by the node's index path, not its GUID: cards in a
// deck share the deck's GUID, so GUID-based selection would resolve to the
// wrong card.
const [selectedPath, setSelectedPath] = useState<string | null>(null);
useEffect(() => {
if (id) load(id, item?.fileUrl);
@@ -26,8 +29,8 @@ export default function ModPage() {
const refs = useMemo(() => (mod ? collectRefs(mod) : []), [mod]);
const selected = useMemo(
() => (mod ? findInTree(tree, selectedGuid) : undefined),
[tree, selectedGuid],
() => (mod ? findInTree(tree, selectedPath) : undefined),
[tree, selectedPath],
);
if (loading) return <p className="text-sm text-zinc-400">Loading mod</p>;
@@ -56,8 +59,8 @@ export default function ModPage() {
<aside className="rounded-lg border border-zinc-800 bg-zinc-900 p-2">
<ObjectTree
nodes={tree}
selectedGuid={selectedGuid}
onSelect={setSelectedGuid}
selectedPath={selectedPath}
onSelect={setSelectedPath}
/>
</aside>
@@ -105,13 +108,11 @@ export default function ModPage() {
function findInTree(
nodes: ReturnType<typeof buildTree>,
guid: string | null,
path: string | null,
): ReturnType<typeof buildTree>[number] | undefined {
if (!guid) return undefined;
for (const node of nodes) {
if (node.object.GUID === guid) return node;
const found = findInTree(node.children, guid);
if (found) return found;
}
return undefined;
if (!path) return undefined;
const [head, ...rest] = path.split('-');
const node = nodes[Number(head)];
if (!node) return undefined;
return rest.length === 0 ? node : findInTree(node.children, rest.join('-'));
}