177 lines
6.2 KiB
JavaScript
177 lines
6.2 KiB
JavaScript
// svrnty_nav.js — injects Svrnty sidebar buttons into hermes-webui's
|
||
// .sidebar-nav and wraps switchPanel so our panels participate in the existing
|
||
// main-view show/hide system (showing-<name> on <main>).
|
||
//
|
||
// Each panel module (adwright.js, bte.js) mounts its content inside <main>
|
||
// and CSS keys visibility off main.showing-adwright / main.showing-bte.
|
||
//
|
||
// IIFE, idempotent — guarded by window.__svrntyNavLoaded.
|
||
|
||
(function () {
|
||
"use strict";
|
||
if (window.__svrntyNavLoaded) return;
|
||
window.__svrntyNavLoaded = true;
|
||
const LOG = (...a) => console.log("[svrnty-nav]", ...a);
|
||
const ERR = (...a) => console.error("[svrnty-nav]", ...a);
|
||
LOG("loaded", { readyState: document.readyState });
|
||
|
||
// SVG paths only — sizes/strokes templated per container (rail uses 20×20
|
||
// stroke 1.5; sidebar-nav uses 18×18 stroke 2). Matches existing buttons.
|
||
const ICONS = {
|
||
adwright:
|
||
'<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
|
||
bte:
|
||
'<path d="M12 2l1.8 5.6L19.4 9.4l-4.5 3.3 1.7 5.7L12 15l-4.6 3.4 1.7-5.7L4.6 9.4l5.6-1.8L12 2z"/>',
|
||
canvas:
|
||
'<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M8 3v18M16 3v18M3 8h18M3 16h18"/>',
|
||
"cortex-os":
|
||
'<path d="M4 7h16M4 12h16M4 17h10"/><circle cx="17" cy="17" r="3"/>',
|
||
};
|
||
const TABS = [
|
||
{ id: "adwright", label: "Adwright", tooltip: "Adwright — marketing intelligence" },
|
||
{ id: "bte", label: "BTE", tooltip: "BTE — brand creative studio" },
|
||
{ id: "canvas", label: "Canvas", tooltip: "Canvas — sovereign UI design" },
|
||
{ id: "cortex-os", label: "Cortex OS", tooltip: "Cortex OS — Runtime Health" },
|
||
];
|
||
|
||
function _svg(iconPath, size, stroke) {
|
||
return (
|
||
'<svg width="' + size + '" height="' + size + '" viewBox="0 0 24 24" ' +
|
||
'fill="none" stroke="currentColor" stroke-width="' + stroke + '" ' +
|
||
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
|
||
iconPath + "</svg>"
|
||
);
|
||
}
|
||
|
||
// Two containers: .rail (desktop, .rail-btn nav-tab 20×20 stroke-1.5) +
|
||
// .sidebar-nav (mobile, .nav-tab 18×18 stroke-2). Inject into BOTH if present.
|
||
const CONTAINERS = [
|
||
{
|
||
selector: ".rail",
|
||
btnClass: "rail-btn nav-tab has-tooltip svrnty-nav-tab",
|
||
size: 20,
|
||
stroke: 1.5,
|
||
tooltipMod: "",
|
||
},
|
||
{
|
||
selector: ".sidebar-nav",
|
||
btnClass: "nav-tab has-tooltip has-tooltip--bottom svrnty-nav-tab",
|
||
size: 18,
|
||
stroke: 2,
|
||
tooltipMod: "--bottom",
|
||
},
|
||
];
|
||
|
||
function _injectButtons() {
|
||
let anyContainerFound = false;
|
||
let totalAdded = 0;
|
||
CONTAINERS.forEach((c) => {
|
||
const container = document.querySelector(c.selector);
|
||
if (!container) return;
|
||
anyContainerFound = true;
|
||
TABS.forEach((t) => {
|
||
if (container.querySelector('[data-panel="' + t.id + '"]')) return;
|
||
try {
|
||
const btn = document.createElement("button");
|
||
btn.className = c.btnClass;
|
||
btn.setAttribute("data-panel", t.id);
|
||
btn.setAttribute("data-label", t.label);
|
||
btn.setAttribute("data-tooltip", t.tooltip);
|
||
btn.setAttribute("aria-label", t.label);
|
||
btn.innerHTML = _svg(ICONS[t.icon || t.id], c.size, c.stroke);
|
||
btn.addEventListener("click", () => {
|
||
LOG("clicked:", t.id);
|
||
if (typeof window.switchPanel === "function") {
|
||
window.switchPanel(t.id, { fromRailClick: true });
|
||
} else {
|
||
ERR("switchPanel undefined — cannot route click");
|
||
}
|
||
});
|
||
container.appendChild(btn);
|
||
totalAdded++;
|
||
} catch (e) {
|
||
ERR("inject failed", c.selector, t.id, e);
|
||
}
|
||
});
|
||
});
|
||
if (!anyContainerFound) {
|
||
LOG("_injectButtons: neither .rail nor .sidebar-nav present yet");
|
||
return false;
|
||
}
|
||
LOG("_injectButtons: added", totalAdded, "buttons across containers");
|
||
return true;
|
||
}
|
||
|
||
// Wrap switchPanel to add showing-<id> class on <main> for our IDs.
|
||
// We chain the original so all upstream behavior (collapse rail, hide other
|
||
// panels, toggle data-panel active) keeps working unchanged.
|
||
function _wrapSwitchPanel() {
|
||
if (typeof window.switchPanel !== "function") return false;
|
||
if (window.switchPanel.__svrntyWrapped) return true;
|
||
const original = window.switchPanel;
|
||
const OUR_IDS = TABS.map((t) => t.id);
|
||
|
||
async function wrapped(name, opts) {
|
||
const result = await original(name, opts);
|
||
const main = document.querySelector("main.main");
|
||
if (main) {
|
||
OUR_IDS.forEach((id) => {
|
||
main.classList.toggle("svrnty-showing-" + id, name === id);
|
||
});
|
||
}
|
||
// Notify panel modules so they can lazy-init/refresh.
|
||
try {
|
||
window.dispatchEvent(
|
||
new CustomEvent("svrnty:panel-switch", { detail: { name } }),
|
||
);
|
||
} catch (_) {}
|
||
return result;
|
||
}
|
||
wrapped.__svrntyWrapped = true;
|
||
window.switchPanel = wrapped;
|
||
return true;
|
||
}
|
||
|
||
let _initAttempts = 0;
|
||
function _init() {
|
||
_initAttempts++;
|
||
const buttonsOk = _injectButtons();
|
||
const wrapOk = _wrapSwitchPanel();
|
||
if (!buttonsOk || !wrapOk) {
|
||
if (_initAttempts < 100) {
|
||
requestAnimationFrame(_init);
|
||
} else {
|
||
ERR("_init gave up after", _initAttempts, "attempts. buttonsOk=", buttonsOk, "wrapOk=", wrapOk);
|
||
}
|
||
return;
|
||
}
|
||
LOG("_init completed in", _initAttempts, "attempt(s)");
|
||
}
|
||
|
||
if (document.readyState === "loading") {
|
||
document.addEventListener("DOMContentLoaded", _init);
|
||
} else {
|
||
_init();
|
||
}
|
||
|
||
// Re-inject buttons if something re-renders rail or sidebar-nav (defensive).
|
||
const obs = new MutationObserver(() => {
|
||
CONTAINERS.forEach((c) => {
|
||
const container = document.querySelector(c.selector);
|
||
if (!container) return;
|
||
const missing = TABS.some((t) => !container.querySelector('[data-panel="' + t.id + '"]'));
|
||
if (missing) _injectButtons();
|
||
});
|
||
});
|
||
if (document.body) {
|
||
obs.observe(document.body, { childList: true, subtree: true });
|
||
} else {
|
||
document.addEventListener("DOMContentLoaded", () =>
|
||
obs.observe(document.body, { childList: true, subtree: true }),
|
||
);
|
||
}
|
||
|
||
// Expose namespace
|
||
window.SvrntyNav = { TABS, _injectButtons, _wrapSwitchPanel };
|
||
})();
|