import type { SelectionStage } from '../components/platform-entry'; export type RuntimePageRoute = 'rpg-character-select' | 'rpg-adventure'; export const PUBLIC_WORK_QUERY_PARAM = 'work'; const STAGE_ROUTE_ENTRIES = [ ['platform', '/'], ['profile-feedback', '/profile/feedback'], ['work-detail', '/works/detail'], ['detail', '/worlds/detail'], ['agent-workspace', '/creation/rpg/agent'], ['custom-world-generating', '/creation/rpg/generating'], ['custom-world-result', '/creation/rpg/result'], ['big-fish-agent-workspace', '/creation/big-fish/agent'], ['big-fish-result', '/creation/big-fish/result'], ['big-fish-runtime', '/runtime/big-fish'], ['match3d-agent-workspace', '/creation/match3d/agent'], ['match3d-result', '/creation/match3d/result'], ['match3d-runtime', '/runtime/match3d'], ['square-hole-agent-workspace', '/creation/square-hole/agent'], ['square-hole-result', '/creation/square-hole/result'], ['square-hole-runtime', '/runtime/square-hole'], ['creative-agent-workspace', '/creation/creative-agent'], ['visual-novel-agent-workspace', '/creation/visual-novel/agent'], ['visual-novel-result', '/creation/visual-novel/result'], ['visual-novel-gallery-detail', '/gallery/visual-novel/detail'], ['visual-novel-runtime', '/runtime/visual-novel'], ['baby-object-match-workspace', '/creation/baby-object-match'], ['baby-object-match-generating', '/creation/baby-object-match/generating'], ['baby-object-match-result', '/creation/baby-object-match/result'], ['baby-object-match-runtime', '/runtime/baby-object-match'], ['baby-love-drawing-runtime', '/runtime/baby-love-drawing'], ['puzzle-agent-workspace', '/creation/puzzle/agent'], ['puzzle-result', '/creation/puzzle/result'], ['puzzle-gallery-detail', '/gallery/puzzle/detail'], ['puzzle-runtime', '/runtime/puzzle'], ] as const satisfies readonly (readonly [SelectionStage, string])[]; export const APP_STAGE_ROUTES: Record = Object.fromEntries(STAGE_ROUTE_ENTRIES) as Record; export const APP_RUNTIME_ROUTES: Record = { 'rpg-character-select': '/runtime/rpg/characters', 'rpg-adventure': '/runtime/rpg/adventure', }; const ROUTE_STAGE_BY_PATH = new Map( STAGE_ROUTE_ENTRIES.map(([stage, path]) => [path, stage] as const), ) as Map; export function normalizeAppPath(pathname: string) { const trimmedPathname = pathname.trim().toLowerCase(); if (!trimmedPathname || trimmedPathname === '/') { return '/'; } return trimmedPathname.replace(/\/+$/u, ''); } export function resolveSelectionStageFromPath( pathname: string, ): SelectionStage { return ROUTE_STAGE_BY_PATH.get(normalizeAppPath(pathname)) ?? 'platform'; } export function resolvePathForSelectionStage(stage: SelectionStage) { return APP_STAGE_ROUTES[stage] ?? APP_STAGE_ROUTES.platform; } export function readPublicWorkCodeFromLocationSearch(search: string) { const params = new URLSearchParams(search); return params.get(PUBLIC_WORK_QUERY_PARAM)?.trim() || null; } export function buildPublicWorkDetailPath(publicWorkCode: string) { return buildPublicWorkStagePath('work-detail', publicWorkCode); } export function buildPublicWorkStagePath( stage: SelectionStage, publicWorkCode: string, ) { const code = publicWorkCode.trim(); const stagePath = resolvePathForSelectionStage(stage); if (!code) { return stagePath; } const params = new URLSearchParams(); params.set(PUBLIC_WORK_QUERY_PARAM, code); return `${stagePath}?${params.toString()}`; } export function buildPublicWorkDetailUrl( publicWorkCode: string, origin = window.location.origin, ) { return new URL(buildPublicWorkDetailPath(publicWorkCode), origin).href; } export function isKnownMainAppPagePath(pathname: string) { const normalizedPath = normalizeAppPath(pathname); const runtimePaths: readonly string[] = Object.values(APP_RUNTIME_ROUTES); return ( ROUTE_STAGE_BY_PATH.has(normalizedPath) || runtimePaths.includes(normalizedPath) ); } export function pushAppHistoryPath(path: string) { const nextUrl = new URL(path, window.location.origin); const normalizedPath = normalizeAppPath(nextUrl.pathname); const nextRelativeUrl = `${normalizedPath}${nextUrl.search}`; const currentRelativeUrl = `${normalizeAppPath(window.location.pathname)}${window.location.search}`; if (currentRelativeUrl === nextRelativeUrl) { return; } // 页面阶段变化是用户可感知导航,写入 history 以支持前进后退。 window.history.pushState(null, '', nextRelativeUrl); }