Brain-5D-Space / src /dashboard /static /box-state-controller.js
github-actions[bot]
Sync: publish Space API fix
5e0b58b
Raw History Blame Contribute Delete
5.23 kB
/* Responsive box states for the dashboard presentation layer. */
"use strict";
const STORAGE_KEY = "brain5d-box-states";
const BOX_SELECTOR = [
".card",
".panel",
".control-card",
".overview-surface",
".snapshot-info-card",
".workspace-tile",
".wesen-card",
".wesen-stage-card",
".wesen-console",
".wesen-symbiosis-card",
".experiment-workflow",
".release-board",
".connection-manager",
".embodiment-system-strip",
".embodiment-loop",
].join(",");
const EXCLUDED_SELECTOR = [
".site-footer",
".workspace-header",
".overview-command-bar",
".dashboard-generated-header",
".dashboard-utility-bar",
".experience-ribbon",
".experience-welcome",
".research-chat-modal",
"dialog",
].join(",");
const HEADER_SELECTOR = [
":scope > .panel-title",
":scope > .overview-surface-title",
":scope > .control-card__header",
":scope > .console-header",
":scope > .experiment-workflow-header",
":scope > .gate-header",
":scope > .release-header",
":scope > header",
].join(",");
let states = {};
let scanQueued = false;
function loadStates() {
try {
states = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
} catch (_) {
states = {};
}
}
function saveStates() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(states));
} catch (_) {
// Preferences are optional.
}
}
function boxKey(box, index) {
if (box.id) return box.id;
const workspace = box.closest(".tab-content")?.id || "dashboard";
return `${workspace}:${box.className}:${index}`;
}
function isNestedBox(box) {
return Boolean(box.parentElement?.closest(BOX_SELECTOR));
}
function findHeader(box) {
return box.querySelector(HEADER_SELECTOR);
}
function ensureHeader(box) {
const existing = findHeader(box);
if (existing) return existing;
const heading = box.querySelector(":scope > h2, :scope > h3, :scope > h4");
const label = box.getAttribute("aria-label") || heading?.textContent?.trim() || "Panel";
const header = document.createElement("div");
header.className = "box-state-generated-header";
const title = document.createElement("strong");
title.textContent = label;
header.appendChild(title);
box.prepend(header);
return header;
}
function markContent(box, header) {
[...box.children].forEach((child) => {
if (child === header || child.classList.contains("box-state-tools")) return;
child.classList.add("box-state-content");
});
}
function makeButton(label, icon, state) {
const button = document.createElement("button");
button.type = "button";
button.className = "box-state-button";
button.dataset.boxState = state;
button.setAttribute("aria-label", label);
button.title = label;
button.textContent = icon;
return button;
}
function setState(box, key, state, persist = true) {
const next = state === "minimized" || state === "maximized" ? state : "standard";
box.dataset.boxState = next;
box.classList.toggle("box-state-minimized", next === "minimized");
box.classList.toggle("box-state-maximized", next === "maximized");
if (next === "maximized") document.body.classList.add("box-state-lock");
if (persist && next !== "maximized") {
states[key] = next;
saveStates();
}
box.querySelectorAll(".box-state-button").forEach((button) => {
button.setAttribute("aria-pressed", String(button.dataset.boxState === next));
});
}
function enhanceBox(box, index) {
if (box.classList.contains("box-state-ready") || box.matches(EXCLUDED_SELECTOR) || isNestedBox(box)) return;
const header = ensureHeader(box);
const key = boxKey(box, index);
const tools = document.createElement("div");
tools.className = "box-state-tools";
tools.setAttribute("aria-label", "Boxgröße");
tools.append(
makeButton("Minimieren", "−", "minimized"),
makeButton("Standardgröße", "□", "standard"),
makeButton("Maximieren", "⛶", "maximized"),
);
tools.addEventListener("click", (event) => {
const button = event.target.closest(".box-state-button");
if (!button) return;
event.preventDefault();
event.stopPropagation();
setState(box, key, button.dataset.boxState);
});
header.classList.add("box-state-header");
header.appendChild(tools);
markContent(box, header);
box.dataset.boxKey = key;
box.classList.add("box-state-ready");
setState(box, key, states[key] || "standard", false);
}
function scan() {
scanQueued = false;
let index = 0;
document.querySelectorAll(`.tab-content ${BOX_SELECTOR}`).forEach((box) => enhanceBox(box, index++));
}
function queueScan() {
if (scanQueued) return;
scanQueued = true;
requestAnimationFrame(scan);
}
function restoreStandard() {
const expanded = document.querySelector(".box-state-maximized");
if (!expanded) return;
const key = expanded.dataset.boxKey;
setState(expanded, key, "standard");
document.body.classList.remove("box-state-lock");
}
export function initBoxStates() {
loadStates();
scan();
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") restoreStandard();
});
const observer = new MutationObserver(queueScan);
observer.observe(document.querySelector("main") || document.body, { childList: true, subtree: true });
}