multimodalart's picture
Squashing commit
4450790 verified
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<LGraphNode>): 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 `
<p>
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.
</p>
<ul>
<li><p>
Optionally, connect this mode's output to a ${stripRgthree(NodeTypesString.FAST_MUTER)}
or ${stripRgthree(NodeTypesString.FAST_BYPASSER)} for a single toggle to quickly
mute/bypass all its connected nodes.
</p></li>
<li><p>
Optionally, connect a ${stripRgthree(NodeTypesString.NODE_MODE_RELAY)} to this nodes
inputs to have it automatically toggle its mode. If connected, this will always take
precedence (and disconnect any connected fast togglers).
</p></li>
</ul>
`;
}
}
app.registerExtension({
name: "rgthree.NodeModeRepeater",
registerCustomNodes() {
addConnectionLayoutSupport(NodeModeRepeater, app, [
["Left", "Right"],
["Right", "Left"],
]);
LiteGraph.registerNodeType(NodeModeRepeater.type, NodeModeRepeater);
NodeModeRepeater.category = NodeModeRepeater._category;
},
});