// Top-level app โ€” owns view state and renders topbar + active view. // View routing is purely client-side; the SPA reads window.location on first // load so deep links like /feature/4/4714 land directly on the feature page. function parseInitialView() { const path = window.location.pathname || "/"; const parts = path.split("/").filter(Boolean); // Empty path โ†’ landing if (parts.length === 0) return { kind: "landing" }; // /layer/ if (parts[0] === "layer" && parts[1]) { const layer = parseInt(parts[1], 10); if (!Number.isNaN(layer)) return { kind: "layer", layer }; } // /feature// (preferred) OR /feature/ (legacy single-layer) if (parts[0] === "feature") { if (parts.length >= 3) { const layer = parseInt(parts[1], 10); const fid = parseInt(parts[2], 10); if (!Number.isNaN(layer) && !Number.isNaN(fid)) return { kind: "feature", layer, featureId: fid }; } else if (parts.length === 2) { const fid = parseInt(parts[1], 10); if (!Number.isNaN(fid)) return { kind: "feature", layer: null, featureId: fid }; } } // /case//[/family// | /feature/] if (parts[0] === "case" && parts[1] && parts[2]) { const layer = parseInt(parts[2], 10); if (!Number.isNaN(layer)) { // Family detail under granularity if (parts[1] === "granularity" && parts[3] === "family" && parts[4] && parts[5]) { return { kind: "casestudy", id: "granularity", layer, family: { source: parts[4], code: decodeURIComponent(parts[5]) }, }; } // Feature detail under metagenomic transfer (Figure 4-style side-by-side) if (parts[1] === "metagenomic" && parts[3] === "feature" && parts[4]) { const fid = parseInt(parts[4], 10); if (!Number.isNaN(fid)) { return { kind: "casestudy", id: "metagenomic", layer, featureId: fid }; } } return { kind: "casestudy", id: parts[1], layer }; } } return { kind: "landing" }; } function pushUrl(view) { const path = (() => { if (view.kind === "landing") return "/"; if (view.kind === "layer") return `/layer/${view.layer}`; if (view.kind === "feature") return `/feature/${view.layer}/${view.featureId}`; if (view.kind === "casestudy") { if (view.family) { return `/case/${view.id}/${view.layer}/family/${view.family.source}/${encodeURIComponent(view.family.code)}`; } if (view.featureId != null) { return `/case/${view.id}/${view.layer}/feature/${view.featureId}`; } return `/case/${view.id}/${view.layer}`; } return "/"; })(); if (window.location.pathname !== path) { window.history.pushState({}, "", path); } } function App() { const layersList = useFetch(API.layers, []); const landing = useFetch(API.landing, []); const [view, setView] = React.useState(parseInitialView); React.useEffect(() => { pushUrl(view); window.scrollTo({ top: 0, behavior: "instant" }); }, [view]); React.useEffect(() => { const onPop = () => setView(parseInitialView()); window.addEventListener("popstate", onPop); return () => window.removeEventListener("popstate", onPop); }, []); // If feature view is missing a layer (e.g. legacy URL), fall back to the only // available layer if just one is loaded; otherwise prompt to pick. React.useEffect(() => { if (view.kind === "feature" && view.layer == null && layersList.data && layersList.data.length === 1) { setView({ kind: "feature", layer: layersList.data[0].layer, featureId: view.featureId }); } }, [view, layersList.data]); const breadcrumb = (
setView({ kind: "landing" })}>GeoPedia {view.layer != null && ( / setView({ kind: "layer", layer: view.layer })}>Layer {view.layer} )} {view.kind === "feature" && ( / f/{view.featureId} )} {view.kind === "casestudy" && (() => { const sublabel = view.id === "geom-fills-db" ? "case 01" : view.id === "granularity" ? "case 02" : view.id === "metagenomic" ? "case 03" : view.id; const isLeaf = !view.family && view.featureId == null; return ( / {isLeaf ? ( {sublabel} ) : ( setView({ kind: "casestudy", id: view.id, layer: view.layer })}> {sublabel} )} {view.family && ( / {view.family.code} )} {view.featureId != null && ( / f/{view.featureId} )} ); })()}
); const layersAvailable = (layersList.data || []).map(L => L.layer); return (

setView({ kind: "landing" })} style={{cursor:'pointer'}}>GeoPedia

v0.5 {view.kind !== "landing" && {breadcrumb}}
{view.kind === "landing" && ( setView({ kind: "layer", layer })} onPickFeature={(layer, featureId) => setView({ kind: "feature", layer, featureId })} /> )} {view.kind === "layer" && ( setView({ kind: "feature", layer, featureId })} onOpenCaseStudy={(id) => setView({ kind: "casestudy", id, layer: view.layer })} layersAvailable={layersAvailable} /> )} {view.kind === "feature" && view.layer != null && ( setView({ kind: "layer", layer: view.layer })} /> )} {view.kind === "feature" && view.layer == null && (
)} {view.kind === "casestudy" && view.id === "geom-fills-db" && ( setView({ kind: "feature", layer, featureId })} /> )} {view.kind === "casestudy" && view.id === "granularity" && !view.family && ( setView({ kind: "feature", layer, featureId })} onPickFamily={(source, code) => setView({ kind: "casestudy", id: "granularity", layer: view.layer, family: { source, code }, })} /> )} {view.kind === "casestudy" && view.id === "granularity" && view.family && ( setView({ kind: "casestudy", id: "granularity", layer: view.layer })} onPickFeature={(layer, featureId) => setView({ kind: "feature", layer, featureId })} /> )} {view.kind === "casestudy" && view.id === "metagenomic" && view.featureId == null && ( setView({ kind: "casestudy", id: "metagenomic", layer: view.layer, featureId })} layersAvailable={layersAvailable} /> )} {view.kind === "casestudy" && view.id === "metagenomic" && view.featureId != null && ( setView({ kind: "casestudy", id: "metagenomic", layer: view.layer })} onOpenFeaturePage={(featureId) => setView({ kind: "feature", layer: view.layer, featureId })} /> )}
GeoPedia ยท ESM-2 SAE feature atlas {layersList.data ? `${layersList.data.length} layer${layersList.data.length === 1 ? '' : 's'} loaded` : ''}
); } // Error boundary so a runtime crash inside a view doesn't blank the screen. // Without this, React 18 unmounts the whole tree on an uncaught error and // you just see an empty page with no clue why. class RootErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { error: null, info: null }; } static getDerivedStateFromError(error) { return { error }; } componentDidCatch(error, info) { console.error("GeoPedia crash:", error, info); this.setState({ info }); } render() { if (this.state.error) { return (

Something crashed

            {String(this.state.error?.stack || this.state.error)}
          
{this.state.info?.componentStack && (
              {this.state.info.componentStack}
            
)}

โ† back to landing

); } return this.props.children; } } ReactDOM.createRoot(document.getElementById("root")).render( );