import { readJSON, enhance, icon } from "./ui.js"; import { defaults, catalogView, bindCatalog, overviewView, detailView, bindDetail, } from "./catalog.js"; import { methodsView, somaView, samplesView } from "./reference.js"; import synthetic from "./synthetic.js?v=history-20261103"; const app = document.getElementById("app"); let context; let currentRoute; function route() { const [path, search = ""] = (location.hash.slice(1) || "catalog").split("?"); const [page, part] = path.split("/"); return { page, part, params: new URLSearchParams(search) }; } function filtersFrom(params) { const filters = { ...defaults }; for (const key of [ "q", "source", "method", "tier", "status", "sort", "route", ]) { if (params.has(key)) filters[key] = params.get(key); } filters.preview = params.get("preview") === "true"; if (!["name", "year", "status"].includes(filters.sort)) filters.sort = "name"; return filters; } function render() { if (!context) return; synthetic.unmount(); app.querySelectorAll("video").forEach((video) => video.pause()); const { page, part, params } = route(); let active = page, title, html; const datasetId = page.startsWith("ds-") ? page.slice(3) : null; if (datasetId) { const tab = ["overview", "annotation", "access", "conversion"].includes( part, ) ? part : "overview"; title = context.byId[datasetId]?.name || "未找到数据集"; active = "catalog"; html = detailView(datasetId, tab, context); } else if (page === "synthetic") { title = "EgoRenderer"; html = synthetic.render(); } else if (page === "overview") { title = "数据分布"; html = overviewView(context); } else if (page === "methods") { title = "标注方法"; html = methodsView(context); } else if (page === "soma") { title = "SOMA转换"; html = somaView(context); } else if (page === "samples") { title = "样例"; html = samplesView(context); } else { title = "数据集"; active = "catalog"; html = catalogView(context, filtersFrom(params)); } app.innerHTML = html; document.title = `${title} · EgoNet Datasets`; document.querySelectorAll("[data-page]").forEach((link) => { if (link.dataset.page === active) link.setAttribute("aria-current", "page"); else link.removeAttribute("aria-current"); }); if (active === "catalog" && !datasetId) bindCatalog(context, filtersFrom(params)); if (datasetId) bindDetail(datasetId, context); if (page === "synthetic") synthetic.mount(); enhance(); document.getElementById("nav-toggle").setAttribute("aria-expanded", "false"); document.getElementById("site-nav").classList.remove("is-open"); const heading = app.querySelector("h1"); if (heading) { heading.tabIndex = -1; heading.focus({ preventScroll: true }); } const anchor = part && (page === "methods" ? "method-" + part : page === "soma" ? "route-" + part : null); if (anchor && document.getElementById(anchor)) document.getElementById(anchor).scrollIntoView(); else if (currentRoute !== location.hash) window.scrollTo(0, 0); currentRoute = location.hash; } async function boot() { app.innerHTML = '
读取数据集…
'; try { const [data, previews, figures, videos, sampling] = await Promise.all([ readJSON("data/datasets.json"), ...["previews", "figures", "videos", "sampling"].map((name) => readJSON(`data/${name}.json`, { optional: true }), ), ]); if ( !Array.isArray(data.datasets) || new Set(data.datasets.map((d) => d.id)).size !== data.datasets.length ) throw new Error("Dataset schema"); const by = (items) => Object.fromEntries( items.map((value, order) => [value.id, { ...value, order }]), ); context = { data, previews, figures, videos, sampling, byId: by(data.datasets), meta: { source: by(data.meta.sources), method: by(data.meta.methods), tier: by(data.meta.tiers), status: by(data.meta.statuses), route: by(data.meta.routes), }, }; document.getElementById("catalog-count").textContent = data.datasets.length; document.getElementById("data-updated").textContent = data.meta.updated; render(); } catch (_) { app.innerHTML = ``; document.getElementById("retry-load").onclick = boot; enhance(); } } const themeButton = document.getElementById("theme-toggle"); let savedTheme; try { savedTheme = localStorage.getItem("atlas-theme"); } catch (_) { /* Embedded storage may be restricted. */ } function setTheme(theme) { document.documentElement.dataset.theme = theme; themeButton.title = `切换${theme === "dark" ? "浅色" : "深色"}模式`; themeButton.setAttribute("aria-label", themeButton.title); themeButton.innerHTML = ``; enhance(); } setTheme( savedTheme || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"), ); themeButton.onclick = () => { const theme = document.documentElement.dataset.theme === "dark" ? "light" : "dark"; try { localStorage.setItem("atlas-theme", theme); } catch (_) { /* Keep the current theme without persistence. */ } setTheme(theme); }; document.getElementById("nav-toggle").onclick = (event) => { const button = event.currentTarget; const open = button.getAttribute("aria-expanded") !== "true"; button.setAttribute("aria-expanded", String(open)); document.getElementById("site-nav").classList.toggle("is-open", open); }; window.addEventListener("hashchange", render); document.querySelector(".skip-link").onclick = (event) => { event.preventDefault(); app.focus(); app.scrollIntoView(); }; document.addEventListener("keydown", (event) => { if ( event.key === "Escape" && document.getElementById("site-nav").classList.contains("is-open") ) { document.getElementById("site-nav").classList.remove("is-open"); document .getElementById("nav-toggle") .setAttribute("aria-expanded", "false"); document.getElementById("nav-toggle").focus(); } }); boot();