/** * MiR-style 3-column navigation: primary rail + flyout submenu + content. */ (function () { const STORAGE_MODULE = "mirNavModule"; const STORAGE_SECTION = "mirNavSection"; const STORAGE_FLYOUT = "mirNavFlyoutOpen"; const MODULES = { dashboards: { items: [{ section: "dashboard-list", page: "dashboard" }], dynamic: true, }, setup: { items: [ { section: "missions", page: "missions" }, { section: "maps", page: "maps" }, { section: "sounds", page: "sounds" }, { section: "build-robot", page: "config" }, ], }, monitoring: { items: [{ section: "monitoring-log", page: "monitoring" }], }, system: { items: [{ section: "integrations", page: "integrations" }], }, help: { items: [{ section: "help-api", page: "help" }], }, }; const PAGE_NAV = { dashboard: { module: "dashboards", section: "dashboard-list" }, config: { module: "setup", section: "build-robot" }, maps: { module: "setup", section: "maps" }, missions: { module: "setup", section: "missions" }, sounds: { module: "setup", section: "sounds" }, integrations: { module: "system", section: "integrations" }, monitoring: { module: "monitoring", section: "monitoring-log" }, help: { module: "help", section: "help-api" }, }; let activeModule = "setup"; let activeSection = "missions"; let flyoutOpen = true; const shellEl = () => document.getElementById("mirNavShell"); const flyoutListEl = () => document.getElementById("mirNavFlyoutList"); const flyoutTitleEl = () => document.getElementById("mirNavFlyoutTitle"); const backBtnEl = () => document.getElementById("mirNavBackBtn"); function t(key) { return window.I18n?.t(`nav.${key}`) ?? key; } function canAccessPage(page) { if (window.AuthApp?.canAccessPage) return window.AuthApp.canAccessPage(page); return true; } function moduleItems(moduleId) { const mod = MODULES[moduleId]; if (!mod) return []; if (mod.dynamic && moduleId === "dashboards" && window.DashboardApp?.getNavItems) { return window.DashboardApp.getNavItems(); } return mod.items; } function visibleItems(moduleId) { const mod = MODULES[moduleId]; if (!mod) return []; return moduleItems(moduleId).filter((item) => canAccessPage(item.page)); } function itemLabel(item) { if (item.label) return item.label; return t(item.section); } function moduleHasAccess(moduleId) { return visibleItems(moduleId).length > 0; } function saveState() { try { localStorage.setItem(STORAGE_MODULE, activeModule); localStorage.setItem(STORAGE_SECTION, activeSection); localStorage.setItem(STORAGE_FLYOUT, flyoutOpen ? "1" : "0"); } catch { /* ignore */ } } function renderFlyout() { const list = flyoutListEl(); const title = flyoutTitleEl(); if (!list || !title) return; const items = visibleItems(activeModule); title.textContent = t(activeModule); list.replaceChildren(); items.forEach((item) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "mirNavFlyoutItem"; btn.dataset.section = item.section; btn.dataset.page = item.page; btn.textContent = itemLabel(item); if (item.section === activeSection) { btn.classList.add("is-active"); btn.setAttribute("aria-current", "page"); } btn.addEventListener("click", () => selectSection(item.section, item.page)); list.appendChild(btn); }); } function updateRailUI() { document.querySelectorAll(".mirNavRailItem[data-module]").forEach((btn) => { const mod = btn.dataset.module || ""; const allowed = moduleHasAccess(mod); btn.hidden = !allowed; btn.style.display = allowed ? "" : "none"; const on = mod === activeModule && flyoutOpen; btn.classList.toggle("is-active", on); if (on) btn.setAttribute("aria-current", "true"); else btn.removeAttribute("aria-current"); }); const shell = shellEl(); if (shell) shell.classList.toggle("mirNavShell--flyout-collapsed", !flyoutOpen); const back = backBtnEl(); if (back) { const label = flyoutOpen ? t("collapse") : t("expand"); back.title = label; back.setAttribute("aria-label", label); } renderFlyout(); } function selectModule(moduleId, opts = {}) { if (!MODULES[moduleId] || !moduleHasAccess(moduleId)) return; if (moduleId === activeModule && flyoutOpen && !opts.forceSection) { flyoutOpen = false; saveState(); updateRailUI(); return; } activeModule = moduleId; flyoutOpen = true; const items = visibleItems(moduleId); const keepSection = items.some((i) => i.section === activeSection); if (!keepSection || opts.forceSection) { const preferred = items.find((i) => i.section === opts.section) || items[0]; if (preferred) { activeSection = preferred.section; if (!opts.skipPage) navigateToPage(preferred.page); } } else if (!opts.skipPage) { const current = items.find((i) => i.section === activeSection); if (current) navigateToPage(current.page); } saveState(); updateRailUI(); } function selectSection(section, page) { activeSection = section; saveState(); updateRailUI(); navigateToPage(page); if (page === "dashboard") window.DashboardApp?.handleNav?.(section); } function syncDashboardSection(section) { activeModule = "dashboards"; activeSection = section.startsWith("dashboard-") || section === "dashboard-list" ? section : `dashboard-${section}`; saveState(); updateRailUI(); } function navigateToPage(page) { if (window.LmApp?.setActivePage) window.LmApp.setActivePage(page); } function syncFromPage(page) { const nav = PAGE_NAV[page]; if (!nav) return; activeModule = nav.module; activeSection = nav.section; saveState(); updateRailUI(); if (page === "dashboard") window.DashboardApp?.handleNav?.(activeSection); } function toggleFlyout() { flyoutOpen = !flyoutOpen; saveState(); updateRailUI(); } function applyPermissions() { const modules = Object.keys(MODULES); if (!moduleHasAccess(activeModule)) { const fallback = modules.find((m) => moduleHasAccess(m)); if (fallback) selectModule(fallback, { forceSection: true, skipPage: false }); } else { const items = visibleItems(activeModule); if (!items.some((i) => i.section === activeSection)) { activeSection = items[0]?.section || activeSection; } } updateRailUI(); } function restoreInitialPage() { let page = "missions"; try { const saved = localStorage.getItem("activePage"); if (saved && PAGE_NAV[saved]) page = saved; } catch { /* ignore */ } try { const savedMod = localStorage.getItem(STORAGE_MODULE); const savedSec = localStorage.getItem(STORAGE_SECTION); const savedFlyout = localStorage.getItem(STORAGE_FLYOUT); if (savedMod && MODULES[savedMod]) activeModule = savedMod; if (savedSec) activeSection = savedSec; if (savedFlyout === "0") flyoutOpen = false; } catch { /* ignore */ } const nav = PAGE_NAV[page]; if (nav && moduleHasAccess(nav.module)) { activeModule = nav.module; activeSection = nav.section; } else { const modItems = visibleItems(activeModule); const match = modItems.find((i) => i.page === page) || modItems[0]; if (match) { activeSection = match.section; page = match.page; } } updateRailUI(); navigateToPage(page); } function refreshFlyout() { updateRailUI(); } function refreshLabels() { window.I18n?.applyDOM?.(); updateRailUI(); } function bindEvents() { document.querySelectorAll(".mirNavRailItem[data-module]").forEach((btn) => { btn.addEventListener("click", () => selectModule(btn.dataset.module || "setup")); }); backBtnEl()?.addEventListener("click", toggleFlyout); document.getElementById("mirNavLogout")?.addEventListener("click", () => { window.AuthApp?.logout?.(); }); window.addEventListener("lm:locale-change", () => refreshLabels()); } function init() { refreshLabels(); bindEvents(); applyPermissions(); restoreInitialPage(); } window.NavApp = { init, syncFromPage, syncDashboardSection, applyPermissions, selectModule, selectSection, toggleFlyout, refreshFlyout, }; })();