feat(web): add iconify tag icons for object classes

Replace the text class tag in the tree and inspector with one or more
MDI icons (hover shows the class name). Bundle only the used icons via
a generator script instead of the full set.
This commit is contained in:
2026-08-08 12:32:11 +08:00
parent 3b66e82b67
commit 4f37dabbe7
8 changed files with 235 additions and 4 deletions
+9 -2
View File
@@ -1,5 +1,7 @@
import { useState } from 'react';
import { Icon } from '@iconify/react';
import type { ObjectTreeNode } from '@tts/extract';
import { iconsForObject } from './objectIcons';
interface Props {
nodes: ObjectTreeNode[];
@@ -70,8 +72,13 @@ function TreeNode({
onClick={() => onSelect(node.object.GUID)}
className="block min-w-0 flex-1 truncate py-1 text-left text-sm"
>
<span className="mr-1.5 rounded bg-zinc-800 px-1 py-0.5 text-xs uppercase text-zinc-500">
{node.object.Name}
<span
title={node.object.Name}
className="mr-1.5 inline-flex items-center gap-0.5 align-middle text-zinc-400"
>
{iconsForObject(node.object.Name).map((icon) => (
<Icon key={icon} icon={icon} className="h-4 w-4" />
))}
</span>
<span className="truncate">{node.label}</span>
</button>
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';
import { iconsForObject } from './objectIcons';
describe('iconsForObject', () => {
it('maps known classes to icons', () => {
expect(iconsForObject('Card')).toEqual(['mdi:cards-outline']);
expect(iconsForObject('Custom_Model_Bag')).toEqual([
'mdi:bag-personal-outline',
'mdi:cube-outline',
]);
});
it('falls back to the help icon for unknown classes', () => {
expect(iconsForObject('Unknown_Thing')).toEqual(['mdi:help-circle-outline']);
});
});
+28
View File
@@ -0,0 +1,28 @@
import './objectIconsData';
/**
* Map of TTS object class (`TTSObject.Name`) to one or more MDI icon names.
* Icons are registered in `objectIcons.ts` (generated) and referenced here by
* name. Unknown classes fall back to a help icon.
*/
const OBJECT_ICONS: Record<string, string[]> = {
Card: ['mdi:cards-outline'],
Deck: ['mdi:cards'],
Custom_Deck: ['mdi:cards'],
Bag: ['mdi:bag-personal-outline'],
Custom_Model_Bag: ['mdi:bag-personal-outline', 'mdi:cube-outline'],
Custom_Model: ['mdi:cube-outline'],
Custom_Token: ['mdi:circle-outline'],
Custom_Image: ['mdi:image-outline'],
Custom_PDF: ['mdi:file-pdf-box'],
Dice: ['mdi:dice-6'],
Figurine: ['mdi:chess-knight'],
Board: ['mdi:table-large'],
};
const FALLBACK = ['mdi:help-circle-outline'];
/** Resolve the icon name(s) for an object class. */
export function iconsForObject(name: string): string[] {
return OBJECT_ICONS[name] ?? FALLBACK;
}
@@ -0,0 +1,49 @@
// Generated by scripts/generate-object-icons.mjs — do not edit by hand.
// Re-run `node scripts/generate-object-icons.mjs` after changing ICON_NAMES.
import { addCollection } from '@iconify/react';
const mdiSubset = {
"prefix": "mdi",
"width": 24,
"height": 24,
"icons": {
"cards-outline": {
"body": "<path fill=\"currentColor\" d=\"M11.19 2.25c-.26 0-.52.06-.77.15L3.06 5.45a1.994 1.994 0 0 0-1.09 2.6L6.93 20a2 2 0 0 0 1.81 1.25c.26 0 .53-.03.79-.15l7.37-3.05a2.02 2.02 0 0 0 1.23-1.8c.01-.25-.04-.54-.13-.8L13 3.5a1.95 1.95 0 0 0-1.81-1.25m3.48 0l3.45 8.35V4.25a2 2 0 0 0-2-2m4.01 1.54v9.03l2.43-5.86a1.99 1.99 0 0 0-1.09-2.6m-10.28-.14l4.98 12.02l-7.39 3.06L3.8 7.29\"/>"
},
"cards": {
"body": "<path fill=\"currentColor\" d=\"m21.47 4.35l-1.34-.56v9.03l2.43-5.86c.41-1.02-.06-2.19-1.09-2.61m-19.5 3.7L6.93 20a2.01 2.01 0 0 0 1.81 1.26c.26 0 .53-.05.79-.16l7.37-3.05c.75-.31 1.21-1.05 1.23-1.79c.01-.26-.04-.55-.13-.81L13 3.5a1.95 1.95 0 0 0-1.81-1.25c-.26 0-.52.06-.77.15L3.06 5.45a1.994 1.994 0 0 0-1.09 2.6m16.15-3.8a2 2 0 0 0-2-2h-1.45l3.45 8.34\"/>"
},
"bag-personal-outline": {
"body": "<path fill=\"currentColor\" d=\"M16 5V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v1a4 4 0 0 0-4 4v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9a4 4 0 0 0-4-4m-6-1h4v1h-4zm2 5l2 2l-2 2l-2-2zm6 11H6v-4h2v2h1v-2h9zm0-5H6V9a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z\"/>"
},
"cube-outline": {
"body": "<path fill=\"currentColor\" d=\"M21 16.5c0 .38-.21.71-.53.88l-7.9 4.44c-.16.12-.36.18-.57.18s-.41-.06-.57-.18l-7.9-4.44A.99.99 0 0 1 3 16.5v-9c0-.38.21-.71.53-.88l7.9-4.44c.16-.12.36-.18.57-.18s.41.06.57.18l7.9 4.44c.32.17.53.5.53.88zM12 4.15L6.04 7.5L12 10.85l5.96-3.35zM5 15.91l6 3.38v-6.71L5 9.21zm14 0v-6.7l-6 3.37v6.71z\"/>"
},
"cube": {
"body": "<path fill=\"currentColor\" d=\"M21 16.5c0 .38-.21.71-.53.88l-7.9 4.44c-.16.12-.36.18-.57.18s-.41-.06-.57-.18l-7.9-4.44A.99.99 0 0 1 3 16.5v-9c0-.38.21-.71.53-.88l7.9-4.44c.16-.12.36-.18.57-.18s.41.06.57.18l7.9 4.44c.32.17.53.5.53.88zM12 4.15L6.04 7.5L12 10.85l5.96-3.35z\"/>"
},
"circle-outline": {
"body": "<path fill=\"currentColor\" d=\"M12 20a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8m0-18A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2\"/>"
},
"image-outline": {
"body": "<path fill=\"currentColor\" d=\"M19 19H5V5h14m0-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2m-5.04 9.29l-2.75 3.54l-1.96-2.36L6.5 17h11z\"/>"
},
"file-pdf-box": {
"body": "<path fill=\"currentColor\" d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m-9.5 8.5c0 .8-.7 1.5-1.5 1.5H7v2H5.5V9H8c.8 0 1.5.7 1.5 1.5zm5 2c0 .8-.7 1.5-1.5 1.5h-2.5V9H13c.8 0 1.5.7 1.5 1.5zm4-3H17v1h1.5V13H17v2h-1.5V9h3zm-6.5 0h1v3h-1zm-5 0h1v1H7z\"/>"
},
"dice-6": {
"body": "<path fill=\"currentColor\" d=\"M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m2 2a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2m10 10a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2m0-5a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2m0-5a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2M7 10a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2m0 5a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2\"/>"
},
"chess-knight": {
"body": "<path fill=\"currentColor\" d=\"M19 22H5v-2h14zM13 2c-1.25 0-2.42.62-3.11 1.66L7 8l2 2l2.06-1.37c.44-.31 1.08-.19 1.39.27c.02.03.05.06.05.1c.3.59.19 1.3-.28 1.77l-4.8 4.8c-.55.56-.55 1.46.01 2.01c.26.26.62.42.99.42H17V6a4 4 0 0 0-4-4\"/>"
},
"table-large": {
"body": "<path fill=\"currentColor\" d=\"M4 3h16a2 2 0 0 1 2 2v15a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m0 4v3h4V7zm6 0v3h4V7zm10 3V7h-4v3zM4 12v3h4v-3zm0 8h4v-3H4zm6-8v3h4v-3zm0 8h4v-3h-4zm10 0v-3h-4v3zm0-8h-4v3h4z\"/>"
},
"help-circle-outline": {
"body": "<path fill=\"currentColor\" d=\"M11 18h2v-2h-2zm1-16A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8m0-14a4 4 0 0 0-4 4h2a2 2 0 0 1 2-2a2 2 0 0 1 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5a4 4 0 0 0-4-4\"/>"
}
}
};
addCollection(mdiSubset);