diff --git a/js/comfy/adapter.js b/js/comfy/adapter.js index f695c9f..42718b1 100644 --- a/js/comfy/adapter.js +++ b/js/comfy/adapter.js @@ -1,4 +1,5 @@ import { app } from "/scripts/app.js"; +import { SettingIds } from "../core/constants.js"; export function findOriginalPropertiesButton() { const byTestId = document.querySelector('[data-testid="properties-panel-toggle"]'); @@ -26,7 +27,7 @@ export function findOriginalPropertiesButton() { } export function findTopbarContainer() { - const container = document.querySelector('[data-testid="action-bar-buttons"], [class*="actionbar-buttons"], .actionbar-buttons, [class*="actionbar"]'); + const container = document.querySelector('.actionbar-container, [data-testid="action-bar-buttons"], [class*="actionbar-buttons"], .actionbar-buttons, [class*="actionbar"]'); if (container) return container; const orig = findOriginalPropertiesButton(); @@ -156,6 +157,11 @@ export function findNativeExtensionsPanel() { } export function findGraphButton() { + const byTestId = document.querySelector('[data-testid="view-mode-toggle"]'); + if (byTestId && !byTestId.closest('.p-dialog, .comfy-modal, [role="dialog"], .p-sidebar, .comfy-settings, .comfyui-sidebar, .comfy-sidebar')) { + return byTestId; + } + const elements = document.querySelectorAll('.p-1.bg-base-background.rounded-lg, .bg-base-background.rounded-lg, .bg-secondary-background.rounded-lg.items-center.inline-flex.pointer-events-auto, [data-testid="graph-view-button"]'); for (const el of elements) { if (el.closest('.p-dialog, .comfy-modal, [role="dialog"], .p-sidebar, .comfy-settings, .comfyui-sidebar, .comfy-sidebar')) continue; @@ -230,39 +236,135 @@ export function updateSidebarBadge(count) { } } -export function applySidebarOverride() { - const overrideStock = app.ui.settings?.getSettingValue?.("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ?? false; - const stdBtn = findStandardQueueButton(); - const ourBtn = findOurSidebarButton(); +let isApplyingSidebarOverride = false; - if (stdBtn) { - if (!stdBtn._originalDisplay) { - stdBtn._originalDisplay = window.getComputedStyle(stdBtn).display || "block"; +export function findSidebarMenuButton(sidebar) { + const root = sidebar || document.querySelector('[data-testid="sidebar-top-group"]') || + document.querySelector('.comfyui-sidebar, .comfy-sidebar, .sidebar'); + if (!root) return null; + + // 1. Look for explicit menu or logo test ID + const byTestId = root.querySelector( + '[data-testid*="menu-button"], [data-testid*="app-menu"], [data-testid*="comfy-logo"], [data-testid="sidebar-menu-button"]' + ); + if (byTestId) { + return byTestId.closest('button, [role="button"], .comfyui-sidebar-tab, .comfyui-sidebar-item') || byTestId; + } + + // 2. Look for title, tooltip, or aria-label containing "menu" + const candidates = Array.from(root.querySelectorAll('button, [role="button"], a, .comfyui-sidebar-tab, .comfyui-sidebar-item')); + const menuBtn = candidates.find(el => { + if (el.classList.contains('comfy-sidebar-tab') || el.id?.includes('classic-comfy-sidebar')) return false; + const title = (el.getAttribute('title') || el.getAttribute('aria-label') || el.textContent || '').trim().toLowerCase(); + return title === 'menu' || title.startsWith('menu'); + }); + if (menuBtn) return menuBtn; + + // 3. Fallback: the first child element that is not our button and not the stock queue button + const ourBtn = findOurSidebarButton(); + const stdBtn = findStandardQueueButton(); + for (const child of root.children) { + if (child !== ourBtn && child !== stdBtn) { + return child; } + } + + return null; +} + +export function applySidebarOverride() { + if (isApplyingSidebarOverride) return; + isApplyingSidebarOverride = true; + + try { + const overrideStock = ( + app.extensionManager?.setting?.get?.(SettingIds.OVERRIDE_STOCK_HISTORY) ?? + app.ui?.settings?.getSettingValue?.(SettingIds.OVERRIDE_STOCK_HISTORY) ?? + app.ui?.settings?.getSettingValue?.("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ?? + app.ui?.settings?.getSettingValue?.("Comfy Sidebar.Override Stock History") ?? + false + ); + const stdBtn = findStandardQueueButton(); + const ourBtn = findOurSidebarButton(); + const topGroup = document.querySelector('[data-testid="sidebar-top-group"]') || + (ourBtn ? ourBtn.closest('[data-testid="sidebar-top-group"]') : null); + + if (ourBtn && !ourBtn._capturedOriginal) { + ourBtn._capturedOriginal = true; + ourBtn._originalParent = ourBtn.parentNode; + ourBtn._originalNextSibling = ourBtn.nextSibling; + } + if (overrideStock) { - stdBtn.style.setProperty("display", "none", "important"); - if (!stdBtn._overrideClickListener) { - stdBtn._overrideClickListener = (e) => { - e.preventDefault(); - e.stopPropagation(); - const b = findOurSidebarButton(); - if (b) b.click(); - }; - stdBtn.addEventListener('click', stdBtn._overrideClickListener, true); + if (stdBtn) { + if (!stdBtn._originalDisplay) { + stdBtn._originalDisplay = window.getComputedStyle(stdBtn).display || "block"; + } + if (stdBtn.style.display !== "none") { + stdBtn.style.setProperty("display", "none", "important"); + } + if (!stdBtn._overrideClickListener) { + stdBtn._overrideClickListener = (e) => { + e.preventDefault(); + e.stopPropagation(); + const b = findOurSidebarButton(); + if (b) b.click(); + }; + stdBtn.addEventListener('click', stdBtn._overrideClickListener, true); + } } - if (ourBtn && stdBtn.parentNode && ourBtn.nextSibling !== stdBtn) { - stdBtn.parentNode.insertBefore(ourBtn, stdBtn); + + if (ourBtn) { + const parent = (topGroup && topGroup.contains(ourBtn)) ? topGroup : (stdBtn ? stdBtn.parentNode : ourBtn.parentNode); + if (parent) { + const menuBtn = findSidebarMenuButton(parent); + if (menuBtn && menuBtn.parentNode === parent) { + // Place Queue immediately below the Menu button + if (menuBtn.nextSibling !== ourBtn) { + parent.insertBefore(ourBtn, menuBtn.nextSibling); + } + } else if (stdBtn && stdBtn.parentNode === parent) { + // Place Queue right where stock Job History was + if (stdBtn.previousSibling !== ourBtn && stdBtn !== ourBtn) { + parent.insertBefore(ourBtn, stdBtn); + } + } else { + // Fallback: top of the group + const first = parent.firstElementChild; + if (first && first !== ourBtn) { + parent.insertBefore(ourBtn, first); + } + } + } } } else { - stdBtn.style.setProperty("display", stdBtn._originalDisplay === "none" ? "block" : stdBtn._originalDisplay); - if (stdBtn._overrideClickListener) { - stdBtn.removeEventListener('click', stdBtn._overrideClickListener, true); - stdBtn._overrideClickListener = null; + if (stdBtn) { + if (stdBtn.style.display === "none") { + stdBtn.style.removeProperty("display"); + if (stdBtn._originalDisplay && stdBtn._originalDisplay !== "none") { + stdBtn.style.display = stdBtn._originalDisplay; + } + } + if (stdBtn._overrideClickListener) { + stdBtn.removeEventListener('click', stdBtn._overrideClickListener, true); + stdBtn._overrideClickListener = null; + } } - if (ourBtn && stdBtn.parentNode && ourBtn.parentNode === stdBtn.parentNode && ourBtn !== stdBtn.parentNode.lastChild) { - stdBtn.parentNode.appendChild(ourBtn); + + if (ourBtn) { + if (ourBtn._originalNextSibling && ourBtn._originalNextSibling.parentNode === ourBtn.parentNode) { + if (ourBtn.nextSibling !== ourBtn._originalNextSibling) { + ourBtn.parentNode.insertBefore(ourBtn, ourBtn._originalNextSibling); + } + } else if (ourBtn._originalParent && ourBtn.parentNode === ourBtn._originalParent) { + if (ourBtn !== ourBtn.parentNode.lastElementChild) { + ourBtn.parentNode.appendChild(ourBtn); + } + } } } + } finally { + isApplyingSidebarOverride = false; } } diff --git a/js/comfy/layout.js b/js/comfy/layout.js index 1ffe2bf..57a5218 100644 --- a/js/comfy/layout.js +++ b/js/comfy/layout.js @@ -6,8 +6,10 @@ import { findActiveQueueIndicator, findNativeExtensionsPanel, findGraphButton, - findPropertiesPanel + findPropertiesPanel, + applySidebarOverride } from "./adapter.js"; +import { SettingIds } from "../core/constants.js"; import { isRuntimePreviewEnabled, setRuntimePreviewEnabled, setRuntimePreviewStateListener } from "../utils/comparison.js"; // Listen for runtime preview state changes to keep button UI in sync @@ -18,20 +20,22 @@ setRuntimePreviewStateListener(() => { const STYLE_ID = "comfy-sidebar-classic-layout-override"; const CLASSIC_LAYOUT_CSS_MEDIA = ` -/* Neutralize the leftover floating shell box to prevent empty dark square artifacts */ -.actionbar-container, -.shadow-interface.rounded-lg.bg-comfy-menu-bg:not(.floating-panel):not([role="toolbar"]), -.shadow-interface.rounded-lg.border-interface-stroke:not(.floating-panel):not([role="toolbar"]), -.shadow-interface.border.rounded-lg:has([class*="actionbar-buttons"]), -.shadow-interface.border.rounded-lg:has(.actionbar-buttons), -div.border-interface-stroke.rounded-lg:has([class*="actionbar-buttons"]), -div.shadow-interface.py-1\\.75 { +/* Neutralize the leftover floating shell box to prevent empty dark square/circle artifacts */ +[data-testid="action-bar-card"], +.floating-panel:has(.actionbar-container), +.floating-panel:has([class*="actionbar"]), +div.shadow-interface:has(.actionbar-container), +div.shadow-interface:has([class*="actionbar-buttons"]), +div:has(> .actionbar-container):not(header):not(.topbar), +div:has(> [class*="actionbar"]):not(header):not(.topbar):not(.actionbar-container) { border: none !important; background: transparent !important; box-shadow: none !important; + outline: none !important; } /* Dock actionbar buttons in the topbar row with transparent background */ +.actionbar-container, [class*="actionbar"]:not(.actionbar), [class*="actionbar-buttons"], .actionbar-buttons { @@ -102,9 +106,27 @@ export function syncStockHistoryAndProgressSettings(enable) { let savedButtonData = null; let domObserver = null; +let sidebarTopObserver = null; let syncScheduled = false; let actionbarResizeObserver = null; +function setupSidebarTopObserver() { + if (sidebarTopObserver) return; + const target = document.querySelector('[data-testid="sidebar-top-group"]') || + document.querySelector('.comfyui-sidebar, .comfy-sidebar, .sidebar'); + if (!target) return; + + sidebarTopObserver = new MutationObserver(() => { + applySidebarOverride(); + updateSidebarTabsVisibility(); + }); + + sidebarTopObserver.observe(target, { + childList: true, + subtree: true + }); +} + function isSidebarOnLeft() { const sidebar = document.querySelector('.comfyui-sidebar, .comfy-sidebar, .sidebar, [class*="sidebar-nav"], [class*="sidebar"]'); if (!sidebar) return false; @@ -371,7 +393,7 @@ function setupCanvasMenuPopoverRelocator() { } function updateTopMetrics() { - const bar = document.querySelector('.actionbar-buttons, [class*="actionbar-buttons"]') || findTopbarContainer(); + const bar = document.querySelector('.actionbar-container, .actionbar-buttons, [class*="actionbar-buttons"]') || findTopbarContainer(); const tabsEl = document.querySelector('.workflow-tabs-container'); const topbar = tabsEl?.closest('header, .topbar, [class*="topbar"]') || tabsEl; @@ -491,7 +513,13 @@ function updateSidebarTabsVisibility() { } }); - const hideStockHistory = app.ui?.settings?.getSettingValue("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ?? false; + const hideStockHistory = ( + app.extensionManager?.setting?.get?.(SettingIds.OVERRIDE_STOCK_HISTORY) ?? + app.ui?.settings?.getSettingValue?.(SettingIds.OVERRIDE_STOCK_HISTORY) ?? + app.ui?.settings?.getSettingValue?.("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ?? + app.ui?.settings?.getSettingValue?.("Comfy Sidebar.Override Stock History") ?? + false + ); const historyTarget = sidebar.querySelector('[data-testid="queue-tab-button"], [data-testid="job-history-tab-button"], [class*="lucide--history"]'); if (historyTarget) { const historyBtn = historyTarget.closest('.comfyui-sidebar-tab, button, [role="tab"], li, a, .comfyui-sidebar-item') || historyTarget; @@ -775,6 +803,10 @@ export function syncClassicLayout() { syncPropertiesButton(isClassicLayoutEnabled); syncActiveQueueIndicator(isClassicLayoutEnabled); updateSidebarTabsVisibility(); + applySidebarOverride(); + if (!sidebarTopObserver) { + setupSidebarTopObserver(); + } syncRuntimePreviewButton(); } catch (err) { @@ -792,6 +824,7 @@ export function syncClassicLayout() { export function setupPropertiesPanelToggleFix() { setupCanvasMenuTooltipRelocator(); setupCanvasMenuPopoverRelocator(); + setupSidebarTopObserver(); if (!document.getElementById("comfy-sidebar-layout-fix-styles")) { const style = document.createElement("style"); @@ -1083,6 +1116,10 @@ export function destroyLayoutFix() { domObserver.disconnect(); domObserver = null; } + if (sidebarTopObserver) { + sidebarTopObserver.disconnect(); + sidebarTopObserver = null; + } if (actionbarResizeObserver) { actionbarResizeObserver.disconnect(); actionbarResizeObserver = null; diff --git a/js/sidebar.js b/js/sidebar.js index 92da7fa..2b72e26 100644 --- a/js/sidebar.js +++ b/js/sidebar.js @@ -3,7 +3,7 @@ import { SettingIds } from "./core/constants.js"; import { injectStyles } from "./utils/styles.js"; import { setupDragAndDrop } from "./utils/dragdrop.js"; import { renumberNodesTopologically, setManualNodeId } from "./utils/nodeRenumber.js"; -import { findOurSidebarButton } from "./comfy/adapter.js"; +import { findOurSidebarButton, applySidebarOverride } from "./comfy/adapter.js"; import { syncAllNodeBadges, setupVueNodeObserver, toggleIgnoreActiveNode } from "./comfy/nodes.js"; import { applyClassicLayout, setupPropertiesPanelToggleFix, syncClassicLayout, syncStockHistoryAndProgressSettings, destroyLayoutFix } from "./comfy/layout.js"; import { initSessionAndHistory, syncQueue } from "./queue/queueService.js"; @@ -75,7 +75,11 @@ function registerSettings() { if (isInitialized && app.ui?.settings) { syncStockHistoryAndProgressSettings(value); } - setTimeout(() => { syncClassicLayout(); }, 0); + applySidebarOverride(); + setTimeout(() => { + applySidebarOverride(); + syncClassicLayout(); + }, 50); } }); @@ -450,6 +454,10 @@ app.registerExtension({ type: "custom", render: (el) => { el.appendChild(sidebarContainer); } }); + + requestAnimationFrame(() => { + applySidebarOverride(); + }); }, destroy() { diff --git a/pyproject.toml b/pyproject.toml index d0da353..7f8c8a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfy-sidebar" description = "A compact and powerful layout overhaul for ComfyUI that makes generation results easier to browse, preview, compare, and manage." -version = "1.7.3" +version = "1.7.4" license = { file = "LICENSE" } dependencies = [ "send2trash>=1.8.0",