import { Suspense, useEffect, useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { flattenObjects } from '@tts/extract'; import type { TTSObject } from '@tts/shared'; import { useModStore } from '../stores/modStore'; import { useSearchStore } from '../stores/searchStore'; import Scene from '../components/viewers/Scene'; import { TileObjectMesh } from '../components/viewers/TileViewer'; import { TokenObjectMesh } from '../components/viewers/TokenViewer'; import { CardObjectMesh } from '../components/viewers/CardViewer'; import { CustomModelMesh } from '../components/viewers/CustomModelViewer'; import { objectPlacement } from '../components/viewers/transform'; /** Object classes that carry renderable assets (mirrors `viewers/register.ts`). */ const RENDERABLE = new Set([ 'Tile', 'Custom_Tile', 'Custom_Token', 'Card', 'CardCustom', 'Deck', 'DeckCustom', 'Custom_Deck', 'Custom_Model', 'Custom_Model_Bag', 'Custom_Model_Infinite_Bag', ]); /** * A "full setup" view: renders every loadable object in a save into a single * shared scene, placed at its real position from the save's `Transform`. * Geometry and materials are shared across objects (see * `components/viewers/sharedResources.ts`). */ export default function FullSetupPage() { const { id } = useParams<{ id: string }>(); const { mod, loading, error, load } = useModStore(); const item = useSearchStore((s) => s.items.find((i) => i.id === id)); useEffect(() => { if (id) load(id, item?.fileUrl); }, [id, item?.fileUrl, load]); const objects = useMemo( () => (mod ? flattenObjects(mod) : []), [mod], ); const renderable = useMemo( () => objects.filter((o) => RENDERABLE.has(o.Name)), [objects], ); const skipped = objects.length - renderable.length; const placed = useMemo( () => renderable .map((object) => ({ object, placement: objectPlacement(object) })) .filter( (p): p is { object: TTSObject; placement: NonNullable> } => p.placement !== null, ), [renderable], ); if (loading) return

Loading mod…

; if (error) return

{error}

; if (!mod) return

No mod loaded.

; return (

Full setup

{objects.length} objects · {renderable.length} rendered {skipped > 0 ? ` · ${skipped} skipped` : ''}

{/* Key by index, not GUID: cards in a deck share the deck's GUID. */} {placed.map(({ object, placement }, index) => ( ))}
); } /** Dispatch a renderable object to the mesh component for its class. */ function RenderObject({ object }: { object: TTSObject }) { switch (object.Name) { case 'Tile': case 'Custom_Tile': return ; case 'Custom_Token': return ; case 'Card': case 'CardCustom': case 'Deck': case 'DeckCustom': case 'Custom_Deck': return ; case 'Custom_Model': case 'Custom_Model_Bag': case 'Custom_Model_Infinite_Bag': return ; default: return null; } }