Files
m0rtus59-ComfyUI-ComfySidebar/js/utils/overlay.js
T
2026-09-26 00:09:42 +05:00

185 lines
7.5 KiB
JavaScript

import { store } from "../core/store.js";
import { findActiveSidebars, findPropertiesPanel } from "../comfy/adapter.js";
export class SidebarOverlay {
constructor(options = {}) {
this.onDestroy = options.onDestroy || (() => {});
this.onUserClose = options.onUserClose || (() => {});
this.cleanupFns = [];
this.observedElements = new Set();
// Elevate sidebars above overlay
document.body.classList.add("comfy-sidebar-overlay-active");
// Root container: visual backdrop (covers 100% flush from sidebar to sidebar, zero gap)
this.container = document.createElement("div");
this.container.className = `comfy-sidebar-comparison-overlay ${options.className || ""}`;
Object.assign(this.container.style, {
position: "fixed", top: "0", left: "0", width: "100vw", height: "100vh",
background: options.background || "color-mix(in srgb, var(--comfy-menu-bg, #121212) 95%, transparent)",
backdropFilter: "blur(8px)",
color: "var(--fg-color, #eee)",
display: "flex", flexDirection: "column", alignItems: "center",
justifyContent: "center", zIndex: "1000", boxSizing: "border-box",
overflow: "hidden", pointerEvents: "none", userSelect: "none"
});
// Interactive backdrop: insets 6px from each sidebar so the border zones pass clicks to the resizers
this.backdrop = document.createElement("div");
Object.assign(this.backdrop.style, {
position: "absolute", top: "0", bottom: "0", left: "6px", right: "6px",
pointerEvents: "auto", zIndex: "1"
});
this.backdrop.addEventListener("click", (e) => {
if (e.target === this.backdrop) {
this.onUserClose();
this.destroy();
}
});
this.container.appendChild(this.backdrop);
// Close button
this.closeBtn = document.createElement("span");
this.closeBtn.className = "pi pi-times";
this.closeBtn.title = "Close (Esc)";
Object.assign(this.closeBtn.style, {
position: "absolute", top: "16px", right: "24px", zIndex: "30",
cursor: "pointer", fontSize: "18px",
color: "var(--desc-color, #aaa)",
transition: "all 0.15s ease",
background: "var(--comfy-input-bg, rgba(20, 20, 20, 0.6))",
border: "1px solid var(--border-color, rgba(255, 255, 255, 0.1))",
borderRadius: "50%", padding: "6px", pointerEvents: "auto"
});
this.closeBtn.onmouseenter = () => {
this.closeBtn.style.color = "var(--fg-color, #fff)";
this.closeBtn.style.borderColor = "var(--fg-color, #fff)";
};
this.closeBtn.onmouseleave = () => {
this.closeBtn.style.color = "var(--desc-color, #aaa)";
this.closeBtn.style.borderColor = "var(--border-color, rgba(255, 255, 255, 0.1))";
};
this.closeBtn.onclick = () => {
this.onUserClose();
this.destroy();
};
this.container.appendChild(this.closeBtn);
// ResizeObserver to track live drag-resizing on all active sidebars
if (window.ResizeObserver) {
this.ro = new ResizeObserver(() => {
this.updateOverlayBounds();
});
this.cleanupFns.push(() => this.ro.disconnect());
}
// Sidebar boundary alignment
this.updateOverlayBounds = this.updateOverlayBounds.bind(this);
this.updateOverlayBounds();
window.addEventListener("resize", this.updateOverlayBounds);
this.cleanupFns.push(() => window.removeEventListener("resize", this.updateOverlayBounds));
// Live observer for opening/closing panels or layout changes
if (window.MutationObserver) {
let updateScheduled = false;
const mo = new MutationObserver(() => {
if (!updateScheduled) {
updateScheduled = true;
requestAnimationFrame(() => {
this.updateOverlayBounds();
updateScheduled = false;
});
}
});
mo.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["style", "class"] });
this.cleanupFns.push(() => mo.disconnect());
}
// Global Key Handler (Esc to close)
const onKeyDown = (e) => {
if (e.key === "Escape") {
e.preventDefault();
this.onUserClose();
this.destroy();
}
if (options.onKeyDown) options.onKeyDown(e);
};
document.addEventListener("keydown", onKeyDown);
this.cleanupFns.push(() => document.removeEventListener("keydown", onKeyDown));
document.body.appendChild(this.container);
}
observeElement(el) {
if (!el || !this.ro || this.observedElements.has(el)) return;
this.observedElements.add(el);
this.ro.observe(el);
}
updateOverlayBounds() {
let leftOffset = 0;
let rightOffset = 0;
let topOffset = 0;
const winW = window.innerWidth;
const midX = winW / 2;
try {
const { leftPanels, rightPanels } = findActiveSidebars();
[...leftPanels, ...rightPanels].forEach(p => this.observeElement(p));
for (const el of leftPanels) {
const rect = el.getBoundingClientRect();
if (rect.right > 0 && rect.right < midX) {
leftOffset = Math.max(leftOffset, Math.round(rect.right));
}
if (rect.top > 0 && rect.top < 120) {
topOffset = Math.max(topOffset, Math.round(rect.top));
}
}
for (const el of rightPanels) {
const rect = el.getBoundingClientRect();
if (rect.left > midX && rect.left < winW) {
rightOffset = Math.max(rightOffset, Math.round(winW - rect.left));
}
if (rect.top > 0 && rect.top < 120) {
topOffset = Math.max(topOffset, Math.round(rect.top));
}
}
} catch (e) {
console.error("Comfy Sidebar: Error finding active sidebars:", e);
}
// Safety clamp: offsets can never exceed half screen width
leftOffset = Math.min(midX - 50, Math.max(0, leftOffset));
rightOffset = Math.min(midX - 50, Math.max(0, rightOffset));
topOffset = Math.max(0, topOffset);
this.container.style.left = `${leftOffset}px`;
this.container.style.right = `${rightOffset}px`;
this.container.style.width = `calc(100vw - ${leftOffset + rightOffset}px)`;
this.container.style.top = `${topOffset}px`;
this.container.style.height = `calc(100vh - ${topOffset}px)`;
// Crisp 1px visible separator lines along active sidebar borders
this.container.style.borderLeft = leftOffset > 0 ? "1px solid var(--border-color, rgba(255, 255, 255, 0.22))" : "none";
this.container.style.borderRight = rightOffset > 0 ? "1px solid var(--border-color, rgba(255, 255, 255, 0.22))" : "none";
}
addCleanup(fn) {
this.cleanupFns.push(fn);
}
destroy() {
document.body.classList.remove("comfy-sidebar-overlay-active");
for (const fn of this.cleanupFns.splice(0)) {
try { fn(); } catch (e) {}
}
this.container.remove();
this.onDestroy();
}
}