From a3095ead3803196279c061adbecd0fd552ca49de Mon Sep 17 00:00:00 2001 From: hypercross Date: Fri, 14 Aug 2026 10:59:20 +0800 Subject: [PATCH] feat(web): move mod header into site header Replace the global TTS Workshop header on mod routes with a ModHeader showing the thumbnail, game name, and download/setup links styled like the Search/BGM nav. --- apps/web/src/App.tsx | 34 +++++++++------ apps/web/src/components/ModHeader.tsx | 59 +++++++++++++++++++++++++++ apps/web/src/pages/ModPage.tsx | 34 --------------- 3 files changed, 80 insertions(+), 47 deletions(-) create mode 100644 apps/web/src/components/ModHeader.tsx diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 92936cc..0b53aaa 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,7 +1,8 @@ import { lazy, Suspense } from 'react'; -import { Link, Route, Routes } from 'react-router-dom'; +import { Link, Route, Routes, useLocation } from 'react-router-dom'; import SearchPage from './pages/SearchPage'; import ModPage from './pages/ModPage'; +import ModHeader from './components/ModHeader'; // The full-setup view and the bgm/tabletop pages pull in the whole three.js // stack (Scene + all mesh viewers + @tts/tabletop). Lazy-load them so that @@ -18,22 +19,29 @@ const SetupsPage = lazy(() => import('./pages/SetupsPage')); const SetupPage = lazy(() => import('./pages/SetupPage')); export default function App() { + const { pathname } = useLocation(); + const isModRoute = pathname.startsWith('/mod/'); + return (
-
- - TTS Workshop - - -
+ +
+ )}
diff --git a/apps/web/src/components/ModHeader.tsx b/apps/web/src/components/ModHeader.tsx new file mode 100644 index 0000000..913c43b --- /dev/null +++ b/apps/web/src/components/ModHeader.tsx @@ -0,0 +1,59 @@ +import { useMemo } from 'react'; +import { Link, useParams } from 'react-router-dom'; +import { buildTree, collectRefs } from '@tts/extract'; +import { useModStore } from '../stores/modStore'; +import { useSearchStore } from '../stores/searchStore'; +import { modFileUrl } from '../api'; + +/** + * Replaces the global site header on the mod page: thumbnail, game name, and + * a subheader with the mod id and stats, plus download/setup links styled like + * the Search/BGM nav. + */ +export default function ModHeader() { + const { id } = useParams<{ id: string }>(); + const { mod } = useModStore(); + const item = useSearchStore((s) => s.items.find((i) => i.id === id)); + const tree = useMemo(() => (mod ? buildTree(mod.mod) : []), [mod]); + const refs = useMemo(() => (mod ? collectRefs(mod.mod) : []), [mod]); + // Prefer the metadata fetched with the save (survives a refresh); fall back + // to the search result for the brief moment before the save loads. + const title = mod?.title ?? item?.title; + const previewImageUrl = mod?.previewImageUrl ?? item?.previewImageUrl; + + return ( +
+
+ {previewImageUrl && ( + {title} + )} +
+

+ {title ?? `Mod ${id}`} +

+ {mod && ( +

+ {id} · {mod.mod.GameMode} ·{' '} + {mod.mod.Date} · {tree.length} objects · {refs.length} asset refs +

+ )} +
+
+ +
+ ); +} \ No newline at end of file diff --git a/apps/web/src/pages/ModPage.tsx b/apps/web/src/pages/ModPage.tsx index 0ac0653..e80a442 100644 --- a/apps/web/src/pages/ModPage.tsx +++ b/apps/web/src/pages/ModPage.tsx @@ -4,7 +4,6 @@ import { Link, useParams } from 'react-router-dom'; import { buildTree, collectRefs } from '@tts/extract'; import { useModStore } from '../stores/modStore'; import { useSearchStore } from '../stores/searchStore'; -import { modFileUrl } from '../api'; import ObjectTree from '../components/ObjectTree'; import { ErrorBoundary } from '@tts/tabletop'; import { resolveViewer } from '../components/viewers'; @@ -41,39 +40,6 @@ export default function ModPage() { return (
-
- {item?.previewImageUrl && ( - {item.title} - )} -
-

- {item?.title ?? `Mod ${id}`} -

-

- {id} · {mod.GameMode} ·{' '} - {mod.Date} · {tree.length} objects · {refs.length} asset refs -

-
-
- - Download save file - - - Full setup - -
-
-