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:
@@ -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}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -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('-'));
|
||||
}
|
||||
Reference in New Issue
Block a user