Files
m0rtus59-ComfyUI-ComfySidebar/js/layout.js
T
2026-07-18 14:11:04 +05:00

408 lines
14 KiB
JavaScript

// layout.js - Fully Fixed & Renamed to 'Hide Junk'
import { app } from "/scripts/app.js";
const STYLE_ID = "comfy-sidebar-classic-layout-override";
const CLASSIC_LAYOUT_CSS_MEDIA = `
/* Style and align the custom plugins bar (Manager, LoRA, etc.) */
[class*="actionbar"]:not(.actionbar),
[class*="actionbar-buttons"],
.actionbar-buttons {
position: fixed !important;
top: -5px !important;
right: 4px !important;
left: auto !important;
transform: none !important;
z-index: 1010 !important;
/* Nuke the background rectangle, border, and shadows */
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
margin: 0 !important;
display: flex !important;
align-items: center !important;
}
/* Base rules: Large/Wide desktop screens (>= 1600px width) */
.p-tabview-nav-content,
[class*="tabview"] {
padding-left: 150px !important;
padding-right: 580px !important;
}
/* Medium desktop screens (between 1200px and 1599px width) */
@media (max-width: 1599px) {
.p-tabview-nav-content,
[class*="tabview"] {
padding-left: 100px !important;
padding-right: 420px !important;
}
}
/* Standard laptops / small monitors (between 900px and 1199px width) */
@media (max-width: 1199px) {
.p-tabview-nav-content,
[class*="tabview"] {
padding-left: 40px !important;
padding-right: 280px !important;
}
}
/* Very narrow or heavily zoomed displays (< 900px width) */
@media (max-width: 899px) {
.p-tabview-nav-content,
[class*="tabview"] {
padding-left: 10px !important;
padding-right: 180px !important;
}
}
`;
export function applyClassicLayout(enable) {
let styleEl = document.getElementById(STYLE_ID);
if (enable) {
if (app.ui && app.ui.settings) {
app.ui.settings.setSettingValue("Comfy.Workflow.WorkflowTabsPosition", "Topbar");
}
if (!styleEl) {
styleEl = document.createElement("style");
styleEl.id = STYLE_ID;
document.head.appendChild(styleEl);
}
styleEl.textContent = CLASSIC_LAYOUT_CSS_MEDIA;
} else {
if (styleEl) styleEl.remove();
if (app.ui && app.ui.settings) {
app.ui.settings.setSettingValue("Comfy.Workflow.WorkflowTabsPosition", "Sidebar");
}
}
}
/* --- Properties Panel Toggle Corrections --- */
let savedButtonData = null;
let domObserver = null;
let syncScheduled = false;
function findOriginalButton() {
const icon = document.querySelector('[class*="lucide--panel-right"], [class*="lucide--panel-left"]');
if (icon) {
const btn = icon.closest('button, [role="button"], .comfyui-menu-item, .p-button');
if (btn && !btn.classList.contains("comfy-sidebar-custom-properties-toggle")) {
return btn;
}
}
return null;
}
function findTopbarContainer() {
const container = document.querySelector('[class*="actionbar-buttons"], .actionbar-buttons, [class*="actionbar"]');
if (container) return container;
const orig = findOriginalButton();
if (orig && orig.parentNode) return orig.parentNode;
const buttons = document.querySelectorAll("button, .p-button");
for (const btn of buttons) {
if (btn.classList.contains("comfy-sidebar-custom-properties-toggle")) continue;
const title = (btn.getAttribute("title") || btn.getAttribute("aria-label") || "").toLowerCase();
if (title.includes("run") || title.includes("queue") || title.includes("manager")) {
if (btn.parentNode) return btn.parentNode;
}
}
return null;
}
function isPropertiesPanelOpen() {
const icon = document.querySelector('[class*="lucide--panel-right"], [class*="lucide--panel-left"]');
return !!(icon && icon.className.includes("lucide--panel-left"));
}
function findActiveQueueIndicator() {
const container = findTopbarContainer();
if (!container) return null;
const buttons = container.querySelectorAll("button, .p-button, .comfyui-menu-item");
for (const btn of buttons) {
if (btn.classList.contains("comfy-sidebar-custom-properties-toggle")) continue;
const text = (btn.textContent || "").toLowerCase();
if (text.includes("active") || text.includes("queued") || text.includes("running") || text.includes("pending")) {
return btn;
}
}
return null;
}
function updateSidebarTabsVisibility() {
const sidebar = document.querySelector('.comfyui-sidebar, .comfy-sidebar, .sidebar, [class*="sidebar-nav"], [class*="sidebar"]');
if (!sidebar) return;
// Direct mapping from tab settings to their unique class-based HTML icon tags
const tabIconSelectors = {
"Assets": '[class*="comfy--image-ai-edit"]',
"Nodes": '[class*="comfy--node"]',
"Models": '[class*="comfy--ai-model"]',
"Workflows": '[class*="comfy--workflow"]',
"Apps": '[class*="lucide--panels-top-left"]',
"NodesMap": '[class*="pi-sitemap"]',
"Templates": '[class*="comfy--template"]'
};
Object.entries(tabIconSelectors).forEach(([tab, selector]) => {
// Read from renamed "Hide Junk" path
const shouldHide = app.ui?.settings?.getSettingValue(`Comfy Sidebar.Hide Junk.${tab}`) ?? false;
const icon = sidebar.querySelector(selector);
if (icon) {
const tabBtn = icon.closest('.comfyui-sidebar-tab, button, [role="tab"], li, a, .comfyui-sidebar-item') || icon;
if (shouldHide) {
if (tabBtn.style.display !== "none") {
tabBtn.style.setProperty("display", "none", "important");
}
} else {
if (tabBtn.style.display === "none") {
tabBtn.style.removeProperty("display");
}
}
}
});
// Handle "Override Stock Job History Tab" using its renamed setting path
const hideStockHistory = app.ui?.settings?.getSettingValue("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ?? false;
const historyIcon = sidebar.querySelector('[class*="lucide--history"]');
if (historyIcon) {
const historyBtn = historyIcon.closest('.comfyui-sidebar-tab, button, [role="tab"], li, a, .comfyui-sidebar-item') || historyIcon;
if (hideStockHistory) {
if (historyBtn.style.display !== "none") {
historyBtn.style.setProperty("display", "none", "important");
}
} else {
if (historyBtn.style.display === "none") {
historyBtn.style.removeProperty("display");
}
}
}
}
function findGraphButton() {
// 1. Target elements with Tailwind classes, but IGNORE those inside modal settings/sidebars
const elements = document.querySelectorAll('.bg-secondary-background.rounded-lg.items-center.inline-flex.pointer-events-auto');
for (const el of elements) {
// Filter out dialogs, sidebar overlays, settings drawers, and modals
if (el.closest('.p-dialog, .comfy-modal, [role="dialog"], .p-sidebar, .comfy-settings')) {
continue;
}
return el;
}
// 2. Same fallback logic, excluding modals
const fallbacks = document.querySelectorAll('button, .bg-secondary-background');
for (const el of fallbacks) {
if (el.closest('.p-dialog, .comfy-modal, [role="dialog"], .p-sidebar, .comfy-settings')) {
continue;
}
const text = (el.textContent || el.getAttribute("title") || "").toLowerCase();
if (text.includes("graph") || text.includes("workflow") || el.querySelector('[class*="sitemap"], [class*="workflow"]')) {
return el;
}
}
return null;
}
export function syncClassicLayout() {
const isClassicLayoutEnabled = app.ui?.settings?.getSettingValue("Comfy Sidebar.Comfy Layout") ?? false;
// 1. Handle "Hide Graph Button" Toggle under the renamed "Hide Junk" path
const hideGraphBtn = app.ui?.settings?.getSettingValue("Comfy Sidebar.Hide Junk.Graph Button") ?? false;
const graphBtn = findGraphButton();
if (graphBtn) {
if (hideGraphBtn) {
if (graphBtn.style.display !== "none") {
graphBtn.style.setProperty("display", "none", "important");
}
} else {
if (graphBtn.style.display === "none") {
graphBtn.style.removeProperty("display");
}
}
}
// 2. Exit early and clean up overrides if Classic Topbar layout is off
if (!isClassicLayoutEnabled) {
const originalBtn = findOriginalButton();
if (originalBtn && originalBtn.classList.contains("comfy-sidebar-hide-original-properties-btn")) {
originalBtn.classList.remove("comfy-sidebar-hide-original-properties-btn");
}
const customBtn = document.querySelector(".comfy-sidebar-custom-properties-toggle");
if (customBtn) {
customBtn.remove();
}
const indicator = findActiveQueueIndicator();
if (indicator && indicator.style.display === "none") {
indicator.style.removeProperty("display");
}
updateSidebarTabsVisibility();
return;
}
// 3. Handle Active Layout elements synchronization
const originalBtn = findOriginalButton();
const container = findTopbarContainer();
const openState = isPropertiesPanelOpen();
if (originalBtn) {
if (!originalBtn.classList.contains("comfy-sidebar-hide-original-properties-btn")) {
originalBtn.classList.add("comfy-sidebar-hide-original-properties-btn");
}
savedButtonData = {
className: originalBtn.className.replace("comfy-sidebar-hide-original-properties-btn", "").trim(),
innerHTML: originalBtn.innerHTML,
tagName: originalBtn.tagName,
attributes: Array.from(originalBtn.attributes).map(attr => ({
name: attr.name,
value: attr.value
}))
};
}
let customBtn = document.querySelector(".comfy-sidebar-custom-properties-toggle");
if (!customBtn && savedButtonData && container) {
customBtn = document.createElement(savedButtonData.tagName);
customBtn.className = savedButtonData.className + " comfy-sidebar-custom-properties-toggle";
customBtn.innerHTML = savedButtonData.innerHTML;
for (const attr of savedButtonData.attributes) {
if (attr.name !== "class" && attr.name !== "id" && attr.name !== "style") {
customBtn.setAttribute(attr.name, attr.value);
}
}
Object.assign(customBtn.style, {
position: "relative",
zIndex: "1000",
margin: "0",
boxSizing: "border-box",
opacity: "1",
pointerEvents: "auto",
display: "inline-flex"
});
customBtn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const nativeBtn = findOriginalButton();
if (nativeBtn) nativeBtn.click();
});
container.appendChild(customBtn);
}
if (customBtn) {
customBtn.classList.toggle("comfy-panel-open", openState);
customBtn.style.display = "inline-flex";
}
// 4. Handle "0 active" indicator visibility based on history tab settings
const hideQueueIndicator = app.ui?.settings?.getSettingValue("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ?? false;
const indicator = findActiveQueueIndicator();
if (indicator) {
if (hideQueueIndicator) {
if (indicator.style.display !== "none") {
indicator.style.setProperty("display", "none", "important");
}
} else {
if (indicator.style.display === "none") {
indicator.style.removeProperty("display");
}
}
}
updateSidebarTabsVisibility();
}
export function setupPropertiesPanelToggleFix() {
if (!document.getElementById("comfy-sidebar-layout-fix-styles")) {
const style = document.createElement("style");
style.id = "comfy-sidebar-layout-fix-styles";
style.textContent = `
.comfy-sidebar-hide-original-properties-btn {
display: none !important;
}
.comfy-sidebar-custom-properties-toggle {
order: 99999 !important;
margin-left: -8px !important;
margin-right: 0px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
padding: 0 !important;
}
.comfy-sidebar-custom-properties-toggle.comfy-panel-open {
order: 99999 !important;
margin-left: 0px !important;
margin-right: 0px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
padding: 0 !important;
}
`;
document.head.appendChild(style);
}
if (domObserver) {
domObserver.disconnect();
}
syncClassicLayout();
domObserver = new MutationObserver((mutations) => {
let shouldSync = false;
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0) {
// Ensure mutation is on an actual Element node (nodeType 1) and not just text node updates
const hasElementMutation = Array.from(mutation.addedNodes).some(node => node.nodeType === 1) ||
Array.from(mutation.removedNodes).some(node => node.nodeType === 1);
if (!hasElementMutation) continue;
// Ignore mutations on our own components to avoid feedback loops
const isOurNode = Array.from(mutation.addedNodes).some(node =>
node.classList?.contains?.("comfy-sidebar-custom-properties-toggle") ||
node.id === "comfy-sidebar-layout-fix-styles"
);
if (isOurNode) continue;
shouldSync = true;
break;
}
}
if (shouldSync && !syncScheduled) {
syncScheduled = true;
requestAnimationFrame(() => {
syncClassicLayout();
syncScheduled = false;
});
}
});
domObserver.observe(document.body, {
childList: true,
subtree: true
});
}