File size: 7,749 Bytes
4450790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
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;
  },
});