Compare commits

..
3 Commits
5 changed files with 340 additions and 139 deletions
+127 -25
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",