Embed umbrella graph in workspace panel
This commit is contained in:
parent
28ffa92f6f
commit
c0ff59097c
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Upstream version:** v0.51.118
|
**Upstream version:** v0.51.118
|
||||||
**Plugin version:** 0.5.0
|
**Plugin version:** 0.5.0
|
||||||
**Total dependencies:** 32 (23 public API · 0 forced internal · 9 frontend)
|
**Total dependencies:** 34 (25 public API · 0 forced internal · 9 frontend)
|
||||||
|
|
||||||
> **Auto-generated by `scripts/ast-connection-map.py`. Do not hand-edit.**
|
> **Auto-generated by `scripts/ast-connection-map.py`. Do not hand-edit.**
|
||||||
> To change a justification, edit the `# CONNECTION:` comment above the
|
> To change a justification, edit the `# CONNECTION:` comment above the
|
||||||
@ -23,6 +23,8 @@
|
|||||||
| `plugin.py:43` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/adwright.js")` |
|
| `plugin.py:43` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/adwright.js")` |
|
||||||
| `plugin.py:45` | `api.inject_stylesheet` | `api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/bte.css")` |
|
| `plugin.py:45` | `api.inject_stylesheet` | `api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/bte.css")` |
|
||||||
| `plugin.py:46` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/bte.js")` |
|
| `plugin.py:46` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/bte.js")` |
|
||||||
|
| `plugin.py:48` | `api.inject_stylesheet` | `api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/umbrella_inline.css")` |
|
||||||
|
| `plugin.py:49` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/umbrella_inline.js")` |
|
||||||
| `routes/adwright.py:68` | `api.logger` | `log = api.logger("svrnty.routes.adwright")` |
|
| `routes/adwright.py:68` | `api.logger` | `log = api.logger("svrnty.routes.adwright")` |
|
||||||
| `routes/adwright.py:69` | `api.register_route` | `api.register_route(` |
|
| `routes/adwright.py:69` | `api.register_route` | `api.register_route(` |
|
||||||
| `routes/adwright.py:71` | `api.register_route` | `api.register_route(` |
|
| `routes/adwright.py:71` | `api.register_route` | `api.register_route(` |
|
||||||
@ -52,13 +54,13 @@ _None. Plugin uses only the public API._ ✓
|
|||||||
|
|
||||||
| File | Line | URL |
|
| File | Line | URL |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `static/bte.js` | 329 | `/api/command/requestPhotoshoot` |
|
| `static/bte.js` | 365 | `/api/command/requestPhotoshoot` |
|
||||||
| `static/bte.js` | 360 | `/api/query/assetDtos` |
|
| `static/bte.js` | 396 | `/api/query/assetDtos` |
|
||||||
| `static/bte.js` | 372 | `/api/assets/` |
|
| `static/bte.js` | 408 | `/api/assets/` |
|
||||||
| `static/bte.js` | 481 | `/api/command/rateAsset` |
|
| `static/bte.js` | 517 | `/api/command/rateAsset` |
|
||||||
| `static/adwright.js` | 176 | `/api/profile/switch` |
|
| `static/adwright.js` | 176 | `/api/profile/switch` |
|
||||||
| `static/adwright.js` | 197 | `/api/profile/active` |
|
| `static/adwright.js` | 197 | `/api/profile/active` |
|
||||||
| `static/adwright.js` | 606 | `/api/adwright/provision-creds` |
|
| `static/adwright.js` | 606 | `/api/adwright/provision-creds` |
|
||||||
| `static/umbrella.js` | 41 | `/api/umbrella` |
|
| `static/umbrella.js` | 57 | `/api/umbrella` |
|
||||||
| `static/app.js` | 165 | `/api/vault/status` |
|
| `static/app.js` | 165 | `/api/vault/status` |
|
||||||
|
|
||||||
|
|||||||
@ -33,10 +33,12 @@ assets:
|
|||||||
- /plugins/svrnty/svrnty_nav.js
|
- /plugins/svrnty/svrnty_nav.js
|
||||||
- /plugins/svrnty/adwright.js
|
- /plugins/svrnty/adwright.js
|
||||||
- /plugins/svrnty/bte.js
|
- /plugins/svrnty/bte.js
|
||||||
|
- /plugins/svrnty/umbrella_inline.js
|
||||||
stylesheets:
|
stylesheets:
|
||||||
- /plugins/svrnty/app.css
|
- /plugins/svrnty/app.css
|
||||||
- /plugins/svrnty/adwright.css
|
- /plugins/svrnty/adwright.css
|
||||||
- /plugins/svrnty/bte.css
|
- /plugins/svrnty/bte.css
|
||||||
|
- /plugins/svrnty/umbrella_inline.css
|
||||||
|
|
||||||
# Routes this plugin registers at load time (declarative cross-check vs runtime).
|
# Routes this plugin registers at load time (declarative cross-check vs runtime).
|
||||||
# Each row maps to a routes/<file>.py.
|
# Each row maps to a routes/<file>.py.
|
||||||
|
|||||||
@ -44,6 +44,9 @@ def register(api):
|
|||||||
# BTE Command Center panel — same pattern (main.svrnty-showing-bte).
|
# BTE Command Center panel — same pattern (main.svrnty-showing-bte).
|
||||||
api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/bte.css")
|
api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/bte.css")
|
||||||
api.inject_script(f"/plugins/{STATIC_PREFIX}/bte.js")
|
api.inject_script(f"/plugins/{STATIC_PREFIX}/bte.js")
|
||||||
|
# Inline Umbrella graph for the Hermes Workspace right panel.
|
||||||
|
api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/umbrella_inline.css")
|
||||||
|
api.inject_script(f"/plugins/{STATIC_PREFIX}/umbrella_inline.js")
|
||||||
log.info("static + assets wired at /plugins/%s/", STATIC_PREFIX)
|
log.info("static + assets wired at /plugins/%s/", STATIC_PREFIX)
|
||||||
|
|
||||||
# Routes — each feature lives in its own module under routes/.
|
# Routes — each feature lives in its own module under routes/.
|
||||||
|
|||||||
@ -22,19 +22,10 @@
|
|||||||
'<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
|
'<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
|
||||||
bte:
|
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"/>',
|
'<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"/>',
|
||||||
graph:
|
|
||||||
'<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"/>',
|
|
||||||
};
|
};
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ id: "adwright", label: "Adwright", tooltip: "Adwright — marketing intelligence" },
|
{ id: "adwright", label: "Adwright", tooltip: "Adwright — marketing intelligence" },
|
||||||
{ id: "bte", label: "BTE", tooltip: "BTE — brand creative studio" },
|
{ id: "bte", label: "BTE", tooltip: "BTE — brand creative studio" },
|
||||||
{
|
|
||||||
id: "project-graph",
|
|
||||||
label: "Project Graph",
|
|
||||||
tooltip: "Project Graph — open workspace graph",
|
|
||||||
href: "/plugins/svrnty/umbrella.html",
|
|
||||||
icon: "graph",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function _svg(iconPath, size, stroke) {
|
function _svg(iconPath, size, stroke) {
|
||||||
@ -84,10 +75,6 @@
|
|||||||
btn.innerHTML = _svg(ICONS[t.icon || t.id], c.size, c.stroke);
|
btn.innerHTML = _svg(ICONS[t.icon || t.id], c.size, c.stroke);
|
||||||
btn.addEventListener("click", () => {
|
btn.addEventListener("click", () => {
|
||||||
LOG("clicked:", t.id);
|
LOG("clicked:", t.id);
|
||||||
if (t.href) {
|
|
||||||
window.open(t.href, "_blank", "noopener,noreferrer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof window.switchPanel === "function") {
|
if (typeof window.switchPanel === "function") {
|
||||||
window.switchPanel(t.id, { fromRailClick: true });
|
window.switchPanel(t.id, { fromRailClick: true });
|
||||||
} else {
|
} else {
|
||||||
@ -116,7 +103,7 @@
|
|||||||
if (typeof window.switchPanel !== "function") return false;
|
if (typeof window.switchPanel !== "function") return false;
|
||||||
if (window.switchPanel.__svrntyWrapped) return true;
|
if (window.switchPanel.__svrntyWrapped) return true;
|
||||||
const original = window.switchPanel;
|
const original = window.switchPanel;
|
||||||
const OUR_IDS = TABS.filter((t) => !t.href).map((t) => t.id);
|
const OUR_IDS = TABS.map((t) => t.id);
|
||||||
|
|
||||||
async function wrapped(name, opts) {
|
async function wrapped(name, opts) {
|
||||||
const result = await original(name, opts);
|
const result = await original(name, opts);
|
||||||
|
|||||||
@ -159,3 +159,56 @@
|
|||||||
display: flex; justify-content: space-between; align-items: center;
|
display: flex; justify-content: space-between; align-items: center;
|
||||||
}
|
}
|
||||||
.umbrella-footer a { color: var(--accent); }
|
.umbrella-footer a { color: var(--accent); }
|
||||||
|
|
||||||
|
body.umbrella-inline {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-root {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-header {
|
||||||
|
padding: 8px;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-header h1 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-stats {
|
||||||
|
font-size: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-controls {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-controls input[type=search] {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 5px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-controls button,
|
||||||
|
body.umbrella-inline .chip {
|
||||||
|
padding: 4px 7px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-filters,
|
||||||
|
body.umbrella-inline .umbrella-disclosure {
|
||||||
|
max-height: 58px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-footer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .umbrella-side {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body.umbrella-inline .side-body {
|
||||||
|
max-height: 38vh;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,11 @@
|
|||||||
<link rel="stylesheet" href="/plugins/svrnty/umbrella.css" />
|
<link rel="stylesheet" href="/plugins/svrnty/umbrella.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<script>
|
||||||
|
if (new URLSearchParams(location.search).get("inline") === "1") {
|
||||||
|
document.body.classList.add("umbrella-inline");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<div id="svrnty-umbrella" class="umbrella-root">
|
<div id="svrnty-umbrella" class="umbrella-root">
|
||||||
<header class="umbrella-header">
|
<header class="umbrella-header">
|
||||||
<h1>Cortex-OS Umbrella</h1>
|
<h1>Cortex-OS Umbrella</h1>
|
||||||
|
|||||||
28
static/umbrella_inline.css
Normal file
28
static/umbrella_inline.css
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/* Inline Umbrella graph mounted into Hermes WebUI's Workspace right panel. */
|
||||||
|
|
||||||
|
.svrnty-graph-btn.active {
|
||||||
|
background: var(--accent-bg);
|
||||||
|
color: var(--accent-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.svrnty-umbrella-inline-surface {
|
||||||
|
display: none;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
background: var(--bg, #0f1115);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svrnty-umbrella-inline-surface[data-open="true"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svrnty-umbrella-inline-frame {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
border: 0;
|
||||||
|
background: #0f1115;
|
||||||
|
}
|
||||||
210
static/umbrella_inline.js
Normal file
210
static/umbrella_inline.js
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
// 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();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@ -66,6 +66,8 @@ def test_loader_register_wires_our_plugin(loader, monkeypatch):
|
|||||||
assert "svrnty" in loader._STATIC
|
assert "svrnty" in loader._STATIC
|
||||||
assert "/plugins/svrnty/app.css" in loader._STYLESHEETS
|
assert "/plugins/svrnty/app.css" in loader._STYLESHEETS
|
||||||
assert "/plugins/svrnty/app.js" in loader._SCRIPTS
|
assert "/plugins/svrnty/app.js" in loader._SCRIPTS
|
||||||
|
assert "/plugins/svrnty/umbrella_inline.css" in loader._STYLESHEETS
|
||||||
|
assert "/plugins/svrnty/umbrella_inline.js" in loader._SCRIPTS
|
||||||
# Audio processor for voice-message transcription
|
# Audio processor for voice-message transcription
|
||||||
assert len(loader._AUDIO_PROCESSORS) == 1
|
assert len(loader._AUDIO_PROCESSORS) == 1
|
||||||
|
|
||||||
|
|||||||
@ -5,13 +5,14 @@ from pathlib import Path
|
|||||||
NAV_JS = Path(__file__).resolve().parents[2] / "static" / "svrnty_nav.js"
|
NAV_JS = Path(__file__).resolve().parents[2] / "static" / "svrnty_nav.js"
|
||||||
|
|
||||||
|
|
||||||
def test_project_graph_nav_opens_umbrella_page_in_new_tab():
|
def test_project_graph_is_not_in_left_nav():
|
||||||
src = NAV_JS.read_text()
|
src = NAV_JS.read_text()
|
||||||
assert "Project Graph" in src
|
assert "Project Graph" not in src
|
||||||
assert "/plugins/svrnty/umbrella.html" in src
|
assert "/plugins/svrnty/umbrella.html" not in src
|
||||||
assert 'window.open(t.href, "_blank", "noopener,noreferrer")' in src
|
assert "window.open" not in src
|
||||||
|
|
||||||
|
|
||||||
def test_project_graph_does_not_participate_in_panel_switching():
|
def test_svrnty_tabs_participate_in_panel_switching():
|
||||||
src = NAV_JS.read_text()
|
src = NAV_JS.read_text()
|
||||||
assert "TABS.filter((t) => !t.href).map((t) => t.id)" in src
|
assert "const TABS = [" in src
|
||||||
|
assert "const OUR_IDS = TABS.map((t) => t.id)" in src
|
||||||
|
|||||||
54
tests/unit/test_umbrella_inline_static.py
Normal file
54
tests/unit/test_umbrella_inline_static.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""Static checks for the inline Workspace-panel umbrella graph integration."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
INLINE_JS = ROOT / "static" / "umbrella_inline.js"
|
||||||
|
INLINE_CSS = ROOT / "static" / "umbrella_inline.css"
|
||||||
|
UMBRELLA_HTML = ROOT / "static" / "umbrella.html"
|
||||||
|
UMBRELLA_CSS = ROOT / "static" / "umbrella.css"
|
||||||
|
PLUGIN = ROOT / "plugin.py"
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_injects_inline_umbrella_assets():
|
||||||
|
src = PLUGIN.read_text()
|
||||||
|
assert "/plugins/{STATIC_PREFIX}/umbrella_inline.css" in src
|
||||||
|
assert "/plugins/{STATIC_PREFIX}/umbrella_inline.js" in src
|
||||||
|
|
||||||
|
|
||||||
|
def test_inline_graph_targets_workspace_right_panel():
|
||||||
|
src = INLINE_JS.read_text()
|
||||||
|
assert "btnSvrntyWorkspaceGraph" in src
|
||||||
|
assert '.rightpanel .panel-actions' in src
|
||||||
|
assert "Project graph" in src
|
||||||
|
assert "svrntyUmbrellaInlineSurface" in src
|
||||||
|
assert "/plugins/svrnty/umbrella.html?inline=1" in src
|
||||||
|
assert "window.open(" not in src
|
||||||
|
|
||||||
|
|
||||||
|
def test_inline_graph_uses_right_panel_mode_switching():
|
||||||
|
src = INLINE_JS.read_text()
|
||||||
|
assert "openWorkspaceGraph" in src
|
||||||
|
assert "closeWorkspaceGraph" in src
|
||||||
|
assert "openWorkspacePanel" in src
|
||||||
|
assert "expandPanelForGraph" in src
|
||||||
|
assert "restorePanelWidth" in src
|
||||||
|
assert 'panel.style.width = "500px"' in src
|
||||||
|
assert "btnClearPreview" in src
|
||||||
|
assert "stopImmediatePropagation" in src
|
||||||
|
|
||||||
|
|
||||||
|
def test_inline_graph_has_panel_surface_styles():
|
||||||
|
src = INLINE_CSS.read_text()
|
||||||
|
assert ".svrnty-umbrella-inline-surface" in src
|
||||||
|
assert '.svrnty-umbrella-inline-surface[data-open="true"]' in src
|
||||||
|
assert ".svrnty-umbrella-inline-frame" in src
|
||||||
|
|
||||||
|
|
||||||
|
def test_standalone_umbrella_supports_inline_mode():
|
||||||
|
html = UMBRELLA_HTML.read_text()
|
||||||
|
css = UMBRELLA_CSS.read_text()
|
||||||
|
assert 'get("inline") === "1"' in html
|
||||||
|
assert "umbrella-inline" in html
|
||||||
|
assert "body.umbrella-inline .umbrella-header" in css
|
||||||
|
assert "body.umbrella-inline .umbrella-footer" in css
|
||||||
Loading…
Reference in New Issue
Block a user