// app.js — entry point. Routes hash changes to view renderers, keeps // the top-nav active state in sync, and re-renders chrome strings when // the locale changes. import { onRoute } from "./state.js?v=persist-all-jobs-1"; import { t, currentLocale, setLocale, onLocale } from "./i18n.js?v=persist-all-jobs-1"; import { renderRemotes } from "./views/remotes.js?v=persist-all-jobs-1"; import { renderBrowse } from "./views/browser.js?v=persist-all-jobs-1"; import { renderJobs, renderNewJob, stopJobPolling } from "./views/jobs.js?v=persist-all-jobs-1"; import { renderConfigureNew, renderConfigureEdit, } from "./views/configure.js?v=persist-all-jobs-1"; const views = { remotes: renderRemotes, browse: renderBrowse, jobs: renderJobs, "jobs-new": renderNewJob, "configure-new": renderConfigureNew, "configure-edit": renderConfigureEdit, }; let currentRoute = { name: "remotes", params: {} }; function setActiveNav(routeName) { const links = document.querySelectorAll("#nav-links a"); for (const a of links) { const target = a.dataset.route; let isActive = target === routeName; if (target === "configure" && routeName.startsWith("configure-")) { isActive = true; } a.classList.toggle("active", isActive); } } function applyChromeStrings() { // nav links document.querySelectorAll("#nav-links a").forEach((a) => { const key = a.dataset.route === "configure" ? "nav.configure" : `nav.${a.dataset.route}`; a.textContent = t(key); }); const newJobBtn = document.querySelector(".nav-new-job"); if (newJobBtn) newJobBtn.textContent = t("nav.new_job"); const lt = document.getElementById("loading-title"); if (lt) lt.textContent = t("loading.title"); const lb = document.getElementById("loading-body"); if (lb) lb.innerHTML = t("loading.connecting"); // Anything with data-i18n attribute (footer) document.querySelectorAll("[data-i18n]").forEach((el) => { el.textContent = t(el.dataset.i18n); }); // lang buttons const locale = currentLocale(); document.querySelectorAll(".lang-btn").forEach((btn) => { btn.classList.toggle("active", btn.dataset.lang === locale); }); } function wireLangSwitcher() { document.querySelectorAll(".lang-btn").forEach((btn) => { btn.addEventListener("click", () => setLocale(btn.dataset.lang)); }); } onRoute((route) => { currentRoute = route; setActiveNav(route.name); if (route.name !== "jobs") { stopJobPolling(); } const renderer = views[route.name] || renderRemotes; renderer(route.params).catch((e) => { console.error("view error", e); const app = document.getElementById("app"); if (app) { app.innerHTML = `

${t("error.title")}

${escapeHtml(e.message)}

`; } }); }); // Re-render everything on locale change so views pick up new strings. onLocale(() => { applyChromeStrings(); const renderer = views[currentRoute.name] || renderRemotes; renderer(currentRoute.params).catch((e) => { console.error("view error on locale change", e); }); }); // Initial chrome paint + switcher wiring. applyChromeStrings(); wireLangSwitcher(); function escapeHtml(s) { return String(s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }