DarthSoluke's picture
Document all EgoRenderer versions and audited GRAB rendering campaigns
b0b604c verified
Raw History Blame Contribute Delete
6.5 kB
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 = '<div class="loading-state" role="status">读取数据集…</div>';
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 = `<div class="empty-state" role="alert"><i data-lucide="wifi-off" aria-hidden="true"></i><h1>数据集暂不可用</h1>${icon("refresh-cw", "重新加载", 'id="retry-load"')}</div>`;
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 = `<i data-lucide="${theme === "dark" ? "sun" : "moon"}" aria-hidden="true"></i>`;
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();