From 1abf542a58c13856d1f670dfa6d2cfeaffab0b4b Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sun, 19 Apr 2026 18:06:21 +0200 Subject: [PATCH] Add node locate/focus feature to linker Introduce locateNodeInGraph in LinkerManagerDialog to center, select, and highlight nodes in the ComfyUI canvas with compatibility fallbacks for different app versions and error notifications. Add a 'Locate' button to node headers (top-level nodes only) and wire its click handler so the UI will focus the corresponding node. Improves UX for finding and inspecting missing or referenced nodes. --- web/linker.js | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/web/linker.js b/web/linker.js index 7c056f7..99c40bf 100644 --- a/web/linker.js +++ b/web/linker.js @@ -1788,6 +1788,58 @@ class LinkerManagerDialog extends ComfyDialog { return null; } + /** + * Locate and focus a node in the ComfyUI canvas + */ + locateNodeInGraph(nodeId) { + try { + if (!app?.graph) { + this.showNotification('Cannot locate node - graph not available', 'error'); + return; + } + + // Find the node in the graph + const node = app.graph.getNodeById(nodeId); + if (!node) { + this.showNotification(`Node #${nodeId} not found in graph`, 'error'); + return; + } + + // Focus on the node in the canvas + if (app.canvas && typeof app.canvas.centerOnNode === 'function') { + // Modern ComfyUI versions + app.canvas.centerOnNode(node); + } else if (app.graph._nodes && app.graph._nodes.get(nodeId)) { + // Alternative method for older versions + const canvasNode = app.graph._nodes.get(nodeId); + if (canvasNode && canvasNode.setSelected && canvasNode.graph) { + canvasNode.setSelected(true); + // Scroll to node + app.canvas.scrollToNode(canvasNode); + } + } else if (app.ui && app.ui.nodeGraph && typeof app.ui.nodeGraph.scrollToNode === 'function') { + // Alternative for other versions + app.ui.nodeGraph.scrollToNode(node); + } + + // Also try to flash/select the node + // Deselect all nodes first + if (app.graph && app.graph.nodes) { + app.graph.nodes.forEach(n => { + if (n.selected) n.selected = false; + }); + } + + // Select and highlight our node + node.selected = true; + + this.showNotification(`Focused on Node #${nodeId} (${node.type})`, 'info'); + } catch (e) { + console.error('Model Linker: Error locating node:', e); + this.showNotification('Error locating node: ' + e.message, 'error'); + } + } + /** * Reconnect active downloads to their new progress div elements after UI refresh */ @@ -1993,6 +2045,15 @@ class LinkerManagerDialog extends ComfyDialog { }); } + // Wire Locate button (only for top-level nodes) + const locateId = `locate-${missing.node_id}-${missing.widget_index}`; + const locateBtn = container.querySelector(`#${locateId}`); + if (locateBtn && missing.is_top_level !== false) { + locateBtn.addEventListener('click', () => { + this.locateNodeInGraph(missing.node_id); + }); + } + // Wire up all-models search + dropdown const allSearchId = `search-all-${missing.node_id}-${missing.widget_index}`; const selectAllId = `select-all-${missing.node_id}-${missing.widget_index}`; @@ -2110,6 +2171,11 @@ class LinkerManagerDialog extends ComfyDialog { html += `${missing.category}`; } html += `${nodeLabel} #${missing.node_id}`; + // Add Locate button for top-level nodes only + if (missing.is_top_level !== false) { + const locateId = `locate-${missing.node_id}-${missing.widget_index}`; + html += ``; + } html += ``; // Removed - filename is already shown in title html += ``;