Files
alFrame-ComfyUI-AF-Find-Nodes/AF_Find_Node_by_ID.js
T
Alex Furer 27be4bde3f Update AF_Find_Node_by_ID.js
First version which could be released
2025-09-10 14:09:53 +02:00

550 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ****** ComfyUI-AF-Find Node by ID ******
//
// Creator: Alex Furer - Co-Creator(s): Claude AI & QWEN3 Coder
//
// Praise, comment, bugs, improvements: https://github.com/alFrame/ComfyUI_AF_FindNodeByID
//
// LICENSE: MIT License
//
// v0.0.03
//
// Description:
// A ComfyUI extension that allows you to search for and locate nodes by their ID in complex workflows.
//
// Usage:
// Read Me on Github
//
// Changelog:
// v0.0.01
// - Initial Version
//
// v0.0.02
// - Fixed double initialization error
// - Removed right-click on canvas to trigger the dialog
// - Fixed zooming in to node
//
// v0.0.03
// - Clear button now also clears the search field
// - Dialog content cleared when closed or opened
// - Error messages in red
// - Fixed dialog width to 340px
//
// Feature Requests / Wet Dreams
// -
import { app } from "/scripts/app.js";
import { api } from "/scripts/api.js";
// Prevent double initialization
if (window.AF_Find_Node_by_ID_Widget) {
console.log("AF - Find Node by ID already loaded, skipping initialization");
} else {
class AF_Find_Node_by_ID_Widget {
constructor() {
this.searchPanel = null;
this.isVisible = false;
this.highlightedNode = null;
this.originalNodeColors = new Map();
this.searchHistory = [];
this.maxHistory = 10;
this.inspectorMode = false;
}
createSearchPanel() {
// Create main container
const panel = document.createElement('div');
panel.id = 'af-find-node-by-id-panel';
panel.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
background: #2a2a2a;
border: 1px solid #555;
border-radius: 8px;
padding: 15px;
z-index: 10000;
font-family: monospace;
font-size: 12px;
color: #fff;
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
width: 340px; /* Fixed width */
display: none;
`;
// Title bar
const titleBar = document.createElement('div');
titleBar.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
border-bottom: 1px solid #555;
padding-bottom: 8px;
`;
const title = document.createElement('div');
title.textContent = 'AF - Find Node by ID';
title.style.fontWeight = 'bold';
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.cssText = `
background: #ff4444;
border: none;
color: white;
width: 20px;
height: 20px;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
line-height: 1;
`;
closeBtn.onclick = () => this.AF_Find_Node_by_ID_HidePanel();
titleBar.appendChild(title);
titleBar.appendChild(closeBtn);
// Search input section
const searchSection = document.createElement('div');
searchSection.style.marginBottom = '10px';
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Enter node ID (e.g., 42)';
searchInput.id = 'af-find-node-by-id-input';
searchInput.style.cssText = `
width: 100%;
padding: 6px;
margin-bottom: 8px;
background: #1a1a1a;
border: 1px solid #555;
border-radius: 4px;
color: #fff;
font-family: monospace;
`;
const buttonRow = document.createElement('div');
buttonRow.style.cssText = `
display: flex;
gap: 8px;
margin-bottom: 10px;
`;
const searchBtn = document.createElement('button');
searchBtn.textContent = 'Find';
searchBtn.style.cssText = `
flex: 1;
padding: 6px;
background: #4CAF50;
border: none;
border-radius: 4px;
color: white;
cursor: pointer;
font-size: 11px;
`;
const clearBtn = document.createElement('button');
clearBtn.textContent = 'Clear';
clearBtn.style.cssText = `
flex: 1;
padding: 6px;
background: #666;
border: none;
border-radius: 4px;
color: white;
cursor: pointer;
font-size: 11px;
`;
const inspectorBtn = document.createElement('button');
inspectorBtn.textContent = 'Inspector';
inspectorBtn.id = 'af-find-node-by-id-inspector-toggle';
inspectorBtn.style.cssText = `
flex: 1;
padding: 6px;
background: #2196F3;
border: none;
border-radius: 4px;
color: white;
cursor: pointer;
font-size: 11px;
`;
// Results section
const resultsSection = document.createElement('div');
resultsSection.id = 'af-find-node-by-id-results';
resultsSection.style.cssText = `
max-height: 200px;
overflow-y: auto;
background: #1a1a1a;
border: 1px solid #555;
border-radius: 4px;
padding: 8px;
margin-bottom: 10px;
`;
// History section
const historySection = document.createElement('div');
historySection.style.cssText = `
border-top: 1px solid #555;
padding-top: 8px;
`;
const historyTitle = document.createElement('div');
historyTitle.textContent = 'Recent Searches:';
historyTitle.style.cssText = `
font-size: 10px;
color: #aaa;
margin-bottom: 5px;
`;
const historyList = document.createElement('div');
historyList.id = 'af-find-node-by-id-history';
historyList.style.cssText = `
display: flex;
flex-wrap: wrap;
gap: 4px;
`;
// Event listeners
searchBtn.onclick = () => this.AF_Find_Node_by_ID_Search();
clearBtn.onclick = () => this.AF_Find_Node_by_ID_ClearAll();
inspectorBtn.onclick = () => this.AF_Find_Node_by_ID_ToggleInspector();
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
this.AF_Find_Node_by_ID_Search();
}
});
// Assemble the panel
buttonRow.appendChild(searchBtn);
buttonRow.appendChild(clearBtn);
buttonRow.appendChild(inspectorBtn);
searchSection.appendChild(searchInput);
searchSection.appendChild(buttonRow);
historySection.appendChild(historyTitle);
historySection.appendChild(historyList);
panel.appendChild(titleBar);
panel.appendChild(searchSection);
panel.appendChild(resultsSection);
panel.appendChild(historySection);
document.body.appendChild(panel);
this.searchPanel = panel;
// Update results initially
this.AF_Find_Node_by_ID_UpdateResults('Ready to search. Enter a node ID or use Inspector mode.');
}
AF_Find_Node_by_ID_ShowPanel() {
if (!this.searchPanel) {
this.createSearchPanel();
}
this.searchPanel.style.display = 'block';
this.isVisible = true;
// Clear the search field when dialog is shown
document.getElementById('af-find-node-by-id-input').value = '';
document.getElementById('af-find-node-by-id-input').focus();
}
AF_Find_Node_by_ID_HidePanel() {
if (this.searchPanel) {
this.searchPanel.style.display = 'none';
}
this.isVisible = false;
this.AF_Find_Node_by_ID_ClearAll();
this.AF_Find_Node_by_ID_SetInspectorMode(false);
}
AF_Find_Node_by_ID_TogglePanel() {
if (this.isVisible) {
this.AF_Find_Node_by_ID_HidePanel();
} else {
this.AF_Find_Node_by_ID_ShowPanel();
}
}
AF_Find_Node_by_ID_Search() {
const input = document.getElementById('af-find-node-by-id-input');
const searchId = input.value.trim();
if (!searchId) {
this.AF_Find_Node_by_ID_UpdateResults('Please enter a node ID', true);
return;
}
const nodeId = parseInt(searchId);
if (isNaN(nodeId)) {
this.AF_Find_Node_by_ID_UpdateResults('Invalid node ID. Please enter a number.', true);
return;
}
const node = this.AF_Find_Node_by_ID_FindNodeById(nodeId);
if (node) {
this.AF_Find_Node_by_ID_HighlightNode(node);
this.AF_Find_Node_by_ID_CenterOnNode(node);
this.AF_Find_Node_by_ID_AddToHistory(nodeId);
this.AF_Find_Node_by_ID_UpdateResults(`Found node ${nodeId}: ${node.type || 'Unknown Type'}`);
} else {
this.AF_Find_Node_by_ID_UpdateResults(`Node ${nodeId} not found in current workflow`, true);
}
}
AF_Find_Node_by_ID_FindNodeById(nodeId) {
if (!app.graph || !app.graph.nodes) return null;
return app.graph.nodes.find(node => node.id === nodeId);
}
AF_Find_Node_by_ID_HighlightNode(node) {
this.AF_Find_Node_by_ID_ClearHighlight();
// Store original color
this.originalNodeColors.set(node.id, {
color: node.color,
bgcolor: node.bgcolor
});
// Apply highlight
node.color = '#ff9600';
node.bgcolor = '#ff960033';
this.highlightedNode = node;
app.graph.setDirtyCanvas(true, true);
}
AF_Find_Node_by_ID_ClearHighlight() {
if (this.highlightedNode) {
const original = this.originalNodeColors.get(this.highlightedNode.id);
if (original) {
this.highlightedNode.color = original.color;
this.highlightedNode.bgcolor = original.bgcolor;
}
this.originalNodeColors.delete(this.highlightedNode.id);
this.highlightedNode = null;
app.graph.setDirtyCanvas(true, true);
}
}
AF_Find_Node_by_ID_ClearAll() {
// Clear the search field
const input = document.getElementById('af-find-node-by-id-input');
if (input) {
input.value = '';
}
// Clear highlight
this.AF_Find_Node_by_ID_ClearHighlight();
// Update results
this.AF_Find_Node_by_ID_UpdateResults('Search field cleared.');
}
AF_Find_Node_by_ID_CenterOnNode(node) {
if (!app.canvas || !node) return;
try {
// Clear current selection
app.canvas.deselectAllNodes();
// Select the target node
node.is_selected = true;
app.canvas.selectNode(node);
// Reset zoom to a reasonable level for viewing
const targetScale = 1.0; // or 1.2 for a bit closer
app.canvas.ds.scale = targetScale;
// Center on the node
const canvas = app.canvas;
const nodeWidth = node.size[0];
const nodeHeight = node.size[1];
const nodeCenterX = node.pos[0] + (nodeWidth / 2);
const nodeCenterY = node.pos[1] + (nodeHeight / 2);
canvas.ds.offset[0] = (canvas.canvas.width / 2) - (nodeCenterX * canvas.ds.scale);
canvas.ds.offset[1] = (canvas.canvas.height / 2) - (nodeCenterY * canvas.ds.scale);
app.graph.setDirtyCanvas(true, true);
} catch (error) {
console.warn("AF_Find_Node_by_ID: Could not center on node, using fallback", error);
// Simple fallback
const canvas = app.canvas;
canvas.ds.offset[0] = -node.pos[0] * canvas.ds.scale;
canvas.ds.offset[1] = -node.pos[1] * canvas.ds.scale;
canvas.setDirty(true, true);
}
}
AF_Find_Node_by_ID_ToggleInspector() {
this.AF_Find_Node_by_ID_SetInspectorMode(!this.inspectorMode);
// Fix width when toggling inspector
if (this.searchPanel) {
this.searchPanel.style.width = '340px';
}
}
AF_Find_Node_by_ID_SetInspectorMode(enabled) {
this.inspectorMode = enabled;
const btn = document.getElementById('af-find-node-by-id-inspector-toggle');
if (btn) {
btn.style.background = enabled ? '#ff9800' : '#2196F3';
btn.textContent = enabled ? 'Exit Inspector' : 'Inspector';
}
if (enabled) {
this.AF_Find_Node_by_ID_UpdateResults('Inspector mode active. Click any node to see its ID.');
} else {
this.AF_Find_Node_by_ID_UpdateResults('Inspector mode disabled.');
}
}
AF_Find_Node_by_ID_HandleNodeClick(node) {
if (this.inspectorMode && node) {
const input = document.getElementById('af-find-node-by-id-input');
if (input) {
input.value = node.id.toString();
}
this.AF_Find_Node_by_ID_HighlightNode(node);
this.AF_Find_Node_by_ID_AddToHistory(node.id);
this.AF_Find_Node_by_ID_UpdateResults(`Selected node ${node.id}: ${node.type || 'Unknown Type'}`);
}
}
AF_Find_Node_by_ID_AddToHistory(nodeId) {
// Remove if already exists
this.searchHistory = this.searchHistory.filter(id => id !== nodeId);
// Add to front
this.searchHistory.unshift(nodeId);
// Limit size
if (this.searchHistory.length > this.maxHistory) {
this.searchHistory = this.searchHistory.slice(0, this.maxHistory);
}
this.AF_Find_Node_by_ID_UpdateHistory();
}
AF_Find_Node_by_ID_UpdateHistory() {
const historyContainer = document.getElementById('af-find-node-by-id-history');
if (!historyContainer) return;
historyContainer.innerHTML = '';
this.searchHistory.forEach(nodeId => {
const historyBtn = document.createElement('button');
historyBtn.textContent = nodeId.toString();
historyBtn.style.cssText = `
background: #444;
border: none;
color: #fff;
padding: 2px 6px;
border-radius: 3px;
cursor: pointer;
font-size: 10px;
`;
historyBtn.onclick = () => {
document.getElementById('af-find-node-by-id-input').value = nodeId.toString();
this.AF_Find_Node_by_ID_Search();
};
historyContainer.appendChild(historyBtn);
});
}
AF_Find_Node_by_ID_UpdateResults(message, isError = false) {
const results = document.getElementById('af-find-node-by-id-results');
if (results) {
results.textContent = message;
results.style.color = isError ? '#ff6b6b' : '#fff'; // Red for errors, white otherwise
}
}
// Keyboard shortcut handler
AF_Find_Node_by_ID_HandleKeyboard(event) {
// Ctrl+Shift+F to toggle search panel
if (event.ctrlKey && event.shiftKey && event.code === 'KeyF') {
event.preventDefault();
this.AF_Find_Node_by_ID_TogglePanel();
}
// Escape to close panel
if (event.code === 'Escape' && this.isVisible) {
event.preventDefault();
this.AF_Find_Node_by_ID_HidePanel();
}
}
}
// Initialize the search widget with guard against double initialization
window.AF_Find_Node_by_ID_Widget = new AF_Find_Node_by_ID_Widget();
// Register the extension
app.registerExtension({
name: "AF-Find-Node-by-ID",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// We don't need to modify node definitions
},
async setup() {
// Add keyboard event listeners
document.addEventListener('keydown', (e) => {
window.AF_Find_Node_by_ID_Widget.AF_Find_Node_by_ID_HandleKeyboard(e);
});
// Hook into node selection changes for inspector mode
let lastSelectedNodes = [];
// Monitor for node selection changes
const checkNodeSelection = () => {
if (!window.AF_Find_Node_by_ID_Widget.inspectorMode) return;
const currentSelected = app.canvas.selected_nodes;
if (!currentSelected || Object.keys(currentSelected).length === 0) return;
// Get the first selected node
const nodeId = Object.keys(currentSelected)[0];
const node = currentSelected[nodeId];
if (node && !lastSelectedNodes.includes(node.id)) {
window.AF_Find_Node_by_ID_Widget.AF_Find_Node_by_ID_HandleNodeClick(node);
lastSelectedNodes = [node.id];
}
};
// Check selection periodically when inspector is active
setInterval(() => {
if (window.AF_Find_Node_by_ID_Widget.inspectorMode) {
checkNodeSelection();
} else {
lastSelectedNodes = [];
}
}, 100);
// Also try hooking into the canvas click event
const canvas = app.canvas.canvas;
canvas.addEventListener('click', (e) => {
if (!window.AF_Find_Node_by_ID_Widget.inspectorMode) return;
// Get canvas coordinates
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Convert to canvas coordinates
const canvasX = (x - app.canvas.ds.offset[0]) / app.canvas.ds.scale;
const canvasY = (y - app.canvas.ds.offset[1]) / app.canvas.ds.scale;
// Find node at position
const node = app.graph.getNodeOnPos(canvasX, canvasY);
if (node) {
window.AF_Find_Node_by_ID_Widget.AF_Find_Node_by_ID_HandleNodeClick(node);
e.stopPropagation();
}
}, true);
console.log("AF - Find Node by ID extension loaded. Use Ctrl+Shift+F to open search panel.");
}
});
} // End of guard against double initialization