Add 3D object viewers with r3f stack

Add per-class 3D viewers for tiles, tokens, cards, and custom models
using React Three Fiber, drei, and postprocessing. Viewers are
lazy-loaded and registered through the existing viewer registry, with a
shared scene wrapper for lighting, orbit controls, and subtle effects.

Add a CORS-safe /asset proxy route so three.js loaders can fetch
Workshop-hosted textures and models, and extend TTSObject with the
CustomMesh and CustomTile/CustomToken fields the viewers read.
This commit is contained in:
2026-08-08 12:56:46 +08:00
parent f390f170da
commit 001d5eeb54
18 changed files with 1037 additions and 14 deletions
+33 -6
View File
@@ -58,12 +58,13 @@ tts-workshop/
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── src/
│ │ ├── index.ts # Hono app + @hono/node-server
│ │ ├── routes/
│ │ │ ├── search.ts # GET /search?q=...&page=1
│ │ │ ├── items.ts # GET /items/:id, /items/:id/file
│ │ ── health.ts # GET /health
│ │ └── env.ts # zod env validation
│ │ ├── index.ts # Hono app + @hono/node-server
│ │ ├── routes/
│ │ │ ├── search.ts # GET /search?q=...&page=1
│ │ │ ├── items.ts # GET /items/:id, /items/:id/file
│ │ │ ── asset.ts # GET /asset?url=... (CORS-safe asset proxy)
│ │ └── health.ts # GET /health
│ │ └── env.ts # zod env validation
│ └── web/
│ ├── package.json
│ ├── tsconfig.json
@@ -81,6 +82,14 @@ tts-workshop/
│ │ ├── SearchResults.tsx
│ │ ├── ObjectTree.tsx # containment-tree sidebar
│ │ ├── viewers.tsx # viewer registry + default viewer
│ │ ├── viewers/ # 3D viewers (r3f/drei/postprocessing)
│ │ │ ├── register.ts # registers per-class 3D viewers (lazy)
│ │ │ ├── Scene.tsx # shared Canvas: lights, controls, post
│ │ │ ├── TileViewer.tsx
│ │ │ ├── TokenViewer.tsx
│ │ │ ├── CardViewer.tsx
│ │ │ ├── CustomModelViewer.tsx
│ │ │ └── assetUrl.ts # route asset URLs through the proxy
│ │ ├── objectIcons.tsx # class → icon mapping
│ │ ├── objectIconsData.ts # generated icon subset (do not edit)
│ │ └── objectIcons.test.ts
@@ -188,6 +197,11 @@ Hono server exposing search + fetch.
otherwise resolves via the Steam API.
- `GET /items/:id/file` — raw save bytes, filename from `getFileName`. Also
accepts `fileUrl`.
- `routes/asset.ts`
- `GET /asset?url=...` — fetch an external asset (texture, model) and stream
it back with a `Content-Type` header. Workshop hosts often omit CORS
headers, which would block three.js loaders in the browser; routing through
the proxy makes those assets loadable. Only `http(s)` URLs are allowed.
- `routes/health.ts`
- `GET /health` — liveness.
- `env.ts` — zod validation of `STEAM_API_KEY`, `PORT`.
@@ -212,6 +226,16 @@ proxy API and `packages/extract` directly for analysis.
- `components/viewers.tsx` — viewer registry (`registerViewer` /
`resolveViewer`) plus a `DefaultViewer` that renders an object's fields;
custom per-class viewers can be registered later.
- `components/viewers/` — 3D viewers built on `@react-three/fiber`,
`@react-three/drei`, and `@react-three/postprocessing`. `register.ts`
registers lazy-loaded viewers for `Tile`/`Custom_Tile` (flat box),
`Custom_Token` (cylinder), `Card`/`Deck`/`Custom_Deck` (thin box with
face/back textures), and `Custom_Model`/`Custom_Model_Bag`/
`Custom_Model_Infinite_Bag` (GLTF/OBJ/FBX from `CustomMesh.MeshURL`).
`Scene.tsx` is a shared canvas with lighting, orbit controls, contact
shadows, and subtle bloom/vignette. `assetUrl.ts` routes asset URLs through
the proxy for CORS-safe loading. The viewers are lazy-loaded so the three.js
stack is code-split out of the main bundle.
- `components/objectIcons.tsx` — maps TTS object classes to one or more
Iconify icons (`iconsForObject`); unknown classes fall back to a help icon.
Icons may come from multiple sets (mdi, material-symbols, file-icons, ...);
@@ -234,6 +258,7 @@ proxy API and `packages/extract` directly for analysis.
| GET | `/search?q=&page=` | Scrape Workshop browse, return item list | — |
| GET | `/items/:id` | Full parsed `TTSMod` (`?fileUrl=` skips key) | key* |
| GET | `/items/:id/file` | Raw save bytes, filename from header | key* |
| GET | `/asset?url=` | CORS-safe proxy for external assets | — |
\* `STEAM_API_KEY` is optional; `/items/*` works without it when a `fileUrl`
query param is supplied.
@@ -259,6 +284,8 @@ packages/extract → flatten objects / extract refs / download assets
- `cheerio` — Workshop browse page scraping (backend only).
- `zod` — validation.
- `react`, `react-dom`, `react-router-dom`, `zustand` — frontend.
- `three`, `@react-three/fiber`, `@react-three/drei`,
`@react-three/postprocessing` — 3D object viewers.
- `@iconify/react`, `@iconify-json/mdi`, `@iconify/utils` — iconify icons for
object class tags (subset bundled via `scripts/generate-object-icons.mjs`).
- `vite`, `@vitejs/plugin-react`, `tailwindcss`, `@tailwindcss/vite` — frontend tooling.