diff --git a/static/app.js b/static/app.js index 0249997..63096e9 100644 --- a/static/app.js +++ b/static/app.js @@ -39,12 +39,16 @@ async function createRequest(body) { } } +let toastTimer; + function toast(msg, isError = false) { const t = $("#toast"); t.textContent = msg; t.className = isError ? "error" : ""; t.hidden = false; - setTimeout(() => (t.hidden = true), 4000); + // an older toast's timer would otherwise hide this one early + clearTimeout(toastTimer); + toastTimer = setTimeout(() => (t.hidden = true), 4000); } // quotes included: the result also lands inside attributes (value="${esc(...)}") @@ -88,17 +92,26 @@ async function refreshMissingBadge() { } // ---- navigation ---- +// the view lives in the hash so reload, back button and links keep it +const VIEWS = ["search", "missing", "imported", "import", "settings"]; + +function showView() { + const hash = location.hash.slice(1); + const name = VIEWS.includes(hash) ? hash : "search"; + $$("nav button[data-view]").forEach((b) => b.classList.toggle("active", b.dataset.view === name)); + $$("main > section").forEach((s) => (s.hidden = s.id !== "view-" + name)); + if (name === "missing" || name === "imported") loadRequests(name); + if (name === "settings") loadLibraries().catch((err) => toast(err.message, true)); +} + $$("nav button[data-view]").forEach((btn) => btn.addEventListener("click", () => { - $$("nav button[data-view]").forEach((b) => b.classList.remove("active")); - btn.classList.add("active"); - $$("main > section").forEach((s) => (s.hidden = true)); - $("#view-" + btn.dataset.view).hidden = false; - if (btn.dataset.view === "missing") loadRequests("missing"); - if (btn.dataset.view === "imported") loadRequests("imported"); - if (btn.dataset.view === "settings") loadLibraries(); + // same hash fires no hashchange, but a click should still reload the list + if (location.hash === "#" + btn.dataset.view) showView(); + else location.hash = btn.dataset.view; }) ); +window.addEventListener("hashchange", showView); // ---- libraries ---- async function loadLibraries() { @@ -513,7 +526,15 @@ async function loadRequests(status) { if (t) params.set("media_type", t); if (l) params.set("library_id", l); } - currentRequests[status] = await api("/api/requests?" + params); + const box = status === "missing" ? $("#missing-list") : $("#imported-list"); + if (!currentRequests[status].length) { + box.innerHTML = `
${t("common.loading")}
`; + } + try { + currentRequests[status] = await api("/api/requests?" + params); + } catch (err) { + toast(err.message, true); + } renderRequests(status); if (status === "missing") refreshMissingBadge(); } @@ -530,7 +551,7 @@ function renderRequests(status) { } box.innerHTML = reqs .map( - (r) => `