svrnty-hermes-webui-plugin/static/umbrella_inline.js
Svrnty c0ff59097c
Some checks failed
plugin-tests / test (push) Failing after 5s
upstream-drift / drift (push) Failing after 5s
Embed umbrella graph in workspace panel
2026-05-26 06:34:50 -04:00

211 lines
6.8 KiB
JavaScript

// umbrella_inline.js — mounts the Cortex-OS graph inside Hermes WebUI's
// Workspace right panel. Plugin-only shim; the standalone page remains a
// fallback at /plugins/svrnty/umbrella.html.
(function () {
"use strict";
if (window.__svrntyUmbrellaInlineLoaded) return;
window.__svrntyUmbrellaInlineLoaded = true;
const LOG = (...a) => console.log("[svrnty-umbrella-inline]", ...a);
const ICON =
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" ' +
'stroke="currentColor" stroke-width="2" stroke-linecap="round" ' +
'stroke-linejoin="round" aria-hidden="true">' +
'<circle cx="5" cy="12" r="2"/><circle cx="12" cy="5" r="2"/>' +
'<circle cx="19" cy="12" r="2"/><circle cx="12" cy="19" r="2"/>' +
'<path d="M6.7 10.6l3.9-4"/><path d="M13.4 6.6l3.9 4"/>' +
'<path d="M17.3 13.4l-3.9 4"/><path d="M10.6 17.4l-3.9-4"/>' +
'<path d="M7 12h10"/></svg>';
let surface = null;
let graphOpen = false;
let previousHeading = "Workspace";
let previousPanelWidth = "";
let openedPanelForGraph = false;
function $(id) {
return document.getElementById(id);
}
function isWorkspacePanelOpen() {
const htmlState = document.documentElement.dataset.workspacePanel;
const panel = document.querySelector(".rightpanel");
return htmlState === "open" && !!panel && getComputedStyle(panel).pointerEvents !== "none";
}
function ensureSurface() {
if (surface && document.body.contains(surface)) return surface;
const panel = document.querySelector(".rightpanel");
const preview = $("previewArea");
if (!panel || !preview) return null;
surface = document.createElement("div");
surface.id = "svrntyUmbrellaInlineSurface";
surface.className = "svrnty-umbrella-inline-surface";
surface.dataset.open = "false";
surface.innerHTML =
'<iframe class="svrnty-umbrella-inline-frame" ' +
'title="Project graph" src="/plugins/svrnty/umbrella.html?inline=1"></iframe>';
preview.insertAdjacentElement("afterend", surface);
return surface;
}
function setWorkspaceSurfaces(mode) {
const fileTree = $("fileTree");
const empty = $("wsEmptyState");
const preview = $("previewArea");
const breadcrumb = $("breadcrumbBar");
const graph = ensureSurface();
if (!graph) return;
const showGraph = mode === "graph";
if (fileTree) fileTree.style.display = showGraph ? "none" : "";
if (empty) empty.style.display = showGraph ? "none" : empty.style.display;
if (preview) preview.style.display = showGraph ? "none" : "";
if (breadcrumb) breadcrumb.style.display = showGraph ? "none" : breadcrumb.style.display;
graph.dataset.open = showGraph ? "true" : "false";
}
function syncHeader(open) {
const heading = $("workspacePanelHeading");
const button = $("btnSvrntyWorkspaceGraph");
if (heading) {
if (open) {
previousHeading = heading.textContent || "Workspace";
heading.textContent = "Project Graph";
heading.title = "Project Graph";
} else {
heading.textContent = previousHeading || "Workspace";
heading.title = "Workspace root";
}
}
if (button) {
button.classList.toggle("active", open);
button.setAttribute("aria-pressed", open ? "true" : "false");
}
}
function resizeGraphSoon() {
const frame = surface && surface.querySelector("iframe");
if (!frame) return;
setTimeout(() => {
try {
const cy = frame.contentWindow && frame.contentWindow.cy;
if (cy) {
cy.resize();
cy.fit(null, 24);
}
} catch (_) {}
}, 250);
}
function expandPanelForGraph() {
const panel = document.querySelector(".rightpanel");
if (!panel) return;
previousPanelWidth = panel.style.width || "";
const current = panel.getBoundingClientRect().width || 0;
if (current < 480) {
panel.style.width = "500px";
}
}
function restorePanelWidth() {
const panel = document.querySelector(".rightpanel");
if (!panel) return;
panel.style.width = previousPanelWidth;
previousPanelWidth = "";
}
function openWorkspaceGraph() {
if (graphOpen) {
resizeGraphSoon();
return;
}
openedPanelForGraph = !isWorkspacePanelOpen();
if (typeof window.openWorkspacePanel === "function") {
window.openWorkspacePanel("browse");
} else if (typeof window.toggleWorkspacePanel === "function") {
window.toggleWorkspacePanel(true);
}
expandPanelForGraph();
graphOpen = true;
setWorkspaceSurfaces("graph");
syncHeader(true);
resizeGraphSoon();
LOG("opened inline graph");
}
function closeWorkspaceGraph() {
if (!graphOpen) return false;
graphOpen = false;
setWorkspaceSurfaces("workspace");
syncHeader(false);
restorePanelWidth();
if (openedPanelForGraph && typeof window.closeWorkspacePanel === "function") {
window.closeWorkspacePanel();
} else if (typeof window.renderBreadcrumb === "function") {
window.renderBreadcrumb();
}
openedPanelForGraph = false;
LOG("closed inline graph");
return true;
}
function injectButton() {
const actions = document.querySelector(".rightpanel .panel-actions");
if (!actions || $("btnSvrntyWorkspaceGraph")) return !!actions;
const refresh = $("btnRefreshPanel");
const btn = document.createElement("button");
btn.className = "panel-icon-btn has-tooltip has-tooltip--bottom svrnty-graph-btn";
btn.id = "btnSvrntyWorkspaceGraph";
btn.type = "button";
btn.dataset.tooltip = "Project graph";
btn.setAttribute("aria-label", "Open project graph");
btn.setAttribute("aria-pressed", "false");
btn.innerHTML = ICON;
btn.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
openWorkspaceGraph();
});
if (refresh && refresh.parentElement === actions) {
refresh.insertAdjacentElement("afterend", btn);
} else {
actions.appendChild(btn);
}
ensureSurface();
return true;
}
function interceptCloseButton() {
const close = $("btnClearPreview");
if (!close || close.dataset.svrntyGraphCloseBound === "1") return !!close;
close.dataset.svrntyGraphCloseBound = "1";
close.addEventListener("click", (event) => {
if (!graphOpen) return;
event.preventDefault();
event.stopImmediatePropagation();
closeWorkspaceGraph();
}, true);
return true;
}
function init() {
const ok = injectButton() && interceptCloseButton();
if (!ok) {
requestAnimationFrame(init);
return;
}
window.SvrntyUmbrellaInline = {
open: openWorkspaceGraph,
close: closeWorkspaceGraph,
isOpen: () => graphOpen,
};
LOG("ready");
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();