// Pages menu in the dashboard chrome: one button that opens a popover // linking the standalone pages (flow, stats, API docs) and the swarm UI. // Modelled on the swarm UI's `LinksMenu`. The swarm entry only exists // when `/api/state` carries `swarm_public_url`, so a hive without a // configured swarm domain shows no dead link. import { $ } from "./common.js"; import { el } from "@hive/shared/dom.js"; /** Menu entries for a state snapshot. `external` opens in a new tab. */ export function menuLinks(state) { const links = [ { icon: "📡", label: "flow", url: "/flow.html" }, { icon: "📊", label: "stats", url: "/stats.html" }, { icon: "🧬", label: "api docs", url: "/api/docs/" }, ]; if (state && state.swarm_public_url) { links.push({ icon: "🐝", label: "swarm", url: state.swarm_public_url, external: true, }); } return links; } let wired = false; function closeMenu(root) { const pop = root.querySelector(".links-menu-popover"); const btn = root.querySelector(".links-menu-button"); if (pop) pop.hidden = true; if (btn) btn.setAttribute("aria-expanded", "false"); } function wire(root) { if (wired) return; wired = true; root.querySelector(".links-menu-button").addEventListener("click", () => { const pop = root.querySelector(".links-menu-popover"); const btn = root.querySelector(".links-menu-button"); pop.hidden = !pop.hidden; btn.setAttribute("aria-expanded", String(!pop.hidden)); }); // Outside click / Escape close the popover. document.addEventListener("pointerdown", (e) => { if (!root.contains(e.target)) closeMenu(root); }); document.addEventListener("keydown", (e) => { if (e.key === "Escape") closeMenu(root); }); } /** (Re)render the menu into `#links-menu`. Safe to call on every state * refresh: the open/closed state lives on the popover element, which is * kept; only its items are rebuilt. */ export function renderLinksMenu(state) { const root = $("links-menu"); if (!root) return; if (!root.firstChild) { root.append( el( "button", { type: "button", class: "links-menu-button", "aria-haspopup": "true", "aria-expanded": "false", "aria-label": "pages", title: "pages", }, "☰", ), el("div", { class: "links-menu-popover", role: "menu", hidden: "" }), ); } const pop = root.querySelector(".links-menu-popover"); pop.replaceChildren( ...menuLinks(state).map((link) => { const attrs = { class: "links-menu-item", href: link.url, role: "menuitem", }; if (link.external) { attrs.target = "_blank"; attrs.rel = "noreferrer"; } return el( "a", attrs, el("span", { "aria-hidden": "true" }, link.icon), link.label, ); }), ); wire(root); }