import { app } from "scripts/app.js"; import { BaseCollectorNode } from "./base_node_collector.js"; import { NodeTypesString, stripRgthree } from "./constants.js"; import type { INodeInputSlot, INodeOutputSlot, LGraphGroup, LGraphNode, LLink, SerializedLGraphNode, } from "typings/litegraph.js"; import { PassThroughFollowing, addConnectionLayoutSupport, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodesAndFilterPassThroughs, } from "./utils.js"; import { NodeMode } from "typings/comfy.js"; class NodeModeRepeater extends BaseCollectorNode { override readonly inputsPassThroughFollowing: PassThroughFollowing = PassThroughFollowing.ALL; static override type = NodeTypesString.NODE_MODE_REPEATER; static override title = NodeTypesString.NODE_MODE_REPEATER; override comfyClass = NodeTypesString.NODE_MODE_REPEATER; private hasRelayInput = false; private hasTogglerOutput = false; constructor(title?: string) { super(title); this.onConstructed(); } override onConstructed(): boolean { this.addOutput("OPT_CONNECTION", "*", { color_on: "#Fc0", color_off: "#a80", }); return super.onConstructed(); } override configure(info: SerializedLGraphNode): void { // Patch a small issue (~14h) where multiple OPT_CONNECTIONS may have been created. // https://github.com/rgthree/rgthree-comfy/issues/206 // TODO: This can probably be removed within a few weeks. if (info.outputs?.length) { info.outputs.length = 1; } super.configure(info); } override onConnectOutput( outputIndex: number, inputType: string | -1, inputSlot: INodeInputSlot, inputNode: LGraphNode, inputIndex: number, ): boolean { // We can only connect to a a FAST_MUTER or FAST_BYPASSER if we aren't connectged to a relay, since the relay wins. let canConnect = !this.hasRelayInput; canConnect = canConnect && super.onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex); // Output can only connect to a FAST MUTER, FAST BYPASSER, NODE_COLLECTOR OR ACTION BUTTON let nextNode = getConnectedOutputNodesAndFilterPassThroughs(this, inputNode)[0] || inputNode; return ( canConnect && [ NodeTypesString.FAST_MUTER, NodeTypesString.FAST_BYPASSER, NodeTypesString.NODE_COLLECTOR, NodeTypesString.FAST_ACTIONS_BUTTON, NodeTypesString.REROUTE, NodeTypesString.RANDOM_UNMUTER, ].includes(nextNode.type || "") ); } override onConnectInput( inputIndex: number, outputType: string | -1, outputSlot: INodeOutputSlot, outputNode: LGraphNode, outputIndex: number, ): boolean { // We can only connect to a a FAST_MUTER or FAST_BYPASSER if we aren't connectged to a relay, since the relay wins. let canConnect = super.onConnectInput?.( inputIndex, outputType, outputSlot, outputNode, outputIndex, ); // Output can only connect to a FAST MUTER or FAST BYPASSER let nextNode = getConnectedOutputNodesAndFilterPassThroughs(this, outputNode)[0] || outputNode; const isNextNodeRelay = nextNode.type === NodeTypesString.NODE_MODE_RELAY; return canConnect && (!isNextNodeRelay || !this.hasTogglerOutput); } override onConnectionsChange( type: number, slotIndex: number, isConnected: boolean, linkInfo: LLink, ioSlot: INodeOutputSlot | INodeInputSlot, ): void { super.onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot); let hasTogglerOutput = false; let hasRelayInput = false; const outputNodes = getConnectedOutputNodesAndFilterPassThroughs(this); for (const outputNode of outputNodes) { if ( outputNode?.type === NodeTypesString.FAST_MUTER || outputNode?.type === NodeTypesString.FAST_BYPASSER ) { hasTogglerOutput = true; break; } } const inputNodes = getConnectedInputNodesAndFilterPassThroughs(this); for (const [index, inputNode] of inputNodes.entries()) { if (inputNode?.type === NodeTypesString.NODE_MODE_RELAY) { // We can't be connected to a relay if we're connected to a toggler. Something has gone wrong. if (hasTogglerOutput) { console.log(`Can't be connected to a Relay if also output to a toggler.`); this.disconnectInput(index); } else { hasRelayInput = true; if (this.inputs[index]) { this.inputs[index]!.color_on = "#FC0"; this.inputs[index]!.color_off = "#a80"; } } } else { inputNode.mode = this.mode; } } this.hasTogglerOutput = hasTogglerOutput; this.hasRelayInput = hasRelayInput; // If we have a relay input, then we should remove the toggler output, or add it if not. if (this.hasRelayInput) { if (this.outputs[0]) { this.disconnectOutput(0); this.removeOutput(0); } } else if (!this.outputs[0]) { this.addOutput("OPT_CONNECTION", "*", { color_on: "#Fc0", color_off: "#a80", }); } } /** When a mode change, we want all connected nodes to match except for connected relays. */ override onModeChange(from: NodeMode, to: NodeMode) { super.onModeChange(from, to); const linkedNodes = getConnectedInputNodesAndFilterPassThroughs(this).filter( (node) => node.type !== NodeTypesString.NODE_MODE_RELAY, ); if (linkedNodes.length) { for (const node of linkedNodes) { if (node.type !== NodeTypesString.NODE_MODE_RELAY) { // Use "to" as there may be other getters in the way to access this.mode directly. node.mode = to; } } } else if (app.graph._groups?.length) { // No linked nodes.. check if we're in a group. for (const group of app.graph._groups as LGraphGroup[]) { group.recomputeInsideNodes(); if (group._nodes?.includes(this)) { for (const node of group._nodes) { if (node !== this) { // Use "to" as there may be other getters in the way to access this.mode directly. node.mode = to; } } } } } } override getHelp(): string { return `

When this node's mode (Mute, Bypass, Active) changes, it will "repeat" that mode to all connected input nodes, or, if there are no connected nodes AND it is overlapping a group, "repeat" it's mode to all nodes in that group.

`; } } app.registerExtension({ name: "rgthree.NodeModeRepeater", registerCustomNodes() { addConnectionLayoutSupport(NodeModeRepeater, app, [ ["Left", "Right"], ["Right", "Left"], ]); LiteGraph.registerNodeType(NodeModeRepeater.type, NodeModeRepeater); NodeModeRepeater.category = NodeModeRepeater._category; }, });