Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc8337f6e6 | ||
|
|
b4a21ef852 | ||
|
|
65e53946d2 |
+127
-25
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+72
-39
@@ -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;
|
||||
@@ -578,10 +606,6 @@ function syncPropertiesButton(isClassicLayoutEnabled) {
|
||||
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,
|
||||
@@ -591,6 +615,26 @@ function syncPropertiesButton(isClassicLayoutEnabled) {
|
||||
value: attr.value
|
||||
}))
|
||||
};
|
||||
|
||||
// When closed: keep originalBtn visible (ComfyUI renders error outlines & dot natively).
|
||||
// When open: hide originalBtn so our customBtn takes its pinned place.
|
||||
if (openState) {
|
||||
if (!originalBtn.classList.contains("comfy-sidebar-hide-original-properties-btn")) {
|
||||
originalBtn.classList.add("comfy-sidebar-hide-original-properties-btn");
|
||||
}
|
||||
} else {
|
||||
if (originalBtn.classList.contains("comfy-sidebar-hide-original-properties-btn")) {
|
||||
originalBtn.classList.remove("comfy-sidebar-hide-original-properties-btn");
|
||||
}
|
||||
}
|
||||
|
||||
// Clicking the native button to open the panel triggers an instant layout sync
|
||||
if (!originalBtn._syncAttached) {
|
||||
originalBtn._syncAttached = true;
|
||||
originalBtn.addEventListener("click", () => {
|
||||
requestAnimationFrame(() => syncClassicLayout());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let customBtn = document.querySelector(".comfy-sidebar-custom-properties-toggle");
|
||||
@@ -621,31 +665,11 @@ function syncPropertiesButton(isClassicLayoutEnabled) {
|
||||
}
|
||||
|
||||
if (customBtn) {
|
||||
if (originalBtn) {
|
||||
const cleanedHTML = cleanHTML(originalBtn.innerHTML);
|
||||
if (cleanedHTML && customBtn.innerHTML !== cleanedHTML) {
|
||||
const existingDot = customBtn.querySelector('.comfy-sidebar-error-dot');
|
||||
customBtn.innerHTML = cleanedHTML;
|
||||
if (existingDot) customBtn.appendChild(existingDot);
|
||||
}
|
||||
|
||||
const titleVal = originalBtn.getAttribute("title") || originalBtn.getAttribute("aria-label") || "Toggle properties panel";
|
||||
customBtn.setAttribute("title", titleVal);
|
||||
customBtn.setAttribute("aria-label", titleVal);
|
||||
|
||||
for (const cls of originalBtn.classList) {
|
||||
if (cls !== "comfy-sidebar-hide-original-properties-btn" && !customBtn.classList.contains(cls)) {
|
||||
customBtn.classList.add(cls);
|
||||
}
|
||||
}
|
||||
|
||||
syncErrorBadge(originalBtn, customBtn);
|
||||
} else if (!customBtn.getAttribute("title")) {
|
||||
if (!customBtn.getAttribute("title")) {
|
||||
customBtn.setAttribute("title", "Toggle properties panel");
|
||||
customBtn.setAttribute("aria-label", "Toggle properties panel");
|
||||
}
|
||||
customBtn.classList.toggle("comfy-panel-open", openState);
|
||||
customBtn.style.display = "inline-flex";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,6 +803,10 @@ export function syncClassicLayout() {
|
||||
syncPropertiesButton(isClassicLayoutEnabled);
|
||||
syncActiveQueueIndicator(isClassicLayoutEnabled);
|
||||
updateSidebarTabsVisibility();
|
||||
applySidebarOverride();
|
||||
if (!sidebarTopObserver) {
|
||||
setupSidebarTopObserver();
|
||||
}
|
||||
syncRuntimePreviewButton();
|
||||
|
||||
} catch (err) {
|
||||
@@ -796,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");
|
||||
@@ -812,15 +841,15 @@ export function setupPropertiesPanelToggleFix() {
|
||||
order: 99999 !important;
|
||||
position: relative !important;
|
||||
overflow: visible !important;
|
||||
margin-left: -8px !important;
|
||||
margin-left: 0px !important;
|
||||
margin-right: 0px !important;
|
||||
display: inline-flex !important;
|
||||
display: none !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.comfy-sidebar-custom-properties-toggle.comfy-panel-open {
|
||||
margin-left: 0px !important;
|
||||
display: inline-flex !important;
|
||||
}
|
||||
.comfy-sidebar-custom-properties-toggle > span.comfy-sidebar-error-dot {
|
||||
display: inline-flex !important;
|
||||
@@ -1087,6 +1116,10 @@ export function destroyLayoutFix() {
|
||||
domObserver.disconnect();
|
||||
domObserver = null;
|
||||
}
|
||||
if (sidebarTopObserver) {
|
||||
sidebarTopObserver.disconnect();
|
||||
sidebarTopObserver = null;
|
||||
}
|
||||
if (actionbarResizeObserver) {
|
||||
actionbarResizeObserver.disconnect();
|
||||
actionbarResizeObserver = null;
|
||||
|
||||
+10
-2
@@ -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() {
|
||||
|
||||
+130
-72
@@ -56,6 +56,106 @@ export function showNotification(message, type = "success", title = null) {
|
||||
}, 3500);
|
||||
}
|
||||
|
||||
function remapWidgetValue(val, oldToNew) {
|
||||
if (typeof val === "string") {
|
||||
const match = val.match(/^\$([^-]+)-(.*)$/);
|
||||
if (match) {
|
||||
const mapped = oldToNew.get(match[1]) ?? oldToNew.get(Number(match[1]));
|
||||
if (mapped != null) {
|
||||
return `$${mapped}-${match[2]}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
function applyIdMappingToGraph(oldToNew) {
|
||||
if (!app.graph) return;
|
||||
const graphData = app.graph.serialize();
|
||||
if (!graphData) return;
|
||||
|
||||
// 1. Remap node IDs and internal widget references
|
||||
if (Array.isArray(graphData.nodes)) {
|
||||
for (const node of graphData.nodes) {
|
||||
const oldId = node.id;
|
||||
const newId = oldToNew.get(oldId) ?? oldToNew.get(String(oldId)) ?? oldToNew.get(Number(oldId));
|
||||
if (newId != null) {
|
||||
node.id = newId;
|
||||
}
|
||||
if (Array.isArray(node.widgets_values)) {
|
||||
node.widgets_values = node.widgets_values.map(val => remapWidgetValue(val, oldToNew));
|
||||
} else if (node.widgets_values && typeof node.widgets_values === "object") {
|
||||
for (const key of Object.keys(node.widgets_values)) {
|
||||
node.widgets_values[key] = remapWidgetValue(node.widgets_values[key], oldToNew);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Remap link endpoints (both array tuples and object representations)
|
||||
const linkList = Array.isArray(graphData.links)
|
||||
? graphData.links
|
||||
: (graphData.links && typeof graphData.links === "object" ? Object.values(graphData.links) : []);
|
||||
|
||||
for (const link of linkList) {
|
||||
if (!link) continue;
|
||||
if (Array.isArray(link)) {
|
||||
// [id, origin_id, origin_slot, target_id, target_slot, type]
|
||||
const originId = link[1];
|
||||
const targetId = link[3];
|
||||
const newOrigin = oldToNew.get(originId) ?? oldToNew.get(String(originId)) ?? oldToNew.get(Number(originId));
|
||||
if (newOrigin != null) link[1] = newOrigin;
|
||||
const newTarget = oldToNew.get(targetId) ?? oldToNew.get(String(targetId)) ?? oldToNew.get(Number(targetId));
|
||||
if (newTarget != null) link[3] = newTarget;
|
||||
} else if (typeof link === "object") {
|
||||
const originId = link.origin_id;
|
||||
const targetId = link.target_id;
|
||||
const newOrigin = oldToNew.get(originId) ?? oldToNew.get(String(originId)) ?? oldToNew.get(Number(originId));
|
||||
if (newOrigin != null) link.origin_id = newOrigin;
|
||||
const newTarget = oldToNew.get(targetId) ?? oldToNew.get(String(targetId)) ?? oldToNew.get(Number(targetId));
|
||||
if (newTarget != null) link.target_id = newTarget;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Remap group membership if defined
|
||||
if (Array.isArray(graphData.groups)) {
|
||||
for (const group of graphData.groups) {
|
||||
if (Array.isArray(group.nodes)) {
|
||||
group.nodes = group.nodes.map(id => oldToNew.get(id) ?? oldToNew.get(String(id)) ?? oldToNew.get(Number(id)) ?? id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Update graph last_node_id counter
|
||||
let maxId = 0;
|
||||
for (const val of oldToNew.values()) {
|
||||
const num = Number(val);
|
||||
if (!isNaN(num) && num > maxId) maxId = num;
|
||||
}
|
||||
graphData.last_node_id = Math.max(graphData.last_node_id || 0, maxId, graphData.nodes?.length || 0);
|
||||
|
||||
// 5. Preserve canvas pan/zoom coordinates so the screen doesn't jump
|
||||
const currentScale = app.canvas?.ds?.scale;
|
||||
const currentOffset = app.canvas?.ds?.offset ? [...app.canvas.ds.offset] : null;
|
||||
|
||||
app.graph.configure(graphData);
|
||||
|
||||
if (app.canvas?.ds && currentScale != null && currentOffset != null) {
|
||||
app.canvas.ds.scale = currentScale;
|
||||
app.canvas.ds.offset[0] = currentOffset[0];
|
||||
app.canvas.ds.offset[1] = currentOffset[1];
|
||||
}
|
||||
|
||||
if (typeof app.graph.afterChange === "function") {
|
||||
app.graph.afterChange();
|
||||
}
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
if (app.canvas?.draw) {
|
||||
app.canvas.draw(true, true);
|
||||
}
|
||||
syncAllNodeBadges();
|
||||
}
|
||||
|
||||
export async function renumberNodesTopologically() {
|
||||
if (!app.graph || !app.graph._nodes || app.graph._nodes.length === 0) {
|
||||
showNotification(t("No nodes found on canvas to renumber."), "warn");
|
||||
@@ -88,55 +188,21 @@ export async function renumberNodesTopologically() {
|
||||
return posYA - posYB;
|
||||
});
|
||||
|
||||
const isStringId = typeof sortedNodes[0]?.id === "string";
|
||||
const oldToNew = new Map();
|
||||
sortedNodes.forEach((node, index) => {
|
||||
const newId = index + 1;
|
||||
const newId = isStringId ? String(index + 1) : (index + 1);
|
||||
oldToNew.set(node.id, newId);
|
||||
oldToNew.set(String(node.id), newId);
|
||||
oldToNew.set(Number(node.id), newId);
|
||||
});
|
||||
|
||||
// 1. Remap node IDs in-place on app.graph
|
||||
app.graph._nodes_by_id = {};
|
||||
sortedNodes.forEach(node => {
|
||||
const oldId = node.id;
|
||||
const newId = oldToNew.get(oldId);
|
||||
if (newId != null) {
|
||||
// Update internal widget IDs like "$2034-0" (PreviewBridge)
|
||||
if (Array.isArray(node.widgets)) {
|
||||
for (const w of node.widgets) {
|
||||
if (typeof w.value === "string" && w.value.startsWith(`$${oldId}-`)) {
|
||||
w.value = w.value.replace(`$${oldId}-`, `$${newId}-`);
|
||||
}
|
||||
}
|
||||
}
|
||||
node.id = newId;
|
||||
app.graph._nodes_by_id[newId] = node;
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Remap links
|
||||
if (app.graph.links) {
|
||||
const links = app.graph.links instanceof Map
|
||||
? Array.from(app.graph.links.values())
|
||||
: Object.values(app.graph.links);
|
||||
|
||||
links.forEach(link => {
|
||||
if (!link) return;
|
||||
if (oldToNew.has(link.origin_id)) link.origin_id = oldToNew.get(link.origin_id);
|
||||
if (oldToNew.has(link.target_id)) link.target_id = oldToNew.get(link.target_id);
|
||||
});
|
||||
}
|
||||
|
||||
app.graph.last_node_id = sortedNodes.length;
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
if (app.canvas?.draw) {
|
||||
app.canvas.draw(true, true);
|
||||
}
|
||||
syncAllNodeBadges();
|
||||
applyIdMappingToGraph(oldToNew);
|
||||
|
||||
const lastNode = sortedNodes[sortedNodes.length - 1];
|
||||
const lastNodeName = lastNode ? (lastNode.title || lastNode.type) : "SaveImage";
|
||||
showNotification(t("Renumbered {count} nodes by execution order. Latest node: #{id} ({name}).", { count: sortedNodes.length, id: lastNode ? lastNode.id : "", name: lastNodeName }), "success");
|
||||
const lastNewId = lastNode ? oldToNew.get(lastNode.id) : "";
|
||||
showNotification(t("Renumbered {count} nodes by execution order. Latest node: #{id} ({name}).", { count: sortedNodes.length, id: lastNewId, name: lastNodeName }), "success");
|
||||
} catch (err) {
|
||||
console.error("ComfySidebar: Failed to renumber nodes", err);
|
||||
showNotification(t("Failed to renumber nodes: ") + err.message, "error");
|
||||
@@ -159,55 +225,47 @@ export async function setManualNodeId(targetNode = null) {
|
||||
const input = prompt(t("Enter new ID for node #{id} ({name}):", { id: currentId, name: node.title || node.type }), String(currentId));
|
||||
if (!input) return;
|
||||
|
||||
const newId = parseInt(input.trim(), 10);
|
||||
if (isNaN(newId) || newId <= 0) {
|
||||
const newIdNum = parseInt(input.trim(), 10);
|
||||
if (isNaN(newIdNum) || newIdNum <= 0) {
|
||||
showNotification(t("Please enter a valid positive number."), "error");
|
||||
return;
|
||||
}
|
||||
if (newId === currentId) return;
|
||||
if (String(newIdNum) === String(currentId)) return;
|
||||
|
||||
try {
|
||||
const existingNode = app.graph.getNodeById ? app.graph.getNodeById(newId) : null;
|
||||
const existingNode = app.graph.getNodeById ? (app.graph.getNodeById(newIdNum) || app.graph.getNodeById(String(newIdNum))) : null;
|
||||
if (existingNode && existingNode !== node) {
|
||||
const confirmSwap = confirm(t("Node ID #{id} is already used by \"{name}\". Swap their IDs?", { id: newId, name: existingNode.title || existingNode.type }));
|
||||
const confirmSwap = confirm(t("Node ID #{id} is already used by \"{name}\". Swap their IDs?", { id: newIdNum, name: existingNode.title || existingNode.type }));
|
||||
if (!confirmSwap) return;
|
||||
}
|
||||
|
||||
const isStringId = typeof currentId === "string";
|
||||
const newId = isStringId ? String(newIdNum) : newIdNum;
|
||||
const oldId = currentId;
|
||||
|
||||
const oldToNew = new Map();
|
||||
if (existingNode && existingNode !== node) {
|
||||
oldToNew.set(currentId, newId);
|
||||
oldToNew.set(newId, currentId);
|
||||
existingNode.id = currentId;
|
||||
const existingId = existingNode.id;
|
||||
oldToNew.set(oldId, newId);
|
||||
oldToNew.set(String(oldId), newId);
|
||||
oldToNew.set(Number(oldId), newId);
|
||||
|
||||
oldToNew.set(existingId, oldId);
|
||||
oldToNew.set(String(existingId), oldId);
|
||||
oldToNew.set(Number(existingId), oldId);
|
||||
} else {
|
||||
oldToNew.set(currentId, newId);
|
||||
}
|
||||
node.id = newId;
|
||||
|
||||
// Rebuild lookup map
|
||||
app.graph._nodes_by_id = {};
|
||||
for (const n of app.graph._nodes) {
|
||||
app.graph._nodes_by_id[n.id] = n;
|
||||
oldToNew.set(oldId, newId);
|
||||
oldToNew.set(String(oldId), newId);
|
||||
oldToNew.set(Number(oldId), newId);
|
||||
}
|
||||
|
||||
// Remap links
|
||||
if (app.graph.links) {
|
||||
const links = app.graph.links instanceof Map
|
||||
? Array.from(app.graph.links.values())
|
||||
: Object.values(app.graph.links);
|
||||
applyIdMappingToGraph(oldToNew);
|
||||
|
||||
links.forEach(link => {
|
||||
if (!link) return;
|
||||
if (oldToNew.has(link.origin_id)) link.origin_id = oldToNew.get(link.origin_id);
|
||||
if (oldToNew.has(link.target_id)) link.target_id = oldToNew.get(link.target_id);
|
||||
});
|
||||
const updatedNode = app.graph.getNodeById ? (app.graph.getNodeById(newId) || app.graph.getNodeById(String(newId))) : null;
|
||||
if (updatedNode && app.canvas?.selectNode) {
|
||||
app.canvas.selectNode(updatedNode);
|
||||
}
|
||||
|
||||
app.graph.last_node_id = Math.max(app.graph.last_node_id || 0, newId, app.graph._nodes.length);
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
if (app.canvas?.draw) {
|
||||
app.canvas.draw(true, true);
|
||||
}
|
||||
syncAllNodeBadges();
|
||||
showNotification(t("Assigned node ID #{id} to \"{name}\".", { id: newId, name: node.title || node.type }), "success");
|
||||
} catch (err) {
|
||||
console.error("ComfySidebar: Failed to set node ID", err);
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user