feat: add workflow overview toggle to canvas editor modal

This commit is contained in:
Dariusz L
2026-08-14 21:58:11 +02:00
parent 0e636ed99b
commit cfe2a92f12
4 changed files with 611 additions and 32 deletions
+241 -3
View File
@@ -1716,9 +1716,212 @@ async function createCanvasWidget(node, widget, _app) {
}
const openEditorBtn = controlPanel.querySelector(`#open-editor-btn-${node.id}`);
let backdrop = null;
let modalContent = null;
let workflowOverviewToggleButton = null;
let workflowOverviewResizeObserver = null;
let workflowOverviewWindowResizeHandler = null;
let workflowOverviewLayoutFrame = null;
let workflowOverviewToggleRequest = 0;
let lastModalBounds = { left: Number.NaN, right: Number.NaN };
let originalParent = null;
let isEditorOpen = false;
let viewportAdjustment = { x: 0, y: 0 };
const workflowOverviewPanelSelector = '[data-testid="properties-panel"]';
const nativeWorkflowOverviewToggleSelector = 'button[aria-label="Toggle properties panel"]:not(.lf-workflow-overview-toggle)';
const isVisibleElement = (element) => {
if (!(element instanceof HTMLElement)) {
return false;
}
const styles = window.getComputedStyle(element);
const rect = element.getBoundingClientRect();
return styles.display !== "none" &&
styles.visibility !== "hidden" &&
rect.width > 0 &&
rect.height > 0;
};
const getWorkflowOverviewPanel = () => {
const panel = document.querySelector(workflowOverviewPanelSelector);
return isVisibleElement(panel) ? panel : null;
};
const findNativeWorkflowOverviewToggle = () => {
const labeledButton = document.querySelector(nativeWorkflowOverviewToggleSelector);
if (isVisibleElement(labeledButton)) {
return labeledButton;
}
const panelCloseButton = document.querySelector(workflowOverviewPanelSelector + ' button[aria-pressed="true"]');
if (isVisibleElement(panelCloseButton)) {
return panelCloseButton;
}
return Array.from(document.querySelectorAll("button:not(.lf-workflow-overview-toggle)")).find((button) => {
if (!isVisibleElement(button)) {
return false;
}
return Array.from(button.querySelectorAll("i")).some((icon) => icon.className.includes("icon-[lucide--panel-"));
}) ?? null;
};
const isWorkflowOverviewOpen = () => {
return getWorkflowOverviewPanel() !== null;
};
const getWorkflowOverviewLayout = () => {
const panel = getWorkflowOverviewPanel();
if (!panel) {
return null;
}
const splitterPanel = panel.closest('[data-pc-name="splitterpanel"]');
const panelElement = splitterPanel ?? panel;
const panelRect = panelElement.getBoundingClientRect();
const viewportWidth = window.innerWidth;
let panelIsOnRight = panelRect.left >= viewportWidth / 2;
let gutter = null;
if (splitterPanel) {
const previousSibling = splitterPanel.previousElementSibling;
const nextSibling = splitterPanel.nextElementSibling;
const previousGutter = previousSibling instanceof HTMLElement &&
previousSibling.classList.contains("p-splitter-gutter")
? previousSibling
: null;
const nextGutter = nextSibling instanceof HTMLElement &&
nextSibling.classList.contains("p-splitter-gutter")
? nextSibling
: null;
if (previousGutter && !nextGutter) {
panelIsOnRight = true;
gutter = previousGutter;
}
else if (nextGutter && !previousGutter) {
panelIsOnRight = false;
gutter = nextGutter;
}
else if (splitterPanel.parentElement?.firstElementChild === splitterPanel) {
panelIsOnRight = false;
}
else if (splitterPanel.parentElement?.lastElementChild === splitterPanel) {
panelIsOnRight = true;
}
}
const gutterRect = gutter?.getBoundingClientRect();
if (panelIsOnRight) {
const boundary = gutterRect?.left ?? Math.max(0, panelRect.left - 8);
return {
left: 0,
right: Math.max(0, viewportWidth - boundary),
observedElements: gutter ? [panelElement, gutter] : [panelElement],
};
}
const boundary = gutterRect?.right ?? Math.min(viewportWidth, panelRect.right + 8);
return {
left: Math.min(viewportWidth, boundary),
right: 0,
observedElements: gutter ? [panelElement, gutter] : [panelElement],
};
};
const updateWorkflowOverviewButton = () => {
if (!workflowOverviewToggleButton) {
return;
}
const isOpen = isWorkflowOverviewOpen();
workflowOverviewToggleButton.setAttribute("aria-pressed", String(isOpen));
workflowOverviewToggleButton.title = isOpen
? "Close Workflow Overview"
: "Open Workflow Overview";
workflowOverviewToggleButton.classList.toggle("lf-workflow-overview-open", isOpen);
};
const applyWorkflowOverviewLayout = () => {
updateWorkflowOverviewButton();
if (!modalContent) {
return;
}
const layout = getWorkflowOverviewLayout();
const left = layout?.left ?? 0;
const right = layout?.right ?? 0;
modalContent.style.left = left + "px";
modalContent.style.right = right + "px";
if (lastModalBounds.left !== left || lastModalBounds.right !== right) {
lastModalBounds = { left, right };
canvas.render();
if (node.onResize) {
node.onResize();
}
}
};
const scheduleWorkflowOverviewLayoutUpdate = () => {
if (workflowOverviewLayoutFrame !== null) {
return;
}
workflowOverviewLayoutFrame = window.requestAnimationFrame(() => {
workflowOverviewLayoutFrame = null;
applyWorkflowOverviewLayout();
});
};
const attachWorkflowOverviewResizeObserver = () => {
workflowOverviewResizeObserver?.disconnect();
workflowOverviewResizeObserver = null;
if (typeof ResizeObserver === "undefined") {
scheduleWorkflowOverviewLayoutUpdate();
return;
}
const layout = getWorkflowOverviewLayout();
if (!layout) {
scheduleWorkflowOverviewLayoutUpdate();
return;
}
const observer = new ResizeObserver(scheduleWorkflowOverviewLayoutUpdate);
layout.observedElements.forEach((element) => observer.observe(element));
workflowOverviewResizeObserver = observer;
scheduleWorkflowOverviewLayoutUpdate();
};
const startWorkflowOverviewLayoutTracking = () => {
if (!workflowOverviewWindowResizeHandler) {
workflowOverviewWindowResizeHandler = scheduleWorkflowOverviewLayoutUpdate;
window.addEventListener("resize", workflowOverviewWindowResizeHandler);
}
attachWorkflowOverviewResizeObserver();
scheduleWorkflowOverviewLayoutUpdate();
};
const stopWorkflowOverviewLayoutTracking = () => {
workflowOverviewResizeObserver?.disconnect();
workflowOverviewResizeObserver = null;
if (workflowOverviewWindowResizeHandler) {
window.removeEventListener("resize", workflowOverviewWindowResizeHandler);
workflowOverviewWindowResizeHandler = null;
}
if (workflowOverviewLayoutFrame !== null) {
window.cancelAnimationFrame(workflowOverviewLayoutFrame);
workflowOverviewLayoutFrame = null;
}
lastModalBounds = { left: Number.NaN, right: Number.NaN };
};
const waitForWorkflowOverviewState = (expectedState) => {
return new Promise((resolve) => {
const deadline = performance.now() + 1000;
const checkState = () => {
if (!isEditorOpen ||
isWorkflowOverviewOpen() === expectedState ||
performance.now() >= deadline) {
resolve();
return;
}
window.requestAnimationFrame(checkState);
};
checkState();
});
};
const toggleWorkflowOverview = async () => {
const requestId = ++workflowOverviewToggleRequest;
const nativeToggle = findNativeWorkflowOverviewToggle();
if (!nativeToggle) {
log.warn("Could not find ComfyUI's Workflow Overview toggle.");
return;
}
const expectedState = !isWorkflowOverviewOpen();
nativeToggle.click();
await waitForWorkflowOverviewState(expectedState);
if (!isEditorOpen || requestId !== workflowOverviewToggleRequest) {
return;
}
attachWorkflowOverviewResizeObserver();
applyWorkflowOverviewLayout();
};
/**
* Adjusts the viewport when entering fullscreen mode.
*/
@@ -1745,11 +1948,19 @@ async function createCanvasWidget(node, widget, _app) {
viewportAdjustment = { x: 0, y: 0 };
};
const closeEditor = () => {
if (originalParent && backdrop) {
if (!isEditorOpen) {
return;
}
stopWorkflowOverviewLayoutTracking();
if (originalParent) {
originalParent.appendChild(mainContainer);
document.body.removeChild(backdrop);
}
if (backdrop?.parentNode) {
backdrop.parentNode.removeChild(backdrop);
}
isEditorOpen = false;
modalContent = null;
workflowOverviewToggleButton = null;
openEditorBtn.textContent = "⛶";
tooltipManager.setTooltip(openEditorBtn, "Open in Editor");
// Remove ESC key listener when editor closes
@@ -1782,11 +1993,28 @@ async function createCanvasWidget(node, widget, _app) {
return;
}
backdrop = $el("div.lf-painter-modal-backdrop");
const modalContent = $el("div.lf-painter-modal-content");
modalContent = $el("div.lf-painter-modal-content");
workflowOverviewToggleButton = document.createElement("button");
workflowOverviewToggleButton.type = "button";
workflowOverviewToggleButton.className =
"lf-painter-button lf-icon-button lf-workflow-overview-toggle";
workflowOverviewToggleButton.setAttribute("aria-label", "Toggle Workflow Overview");
workflowOverviewToggleButton.setAttribute("aria-pressed", "false");
workflowOverviewToggleButton.title = "Open Workflow Overview";
const workflowOverviewIcon = document.createElement("i");
workflowOverviewIcon.className = "icon-[lucide--panel-right] size-4";
workflowOverviewIcon.setAttribute("aria-hidden", "true");
workflowOverviewToggleButton.appendChild(workflowOverviewIcon);
workflowOverviewToggleButton.onclick = () => {
void toggleWorkflowOverview();
};
modalContent.appendChild(mainContainer);
modalContent.appendChild(workflowOverviewToggleButton);
backdrop.appendChild(modalContent);
document.body.appendChild(backdrop);
isEditorOpen = true;
applyWorkflowOverviewLayout();
startWorkflowOverviewLayoutTracking();
openEditorBtn.textContent = "X";
tooltipManager.setTooltip(openEditorBtn, "Close Editor (ESC)");
// Add ESC key listener when editor opens
@@ -1837,6 +2065,16 @@ async function createCanvasWidget(node, widget, _app) {
panel: controlPanel,
destroy: () => {
widgetDestroyed = true;
stopWorkflowOverviewLayoutTracking();
if (isEditorOpen) {
document.removeEventListener('keydown', handleEscKey);
if (backdrop?.parentNode) {
backdrop.parentNode.removeChild(backdrop);
}
isEditorOpen = false;
modalContent = null;
workflowOverviewToggleButton = null;
}
unregisterTooltips();
mattingAbortController?.abort();
closeInputMenu();
+36 -13
View File
@@ -1066,27 +1066,50 @@
.lf-painter-modal-backdrop {
position: fixed;
top: 0;
left: 0;
inset: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
background-color: transparent;
z-index: 999999;
pointer-events: none;
}
.lf-painter-modal-content {
position: absolute;
inset: 0;
width: auto;
height: auto;
background-color: #353535;
border: none;
border-radius: 0;
box-shadow: none;
display: flex;
flex-direction: column;
overflow: hidden;
pointer-events: auto;
box-sizing: border-box;
}
.lf-workflow-overview-toggle {
position: absolute;
top: 12px;
right: 12px;
z-index: 30;
width: 32px;
min-width: 32px;
height: 32px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
}
.lf-painter-modal-content {
width: 90vw;
height: 90vh;
background-color: #353535;
border: 1px solid #222;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
display: flex;
flex-direction: column;
position: relative;
.lf-workflow-overview-toggle i[class~="icon-[lucide--panel-right]"] {
font-size: 16px;
}
.lf-workflow-overview-toggle.lf-workflow-overview-open {
background: var(--comfy-menu-bg, #2f2f2f);
}
.lf-painter-main-container {
+298 -3
View File
@@ -1992,10 +1992,266 @@ $el("label.lf-clipboard-switch.lf-mask-switch", {
const openEditorBtn = controlPanel.querySelector(`#open-editor-btn-${node.id}`) as HTMLButtonElement;
let backdrop: HTMLDivElement | null = null;
let modalContent: HTMLDivElement | null = null;
let workflowOverviewToggleButton: HTMLButtonElement | null = null;
let workflowOverviewResizeObserver: ResizeObserver | null = null;
let workflowOverviewWindowResizeHandler: (() => void) | null = null;
let workflowOverviewLayoutFrame: number | null = null;
let workflowOverviewToggleRequest = 0;
let lastModalBounds = { left: Number.NaN, right: Number.NaN };
let originalParent: HTMLElement | null = null;
let isEditorOpen = false;
let viewportAdjustment = { x: 0, y: 0 };
const workflowOverviewPanelSelector = '[data-testid="properties-panel"]';
const nativeWorkflowOverviewToggleSelector =
'button[aria-label="Toggle properties panel"]:not(.lf-workflow-overview-toggle)';
const isVisibleElement = (element: Element | null): element is HTMLElement => {
if (!(element instanceof HTMLElement)) {
return false;
}
const styles = window.getComputedStyle(element);
const rect = element.getBoundingClientRect();
return styles.display !== "none" &&
styles.visibility !== "hidden" &&
rect.width > 0 &&
rect.height > 0;
};
const getWorkflowOverviewPanel = (): HTMLElement | null => {
const panel = document.querySelector<HTMLElement>(workflowOverviewPanelSelector);
return isVisibleElement(panel) ? panel : null;
};
const findNativeWorkflowOverviewToggle = (): HTMLButtonElement | null => {
const labeledButton = document.querySelector<HTMLButtonElement>(
nativeWorkflowOverviewToggleSelector
);
if (isVisibleElement(labeledButton)) {
return labeledButton;
}
const panelCloseButton = document.querySelector<HTMLButtonElement>(
workflowOverviewPanelSelector + ' button[aria-pressed="true"]'
);
if (isVisibleElement(panelCloseButton)) {
return panelCloseButton;
}
return Array.from(
document.querySelectorAll<HTMLButtonElement>("button:not(.lf-workflow-overview-toggle)")
).find((button) => {
if (!isVisibleElement(button)) {
return false;
}
return Array.from(button.querySelectorAll("i")).some((icon) =>
icon.className.includes("icon-[lucide--panel-")
);
}) ?? null;
};
const isWorkflowOverviewOpen = (): boolean => {
return getWorkflowOverviewPanel() !== null;
};
interface WorkflowOverviewLayout {
left: number;
right: number;
observedElements: HTMLElement[];
}
const getWorkflowOverviewLayout = (): WorkflowOverviewLayout | null => {
const panel = getWorkflowOverviewPanel();
if (!panel) {
return null;
}
const splitterPanel = panel.closest('[data-pc-name="splitterpanel"]') as HTMLElement | null;
const panelElement = splitterPanel ?? panel;
const panelRect = panelElement.getBoundingClientRect();
const viewportWidth = window.innerWidth;
let panelIsOnRight = panelRect.left >= viewportWidth / 2;
let gutter: HTMLElement | null = null;
if (splitterPanel) {
const previousSibling = splitterPanel.previousElementSibling;
const nextSibling = splitterPanel.nextElementSibling;
const previousGutter = previousSibling instanceof HTMLElement &&
previousSibling.classList.contains("p-splitter-gutter")
? previousSibling
: null;
const nextGutter = nextSibling instanceof HTMLElement &&
nextSibling.classList.contains("p-splitter-gutter")
? nextSibling
: null;
if (previousGutter && !nextGutter) {
panelIsOnRight = true;
gutter = previousGutter;
} else if (nextGutter && !previousGutter) {
panelIsOnRight = false;
gutter = nextGutter;
} else if (splitterPanel.parentElement?.firstElementChild === splitterPanel) {
panelIsOnRight = false;
} else if (splitterPanel.parentElement?.lastElementChild === splitterPanel) {
panelIsOnRight = true;
}
}
const gutterRect = gutter?.getBoundingClientRect();
if (panelIsOnRight) {
const boundary = gutterRect?.left ?? Math.max(0, panelRect.left - 8);
return {
left: 0,
right: Math.max(0, viewportWidth - boundary),
observedElements: gutter ? [panelElement, gutter] : [panelElement],
};
}
const boundary = gutterRect?.right ?? Math.min(viewportWidth, panelRect.right + 8);
return {
left: Math.min(viewportWidth, boundary),
right: 0,
observedElements: gutter ? [panelElement, gutter] : [panelElement],
};
};
const updateWorkflowOverviewButton = () => {
if (!workflowOverviewToggleButton) {
return;
}
const isOpen = isWorkflowOverviewOpen();
workflowOverviewToggleButton.setAttribute("aria-pressed", String(isOpen));
workflowOverviewToggleButton.title = isOpen
? "Close Workflow Overview"
: "Open Workflow Overview";
workflowOverviewToggleButton.classList.toggle("lf-workflow-overview-open", isOpen);
};
const applyWorkflowOverviewLayout = () => {
updateWorkflowOverviewButton();
if (!modalContent) {
return;
}
const layout = getWorkflowOverviewLayout();
const left = layout?.left ?? 0;
const right = layout?.right ?? 0;
modalContent.style.left = left + "px";
modalContent.style.right = right + "px";
if (lastModalBounds.left !== left || lastModalBounds.right !== right) {
lastModalBounds = { left, right };
canvas.render();
if (node.onResize) {
node.onResize();
}
}
};
const scheduleWorkflowOverviewLayoutUpdate = () => {
if (workflowOverviewLayoutFrame !== null) {
return;
}
workflowOverviewLayoutFrame = window.requestAnimationFrame(() => {
workflowOverviewLayoutFrame = null;
applyWorkflowOverviewLayout();
});
};
const attachWorkflowOverviewResizeObserver = () => {
workflowOverviewResizeObserver?.disconnect();
workflowOverviewResizeObserver = null;
if (typeof ResizeObserver === "undefined") {
scheduleWorkflowOverviewLayoutUpdate();
return;
}
const layout = getWorkflowOverviewLayout();
if (!layout) {
scheduleWorkflowOverviewLayoutUpdate();
return;
}
const observer = new ResizeObserver(scheduleWorkflowOverviewLayoutUpdate);
layout.observedElements.forEach((element) => observer.observe(element));
workflowOverviewResizeObserver = observer;
scheduleWorkflowOverviewLayoutUpdate();
};
const startWorkflowOverviewLayoutTracking = () => {
if (!workflowOverviewWindowResizeHandler) {
workflowOverviewWindowResizeHandler = scheduleWorkflowOverviewLayoutUpdate;
window.addEventListener("resize", workflowOverviewWindowResizeHandler);
}
attachWorkflowOverviewResizeObserver();
scheduleWorkflowOverviewLayoutUpdate();
};
const stopWorkflowOverviewLayoutTracking = () => {
workflowOverviewResizeObserver?.disconnect();
workflowOverviewResizeObserver = null;
if (workflowOverviewWindowResizeHandler) {
window.removeEventListener("resize", workflowOverviewWindowResizeHandler);
workflowOverviewWindowResizeHandler = null;
}
if (workflowOverviewLayoutFrame !== null) {
window.cancelAnimationFrame(workflowOverviewLayoutFrame);
workflowOverviewLayoutFrame = null;
}
lastModalBounds = { left: Number.NaN, right: Number.NaN };
};
const waitForWorkflowOverviewState = (expectedState: boolean): Promise<void> => {
return new Promise((resolve) => {
const deadline = performance.now() + 1000;
const checkState = () => {
if (!isEditorOpen ||
isWorkflowOverviewOpen() === expectedState ||
performance.now() >= deadline) {
resolve();
return;
}
window.requestAnimationFrame(checkState);
};
checkState();
});
};
const toggleWorkflowOverview = async () => {
const requestId = ++workflowOverviewToggleRequest;
const nativeToggle = findNativeWorkflowOverviewToggle();
if (!nativeToggle) {
log.warn("Could not find ComfyUI's Workflow Overview toggle.");
return;
}
const expectedState = !isWorkflowOverviewOpen();
nativeToggle.click();
await waitForWorkflowOverviewState(expectedState);
if (!isEditorOpen || requestId !== workflowOverviewToggleRequest) {
return;
}
attachWorkflowOverviewResizeObserver();
applyWorkflowOverviewLayout();
};
/**
* Adjusts the viewport when entering fullscreen mode.
*/
@@ -2029,12 +2285,22 @@ $el("label.lf-clipboard-switch.lf-mask-switch", {
};
const closeEditor = () => {
if (originalParent && backdrop) {
if (!isEditorOpen) {
return;
}
stopWorkflowOverviewLayoutTracking();
if (originalParent) {
originalParent.appendChild(mainContainer);
document.body.removeChild(backdrop);
}
if (backdrop?.parentNode) {
backdrop.parentNode.removeChild(backdrop);
}
isEditorOpen = false;
modalContent = null;
workflowOverviewToggleButton = null;
openEditorBtn.textContent = "⛶";
tooltipManager.setTooltip(openEditorBtn, "Open in Editor");
@@ -2074,13 +2340,32 @@ $el("label.lf-clipboard-switch.lf-mask-switch", {
}
backdrop = $el("div.lf-painter-modal-backdrop") as HTMLDivElement;
const modalContent = $el("div.lf-painter-modal-content") as HTMLDivElement;
modalContent = $el("div.lf-painter-modal-content") as HTMLDivElement;
workflowOverviewToggleButton = document.createElement("button");
workflowOverviewToggleButton.type = "button";
workflowOverviewToggleButton.className =
"lf-painter-button lf-icon-button lf-workflow-overview-toggle";
workflowOverviewToggleButton.setAttribute("aria-label", "Toggle Workflow Overview");
workflowOverviewToggleButton.setAttribute("aria-pressed", "false");
workflowOverviewToggleButton.title = "Open Workflow Overview";
const workflowOverviewIcon = document.createElement("i");
workflowOverviewIcon.className = "icon-[lucide--panel-right] size-4";
workflowOverviewIcon.setAttribute("aria-hidden", "true");
workflowOverviewToggleButton.appendChild(workflowOverviewIcon);
workflowOverviewToggleButton.onclick = () => {
void toggleWorkflowOverview();
};
modalContent.appendChild(mainContainer);
modalContent.appendChild(workflowOverviewToggleButton);
backdrop.appendChild(modalContent);
document.body.appendChild(backdrop);
isEditorOpen = true;
applyWorkflowOverviewLayout();
startWorkflowOverviewLayoutTracking();
openEditorBtn.textContent = "X";
tooltipManager.setTooltip(openEditorBtn, "Close Editor (ESC)");
@@ -2143,6 +2428,16 @@ $el("label.lf-clipboard-switch.lf-mask-switch", {
panel: controlPanel,
destroy: () => {
widgetDestroyed = true;
stopWorkflowOverviewLayoutTracking();
if (isEditorOpen) {
document.removeEventListener('keydown', handleEscKey);
if (backdrop?.parentNode) {
backdrop.parentNode.removeChild(backdrop);
}
isEditorOpen = false;
modalContent = null;
workflowOverviewToggleButton = null;
}
unregisterTooltips();
mattingAbortController?.abort();
closeInputMenu();
+36 -13
View File
@@ -1066,27 +1066,50 @@
.lf-painter-modal-backdrop {
position: fixed;
top: 0;
left: 0;
inset: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
background-color: transparent;
z-index: 999999;
pointer-events: none;
}
.lf-painter-modal-content {
position: absolute;
inset: 0;
width: auto;
height: auto;
background-color: #353535;
border: none;
border-radius: 0;
box-shadow: none;
display: flex;
flex-direction: column;
overflow: hidden;
pointer-events: auto;
box-sizing: border-box;
}
.lf-workflow-overview-toggle {
position: absolute;
top: 12px;
right: 12px;
z-index: 30;
width: 32px;
min-width: 32px;
height: 32px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
}
.lf-painter-modal-content {
width: 90vw;
height: 90vh;
background-color: #353535;
border: 1px solid #222;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
display: flex;
flex-direction: column;
position: relative;
.lf-workflow-overview-toggle i[class~="icon-[lucide--panel-right]"] {
font-size: 16px;
}
.lf-workflow-overview-toggle.lf-workflow-overview-open {
background: var(--comfy-menu-bg, #2f2f2f);
}
.lf-painter-main-container {