Files
CoreyCorza-ComfyUI-CRZnodes/js/Passthrough.js
T
2025-09-05 22:43:44 +10:00

413 lines
20 KiB
JavaScript

// ComfyUI - CRZ Passthrough JS Extension
import { app } from "../../scripts/app.js";
app.registerExtension({
name: "CRZ.Passthrough",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "CRZPassthrough") {
// Set node properties for minimal visibility
nodeType.title_mode = LiteGraph.NO_TITLE;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
this.bgcolor = "rgba(24, 24, 24, 0.281)";
this.size = [80, 28];
this.resizable = true;
// Hide input/output labels
if (this.inputs && this.inputs[0]) {
this.inputs[0].localized_name = " ";
this.inputs[0].color_on = "rgba(135, 56, 199, 1)";
this.inputs[0].color_off = "rgba(51, 51, 51, 1)";
}
if (this.outputs && this.outputs[0]) {
this.outputs[0].localized_name = " ";
this.outputs[0].color_on = "rgba(135, 56, 199, 1)";
this.outputs[0].color_off = "rgba(51, 51, 51, 1)";
}
// Mark this as a passthrough node for identification
this.isPassthrough = true;
this.isHovered = false;
// Load preferences
this.loadPreferences();
// Double-click handler for title configuration
this.onDblClick = function(e, pos, canvas) {
const currentTitle = this.title || "CRZ Passthrough";
canvas.prompt(
"Passthrough Title",
currentTitle === "CRZ Passthrough" ? "" : currentTitle,
function(newTitle) {
if (newTitle !== null) {
if (newTitle.trim() === "") {
// Reset to default if empty
this.title = "CRZ Passthrough";
} else {
this.title = newTitle.trim();
}
// Force redraw to show new title
if (this.graph && this.graph.setDirtyCanvas) {
this.graph.setDirtyCanvas(true);
}
}
}.bind(this),
e
);
return true;
};
// Help dynamic nodes find their final targets through the passthrough
this.getPassthroughTarget = function() {
// Find what this passthrough outputs to
if (this.outputs && this.outputs[0] && this.outputs[0].links) {
const linkId = this.outputs[0].links[0];
const link = this.graph.links[linkId];
if (link) {
const targetNode = this.graph.getNodeById(link.target_id);
// If target is also a passthrough, recurse
if (targetNode && targetNode.isPassthrough && targetNode.getPassthroughTarget) {
return targetNode.getPassthroughTarget();
}
return {
node: targetNode,
input: targetNode?.inputs?.[link.target_slot]
};
}
}
return null;
};
return r;
};
// Add proper initialization handling
const originalOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function(info) {
if (originalOnConfigure) {
originalOnConfigure.call(this, info);
}
// Force a property change event to ensure proper initialization
this.setDirtyCanvas(true, true);
};
// Notify connected nodes when our connections change
const originalOnConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function(type, slotIndex, isConnected, linkInfo, ioSlot) {
if (originalOnConnectionsChange) {
originalOnConnectionsChange.call(this, type, slotIndex, isConnected, linkInfo, ioSlot);
}
// When our output connection changes, notify any upstream dropdowns
if (type === LiteGraph.OUTPUT && this.inputs && this.inputs[0] && this.inputs[0].link) {
const inputLinkId = this.inputs[0].link;
const inputLink = this.graph.links[inputLinkId];
if (inputLink) {
const sourceNode = this.graph.getNodeById(inputLink.origin_id);
if (sourceNode && sourceNode.updateComboOptions) {
setTimeout(() => {
sourceNode.updateComboOptions();
}, 100);
}
}
}
};
const originalOnGraphConfigured = nodeType.prototype.onGraphConfigured;
nodeType.prototype.onGraphConfigured = function() {
if (originalOnGraphConfigured) {
originalOnGraphConfigured.call(this);
}
// Ensure the node is marked as configured
this.configured = true;
// Force canvas update
this.setDirtyCanvas(true, true);
};
// Add mouse hover detection for passthrough nodes
const originalOnMouseEnter = nodeType.prototype.onMouseEnter;
nodeType.prototype.onMouseEnter = function(e) {
if (originalOnMouseEnter) {
originalOnMouseEnter.call(this, e);
}
this.isHovered = true;
// --- BEGIN: CRZ Passthrough hover logic for show all links ---
if (this.isPassthrough) {
window.CRZ_passthrough_hovered = true;
window.CRZ_passthrough_show_all_links = window.CRZ_passthrough_hovered && (window.CRZ_ctrl_held || (e && e.ctrlKey));
if (this.graph && this.graph.canvas) {
this.graph.canvas.setDirty(true, true);
}
}
// --- END: CRZ Passthrough hover logic ---
// Force canvas redraw to show connections
if (this.graph && this.graph.canvas) {
this.graph.canvas.setDirty(true, true);
}
};
const originalOnMouseLeave = nodeType.prototype.onMouseLeave;
nodeType.prototype.onMouseLeave = function(e) {
if (originalOnMouseLeave) {
originalOnMouseLeave.call(this, e);
}
this.isHovered = false;
// --- BEGIN: CRZ Passthrough hover logic for show all links ---
if (this.isPassthrough) {
window.CRZ_passthrough_hovered = false;
window.CRZ_passthrough_show_all_links = false;
if (this.graph && this.graph.canvas) {
this.graph.canvas.setDirty(true, true);
}
}
// --- END: CRZ Passthrough hover logic ---
// Force canvas redraw to hide connections again
if (this.graph && this.graph.canvas) {
this.graph.canvas.setDirty(true, true);
}
};
// Override the onDrawBackground to prevent any node rendering
nodeType.prototype.onDrawBackground = function(ctx) {
// Don't draw anything - completely invisible node
};
// Override onDrawForeground to display custom title when changed
nodeType.prototype.onDrawForeground = function(ctx) {
// Only draw custom title if it's been changed from default
if (this.title && this.title !== "CRZ Passthrough") {
ctx.fillStyle = "rgba(192, 192, 192, 0.829)";
ctx.font = "12px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
// Draw title above the node
const titleY = -5; // Position above the node
ctx.fillText(this.title, this.size[0] / 2, titleY);
}
};
// Override computeSize to keep it minimal
nodeType.prototype.computeSize = function() {
return [80, 28];
};
// Load preferences for passthrough behavior
nodeType.prototype.loadPreferences = function() {
const prefs = JSON.parse(localStorage.getItem("crz_preferences") || "{}");
this.alwaysShowConnections = prefs.always_show_passthrough === true;
};
}
},
// Hook into the canvas rendering to hide connections to/from passthrough nodes
async setup() {
// Store original method
const originalRenderLink = LGraphCanvas.prototype.renderLink;
// --- BEGIN: CRZ Passthrough global state and event listeners ---
window.CRZ_passthrough_show_all_links = false;
window.CRZ_passthrough_hovered = false;
window.CRZ_ctrl_held = false;
window.addEventListener("keydown", (e) => {
if (e.key === "Control") {
window.CRZ_ctrl_held = true;
window.CRZ_passthrough_show_all_links = window.CRZ_passthrough_hovered && window.CRZ_ctrl_held;
if (LiteGraph && LiteGraph.LGraphCanvas && LiteGraph.LGraphCanvas.active_canvas) {
LiteGraph.LGraphCanvas.active_canvas.setDirty(true, true);
}
}
});
window.addEventListener("keyup", (e) => {
if (e.key === "Control") {
window.CRZ_ctrl_held = false;
window.CRZ_passthrough_show_all_links = false;
if (LiteGraph && LiteGraph.LGraphCanvas && LiteGraph.LGraphCanvas.active_canvas) {
LiteGraph.LGraphCanvas.active_canvas.setDirty(true, true);
}
}
});
// --- END: CRZ Passthrough global state and event listeners ---
// Listen for preference changes
window.addEventListener("crz_preferences_changed", (e) => {
// Update all passthrough nodes when preferences change
if (LiteGraph && LiteGraph.LGraphCanvas && LiteGraph.LGraphCanvas.active_canvas) {
const graph = LiteGraph.LGraphCanvas.active_canvas.graph;
if (graph && graph._nodes_by_id) {
Object.values(graph._nodes_by_id).forEach(node => {
if (node.isPassthrough && node.loadPreferences) {
node.loadPreferences();
}
});
}
}
});
// Preferences are now handled by CRZPreferences.js
// Helper function to check if a connection is part of a passthrough chain involving a specific node
function isConnectionInPassthroughChain(link, selectedNode, graph) {
if (!link || !selectedNode || !graph) return false;
const originNode = graph._nodes_by_id[link.origin_id];
const targetNode = graph._nodes_by_id[link.target_id];
if (!originNode || !targetNode) return false;
// If either node in this connection is the selected node, it's part of the chain
if (originNode.id === selectedNode.id || targetNode.id === selectedNode.id) {
return true;
}
// If this connection involves a passthrough, check if the passthrough is connected to the selected node
if (originNode.isPassthrough) {
// Check if this passthrough has input from the selected node
if (originNode.inputs) {
for (const input of originNode.inputs) {
if (input.link) {
const inputLink = graph.links[input.link];
if (inputLink) {
const inputOrigin = graph._nodes_by_id[inputLink.origin_id];
if (inputOrigin && inputOrigin.id === selectedNode.id) {
return true;
}
}
}
}
}
}
if (targetNode.isPassthrough) {
// Check if this passthrough has output to the selected node
if (targetNode.outputs) {
for (const output of targetNode.outputs) {
if (output.links) {
for (const linkId of output.links) {
const outputLink = graph.links[linkId];
if (outputLink) {
const outputTarget = graph._nodes_by_id[outputLink.target_id];
if (outputTarget && outputTarget.id === selectedNode.id) {
return true;
}
}
}
}
}
}
}
return false;
}
// Override the renderLink method to skip passthrough connections (unless hovered)
LGraphCanvas.prototype.renderLink = function(ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks) {
// Skip rendering if we can't find the link
if (!link) return originalRenderLink.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks);
// Only apply hiding logic to established connections (not dragging/temporary links)
// Temporary links during drag operations don't have proper origin_id/target_id
if (typeof link.origin_id !== 'number' || typeof link.target_id !== 'number') {
return originalRenderLink.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks);
}
// Get the nodes connected by this link
let originNode = null;
let targetNode = null;
if (this.graph && this.graph._nodes_by_id) {
originNode = this.graph._nodes_by_id[link.origin_id];
targetNode = this.graph._nodes_by_id[link.target_id];
}
// Only hide if we have valid nodes and at least one is a passthrough
if (!originNode || !targetNode) {
return originalRenderLink.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks);
}
// Check if either node is a passthrough
const hasPassthroughOrigin = originNode.isPassthrough;
const hasPassthroughTarget = targetNode.isPassthrough;
// --- BEGIN: CRZ Passthrough link visibility logic ---
if (hasPassthroughOrigin || hasPassthroughTarget) {
// Check preferences
const prefs = JSON.parse(localStorage.getItem("crz_preferences") || "{}");
const alwaysShow = prefs.always_show_passthrough === true;
const alwaysShowDashboard = prefs.always_show_dashboard_connections === true;
// If "always show dashboard connections" is disabled, hide all connections to/from CRZ nodes
if (!alwaysShowDashboard) {
const hasCRZOrigin = originNode.isCRZNode;
const hasCRZTarget = targetNode.isCRZNode;
if (hasCRZOrigin || hasCRZTarget) {
return; // Hide the connection
}
}
// If always show is enabled, show all passthrough connections
if (alwaysShow) {
return originalRenderLink.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks);
}
// Show all passthrough links if Ctrl+hover is active
if (window.CRZ_passthrough_show_all_links) {
const originalAlpha = ctx.globalAlpha;
ctx.globalAlpha = 1.0;
const result = originalRenderLink.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks);
ctx.globalAlpha = originalAlpha;
return result;
}
// Show if either passthrough node is hovered
if ((hasPassthroughOrigin && originNode.isHovered) ||
(hasPassthroughTarget && targetNode.isHovered)) {
return originalRenderLink.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks);
}
// Show if either passthrough node is selected
if (this.selected_nodes) {
if ((hasPassthroughOrigin && this.selected_nodes[originNode.id]) ||
(hasPassthroughTarget && this.selected_nodes[targetNode.id])) {
return originalRenderLink.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks);
}
// Show if this connection is part of a passthrough chain involving any selected node
for (const selectedNode of Object.values(this.selected_nodes)) {
if (isConnectionInPassthroughChain(link, selectedNode, this.graph)) {
return originalRenderLink.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks);
}
}
}
// Otherwise, hide the passthrough link
return;
}
// --- END: CRZ Passthrough link visibility logic ---
// Check if "always show dashboard connections" is disabled for non-passthrough connections
const prefs = JSON.parse(localStorage.getItem("crz_preferences") || "{}");
const alwaysShowDashboard = prefs.always_show_dashboard_connections === true;
if (!alwaysShowDashboard) {
const hasCRZOrigin = originNode.isCRZNode;
const hasCRZTarget = targetNode.isCRZNode;
if (hasCRZOrigin || hasCRZTarget) {
return; // Hide the connection
}
}
// Otherwise render normally
return originalRenderLink.call(this, ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublinks);
};
}
});