import type { ReactNode } from 'react'; import { ProceduralDemoPage } from '../pages/ProceduralDemoPage'; import { getDefaultProceduralDemoId, getProceduralDemoMachine, type ProceduralDemoMachineId, } from '../modules/machines/procedural/proceduralDemoCatalog'; export type ProceduralDemoRouteKind = 'none' | 'catalogue' | 'machine'; export interface ProceduralDemoRouteMatch { kind: ProceduralDemoRouteKind; matched: boolean; basePath?: string; machineId?: ProceduralDemoMachineId; } export interface ProceduralDemoRouteOutletProps { pathname?: string; search?: string; fallback?: ReactNode; embedded?: boolean; } const BASE_SEGMENTS = new Set(['procedural-demo', 'procedural-demos', 'procedural-lab', 'demo-lab']); function pathSegments(pathname: string): string[] { return pathname.split('/').map(decodeURIComponent).filter(Boolean); } function queryMachineId(search: string): ProceduralDemoMachineId | undefined { const params = new URLSearchParams(search); const raw = params.get('machine') ?? params.get('demo') ?? params.get('id'); return getProceduralDemoMachine(raw)?.id; } export function matchProceduralDemoRoute( pathname = typeof window !== 'undefined' ? window.location.pathname : '/', search = typeof window !== 'undefined' ? window.location.search : '', ): ProceduralDemoRouteMatch { const segments = pathSegments(pathname); const queryId = queryMachineId(search); if (segments.length === 0) { return { kind: 'none', matched: false }; } const [first, second, third] = segments; if (BASE_SEGMENTS.has(first)) { if (second) { const machine = getProceduralDemoMachine(second); if (machine) { return { kind: 'machine', matched: true, basePath: `/${first}`, machineId: machine.id, }; } } if (queryId) { return { kind: 'machine', matched: true, basePath: `/${first}`, machineId: queryId, }; } return { kind: 'catalogue', matched: true, basePath: `/${first}`, }; } if ((first === 'viewer' || first === 'machines' || first === 'machine') && second) { const directMachine = getProceduralDemoMachine(second); if (directMachine) { return { kind: 'machine', matched: true, basePath: `/${first}`, machineId: directMachine.id, }; } if ((second === 'procedural' || second === 'procedural-demo' || second === 'procedural-demos') && third) { const nestedMachine = getProceduralDemoMachine(third); if (nestedMachine) { return { kind: 'machine', matched: true, basePath: `/${first}/${second}`, machineId: nestedMachine.id, }; } } } if (queryId && (first === 'viewer' || first === 'machines' || first === 'catalogue' || first === 'catalog')) { return { kind: 'machine', matched: true, basePath: `/${first}`, machineId: queryId, }; } return { kind: 'none', matched: false }; } export function buildProceduralDemoPath( machineId: string | undefined = getDefaultProceduralDemoId(), basePath = '/procedural-demos', ): string { const machine = getProceduralDemoMachine(machineId) ?? getProceduralDemoMachine(getDefaultProceduralDemoId()); return `${basePath.replace(/\/$/, '')}/${machine?.id ?? getDefaultProceduralDemoId()}`; } export const proceduralDemoRoutes = [ { path: '/procedural-demos', label: 'Procedural demo catalogue', element: , }, { path: '/procedural-demos/:machineId', label: 'Procedural machine demo', element: , }, { path: '/viewer/:machineId', label: 'Viewer-compatible procedural machine demo', element: , }, ] as const; export function ProceduralDemoRouteOutlet({ pathname, search, fallback = null, embedded = false, }: ProceduralDemoRouteOutletProps) { const match = matchProceduralDemoRoute(pathname, search); if (!match.matched || match.kind !== 'machine') { return <>{fallback}; } return ; }