Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Includes 30 custom nodes committed directly, 7 Civitai-exclusive loras stored via Git LFS, and a setup script that installs all dependencies and downloads HuggingFace-hosted models on vast.ai. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
69
custom_nodes/rgthree-comfy/web/comfyui/any_switch.js
Normal file
69
custom_nodes/rgthree-comfy/web/comfyui/any_switch.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { IoDirection, addConnectionLayoutSupport, followConnectionUntilType } from "./utils.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
|
||||
import { debounce } from "../../rgthree/common/shared_utils.js";
|
||||
class RgthreeAnySwitch extends RgthreeBaseServerNode {
|
||||
constructor(title = RgthreeAnySwitch.title) {
|
||||
super(title);
|
||||
this.stabilizeBound = this.stabilize.bind(this);
|
||||
this.nodeType = null;
|
||||
this.addAnyInput(5);
|
||||
}
|
||||
onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot) {
|
||||
var _a;
|
||||
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, linkInfo, ioSlot);
|
||||
this.scheduleStabilize();
|
||||
}
|
||||
onConnectionsChainChange() {
|
||||
this.scheduleStabilize();
|
||||
}
|
||||
scheduleStabilize(ms = 64) {
|
||||
return debounce(this.stabilizeBound, ms);
|
||||
}
|
||||
addAnyInput(num = 1) {
|
||||
for (let i = 0; i < num; i++) {
|
||||
this.addInput(`any_${String(this.inputs.length + 1).padStart(2, "0")}`, (this.nodeType || "*"));
|
||||
}
|
||||
}
|
||||
stabilize() {
|
||||
removeUnusedInputsFromEnd(this, 4);
|
||||
this.addAnyInput();
|
||||
let connectedType = followConnectionUntilType(this, IoDirection.INPUT, undefined, true);
|
||||
if (!connectedType) {
|
||||
connectedType = followConnectionUntilType(this, IoDirection.OUTPUT, undefined, true);
|
||||
}
|
||||
this.nodeType = (connectedType === null || connectedType === void 0 ? void 0 : connectedType.type) || "*";
|
||||
for (const input of this.inputs) {
|
||||
input.type = this.nodeType;
|
||||
}
|
||||
for (const output of this.outputs) {
|
||||
output.type = this.nodeType;
|
||||
output.label =
|
||||
output.type === "RGTHREE_CONTEXT"
|
||||
? "CONTEXT"
|
||||
: Array.isArray(this.nodeType) || this.nodeType.includes(",")
|
||||
? (connectedType === null || connectedType === void 0 ? void 0 : connectedType.label) || String(this.nodeType)
|
||||
: String(this.nodeType);
|
||||
}
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, RgthreeAnySwitch);
|
||||
addConnectionLayoutSupport(RgthreeAnySwitch, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
}
|
||||
}
|
||||
RgthreeAnySwitch.title = NodeTypesString.ANY_SWITCH;
|
||||
RgthreeAnySwitch.type = NodeTypesString.ANY_SWITCH;
|
||||
RgthreeAnySwitch.comfyClass = NodeTypesString.ANY_SWITCH;
|
||||
app.registerExtension({
|
||||
name: "rgthree.AnySwitch",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "Any Switch (rgthree)") {
|
||||
RgthreeAnySwitch.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,208 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeBaseVirtualNode } from "./base_node.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { PassThroughFollowing, addConnectionLayoutSupport, addMenuItem, getConnectedInputNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodes, getConnectedOutputNodesAndFilterPassThroughs, } from "./utils.js";
|
||||
export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode {
|
||||
constructor(title = BaseAnyInputConnectedNode.title) {
|
||||
super(title);
|
||||
this.isVirtualNode = true;
|
||||
this.inputsPassThroughFollowing = PassThroughFollowing.NONE;
|
||||
this.debouncerTempWidth = 0;
|
||||
this.schedulePromise = null;
|
||||
}
|
||||
onConstructed() {
|
||||
this.addInput("", "*");
|
||||
return super.onConstructed();
|
||||
}
|
||||
clone() {
|
||||
const cloned = super.clone();
|
||||
if (!rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes) {
|
||||
while (cloned.inputs.length > 1) {
|
||||
cloned.removeInput(cloned.inputs.length - 1);
|
||||
}
|
||||
if (cloned.inputs[0]) {
|
||||
cloned.inputs[0].label = "";
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
scheduleStabilizeWidgets(ms = 100) {
|
||||
if (!this.schedulePromise) {
|
||||
this.schedulePromise = new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
this.schedulePromise = null;
|
||||
this.doStablization();
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
return this.schedulePromise;
|
||||
}
|
||||
stabilizeInputsOutputs() {
|
||||
var _a;
|
||||
let changed = false;
|
||||
const hasEmptyInput = !((_a = this.inputs[this.inputs.length - 1]) === null || _a === void 0 ? void 0 : _a.link);
|
||||
if (!hasEmptyInput) {
|
||||
this.addInput("", "*");
|
||||
changed = true;
|
||||
}
|
||||
for (let index = this.inputs.length - 2; index >= 0; index--) {
|
||||
const input = this.inputs[index];
|
||||
if (!input.link) {
|
||||
this.removeInput(index);
|
||||
changed = true;
|
||||
}
|
||||
else {
|
||||
const node = getConnectedInputNodesAndFilterPassThroughs(this, this, index, this.inputsPassThroughFollowing)[0];
|
||||
const newName = (node === null || node === void 0 ? void 0 : node.title) || "";
|
||||
if (input.name !== newName) {
|
||||
input.name = (node === null || node === void 0 ? void 0 : node.title) || "";
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
doStablization() {
|
||||
if (!this.graph) {
|
||||
return;
|
||||
}
|
||||
let dirty = false;
|
||||
this._tempWidth = this.size[0];
|
||||
dirty = this.stabilizeInputsOutputs();
|
||||
const linkedNodes = getConnectedInputNodesAndFilterPassThroughs(this);
|
||||
dirty = this.handleLinkedNodesStabilization(linkedNodes) || dirty;
|
||||
if (dirty) {
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
this.scheduleStabilizeWidgets(500);
|
||||
}
|
||||
handleLinkedNodesStabilization(linkedNodes) {
|
||||
linkedNodes;
|
||||
throw new Error("handleLinkedNodesStabilization should be overridden.");
|
||||
}
|
||||
onConnectionsChainChange() {
|
||||
this.scheduleStabilizeWidgets();
|
||||
}
|
||||
onConnectionsChange(type, index, connected, linkInfo, ioSlot) {
|
||||
super.onConnectionsChange &&
|
||||
super.onConnectionsChange(type, index, connected, linkInfo, ioSlot);
|
||||
if (!linkInfo)
|
||||
return;
|
||||
const connectedNodes = getConnectedOutputNodesAndFilterPassThroughs(this);
|
||||
for (const node of connectedNodes) {
|
||||
if (node.onConnectionsChainChange) {
|
||||
node.onConnectionsChainChange();
|
||||
}
|
||||
}
|
||||
this.scheduleStabilizeWidgets();
|
||||
}
|
||||
removeInput(slot) {
|
||||
this._tempWidth = this.size[0];
|
||||
return super.removeInput(slot);
|
||||
}
|
||||
addInput(name, type, extra_info) {
|
||||
this._tempWidth = this.size[0];
|
||||
return super.addInput(name, type, extra_info);
|
||||
}
|
||||
addWidget(type, name, value, callback, options) {
|
||||
this._tempWidth = this.size[0];
|
||||
return super.addWidget(type, name, value, callback, options);
|
||||
}
|
||||
removeWidget(widget) {
|
||||
this._tempWidth = this.size[0];
|
||||
super.removeWidget(widget);
|
||||
}
|
||||
computeSize(out) {
|
||||
var _a, _b;
|
||||
let size = super.computeSize(out);
|
||||
if (this._tempWidth) {
|
||||
size[0] = this._tempWidth;
|
||||
this.debouncerTempWidth && clearTimeout(this.debouncerTempWidth);
|
||||
this.debouncerTempWidth = setTimeout(() => {
|
||||
this._tempWidth = null;
|
||||
}, 32);
|
||||
}
|
||||
if (this.properties["collapse_connections"]) {
|
||||
const rows = Math.max(((_a = this.inputs) === null || _a === void 0 ? void 0 : _a.length) || 0, ((_b = this.outputs) === null || _b === void 0 ? void 0 : _b.length) || 0, 1) - 1;
|
||||
size[1] = size[1] - rows * LiteGraph.NODE_SLOT_HEIGHT;
|
||||
}
|
||||
setTimeout(() => {
|
||||
var _a;
|
||||
(_a = this.graph) === null || _a === void 0 ? void 0 : _a.setDirtyCanvas(true, true);
|
||||
}, 16);
|
||||
return size;
|
||||
}
|
||||
onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
|
||||
let canConnect = true;
|
||||
if (super.onConnectOutput) {
|
||||
canConnect = super.onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex);
|
||||
}
|
||||
if (canConnect) {
|
||||
const nodes = getConnectedInputNodes(this);
|
||||
if (nodes.includes(inputNode)) {
|
||||
alert(`Whoa, whoa, whoa. You've just tried to create a connection that loops back on itself, ` +
|
||||
`a situation that could create a time paradox, the results of which could cause a ` +
|
||||
`chain reaction that would unravel the very fabric of the space time continuum, ` +
|
||||
`and destroy the entire universe!`);
|
||||
canConnect = false;
|
||||
}
|
||||
}
|
||||
return canConnect;
|
||||
}
|
||||
onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
|
||||
let canConnect = true;
|
||||
if (super.onConnectInput) {
|
||||
canConnect = super.onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex);
|
||||
}
|
||||
if (canConnect) {
|
||||
const nodes = getConnectedOutputNodes(this);
|
||||
if (nodes.includes(outputNode)) {
|
||||
alert(`Whoa, whoa, whoa. You've just tried to create a connection that loops back on itself, ` +
|
||||
`a situation that could create a time paradox, the results of which could cause a ` +
|
||||
`chain reaction that would unravel the very fabric of the space time continuum, ` +
|
||||
`and destroy the entire universe!`);
|
||||
canConnect = false;
|
||||
}
|
||||
}
|
||||
return canConnect;
|
||||
}
|
||||
connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn) {
|
||||
const lastInput = this.inputs[this.inputs.length - 1];
|
||||
if (!(lastInput === null || lastInput === void 0 ? void 0 : lastInput.link) && (lastInput === null || lastInput === void 0 ? void 0 : lastInput.type) === "*") {
|
||||
var sourceSlot = sourceNode.findOutputSlotByType(sourceSlotType, false, true);
|
||||
return sourceNode.connect(sourceSlot, this, slot);
|
||||
}
|
||||
return super.connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn);
|
||||
}
|
||||
static setUp() {
|
||||
super.setUp();
|
||||
addConnectionLayoutSupport(this, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
addMenuItem(this, app, {
|
||||
name: (node) => { var _a; return `${((_a = node.properties) === null || _a === void 0 ? void 0 : _a["collapse_connections"]) ? "Show" : "Collapse"} Connections`; },
|
||||
property: "collapse_connections",
|
||||
prepareValue: (_value, node) => { var _a; return !((_a = node.properties) === null || _a === void 0 ? void 0 : _a["collapse_connections"]); },
|
||||
callback: (_node) => {
|
||||
var _a;
|
||||
(_a = app.canvas.getCurrentGraph()) === null || _a === void 0 ? void 0 : _a.setDirtyCanvas(true, true);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
const oldLGraphNodeConnectByType = LGraphNode.prototype.connectByType;
|
||||
LGraphNode.prototype.connectByType = function connectByType(slot, targetNode, targetSlotType, optsIn) {
|
||||
if (targetNode.inputs) {
|
||||
for (const [index, input] of targetNode.inputs.entries()) {
|
||||
if (!input.link && input.type === "*") {
|
||||
this.connect(slot, targetNode, index);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ((oldLGraphNodeConnectByType &&
|
||||
oldLGraphNodeConnectByType.call(this, slot, targetNode, targetSlotType, optsIn)) ||
|
||||
null);
|
||||
};
|
||||
316
custom_nodes/rgthree-comfy/web/comfyui/base_node.js
Normal file
316
custom_nodes/rgthree-comfy/web/comfyui/base_node.js
Normal file
@@ -0,0 +1,316 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
|
||||
import { LogLevel, rgthree } from "./rgthree.js";
|
||||
import { addHelpMenuItem } from "./utils.js";
|
||||
import { RgthreeHelpDialog } from "../../rgthree/common/dialog.js";
|
||||
import { importIndividualNodesInnerOnDragDrop, importIndividualNodesInnerOnDragOver, } from "./feature_import_individual_nodes.js";
|
||||
import { defineProperty, moveArrayItem } from "../../rgthree/common/shared_utils.js";
|
||||
export class RgthreeBaseNode extends LGraphNode {
|
||||
constructor(title = RgthreeBaseNode.title, skipOnConstructedCall = true) {
|
||||
super(title);
|
||||
this.comfyClass = "__NEED_COMFY_CLASS__";
|
||||
this.nickname = "rgthree";
|
||||
this.isVirtualNode = false;
|
||||
this.isDropEnabled = false;
|
||||
this.removed = false;
|
||||
this.configuring = false;
|
||||
this._tempWidth = 0;
|
||||
this.__constructed__ = false;
|
||||
this.helpDialog = null;
|
||||
if (title == "__NEED_CLASS_TITLE__") {
|
||||
throw new Error("RgthreeBaseNode needs overrides.");
|
||||
}
|
||||
this.widgets = this.widgets || [];
|
||||
this.properties = this.properties || {};
|
||||
setTimeout(() => {
|
||||
if (this.comfyClass == "__NEED_COMFY_CLASS__") {
|
||||
throw new Error("RgthreeBaseNode needs a comfy class override.");
|
||||
}
|
||||
if (this.constructor.type == "__NEED_CLASS_TYPE__") {
|
||||
throw new Error("RgthreeBaseNode needs overrides.");
|
||||
}
|
||||
this.checkAndRunOnConstructed();
|
||||
});
|
||||
defineProperty(this, "mode", {
|
||||
get: () => {
|
||||
return this.rgthree_mode;
|
||||
},
|
||||
set: (mode) => {
|
||||
if (this.rgthree_mode != mode) {
|
||||
const oldMode = this.rgthree_mode;
|
||||
this.rgthree_mode = mode;
|
||||
this.onModeChange(oldMode, mode);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
checkAndRunOnConstructed() {
|
||||
var _a;
|
||||
if (!this.__constructed__) {
|
||||
this.onConstructed();
|
||||
const [n, v] = rgthree.logger.logParts(LogLevel.DEV, `[RgthreeBaseNode] Child class did not call onConstructed for "${this.type}.`);
|
||||
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
|
||||
}
|
||||
return this.__constructed__;
|
||||
}
|
||||
onDragOver(e) {
|
||||
if (!this.isDropEnabled)
|
||||
return false;
|
||||
return importIndividualNodesInnerOnDragOver(this, e);
|
||||
}
|
||||
async onDragDrop(e) {
|
||||
if (!this.isDropEnabled)
|
||||
return false;
|
||||
return importIndividualNodesInnerOnDragDrop(this, e);
|
||||
}
|
||||
onConstructed() {
|
||||
var _a;
|
||||
if (this.__constructed__)
|
||||
return false;
|
||||
this.type = (_a = this.type) !== null && _a !== void 0 ? _a : undefined;
|
||||
this.__constructed__ = true;
|
||||
rgthree.invokeExtensionsAsync("nodeCreated", this);
|
||||
return this.__constructed__;
|
||||
}
|
||||
configure(info) {
|
||||
this.configuring = true;
|
||||
super.configure(info);
|
||||
for (const w of this.widgets || []) {
|
||||
w.last_y = w.last_y || 0;
|
||||
}
|
||||
this.configuring = false;
|
||||
}
|
||||
clone() {
|
||||
const cloned = super.clone();
|
||||
if ((cloned === null || cloned === void 0 ? void 0 : cloned.properties) && !!window.structuredClone) {
|
||||
cloned.properties = structuredClone(cloned.properties);
|
||||
}
|
||||
cloned.graph = this.graph;
|
||||
return cloned;
|
||||
}
|
||||
onModeChange(from, to) {
|
||||
}
|
||||
async handleAction(action) {
|
||||
action;
|
||||
}
|
||||
removeWidget(widget) {
|
||||
var _a;
|
||||
if (typeof widget === "number") {
|
||||
widget = this.widgets[widget];
|
||||
}
|
||||
if (!widget)
|
||||
return;
|
||||
const canUseComfyUIRemoveWidget = false;
|
||||
if (canUseComfyUIRemoveWidget && typeof super.removeWidget === 'function') {
|
||||
super.removeWidget(widget);
|
||||
}
|
||||
else {
|
||||
const index = this.widgets.indexOf(widget);
|
||||
if (index > -1) {
|
||||
this.widgets.splice(index, 1);
|
||||
}
|
||||
(_a = widget.onRemove) === null || _a === void 0 ? void 0 : _a.call(widget);
|
||||
}
|
||||
}
|
||||
replaceWidget(widgetOrSlot, newWidget) {
|
||||
let index = null;
|
||||
if (widgetOrSlot) {
|
||||
index = typeof widgetOrSlot === "number" ? widgetOrSlot : this.widgets.indexOf(widgetOrSlot);
|
||||
this.removeWidget(this.widgets[index]);
|
||||
}
|
||||
index = index != null ? index : this.widgets.length - 1;
|
||||
if (this.widgets.includes(newWidget)) {
|
||||
moveArrayItem(this.widgets, newWidget, index);
|
||||
}
|
||||
else {
|
||||
this.widgets.splice(index, 0, newWidget);
|
||||
}
|
||||
}
|
||||
defaultGetSlotMenuOptions(slot) {
|
||||
var _a, _b;
|
||||
const menu_info = [];
|
||||
if ((_b = (_a = slot === null || slot === void 0 ? void 0 : slot.output) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length) {
|
||||
menu_info.push({ content: "Disconnect Links", slot });
|
||||
}
|
||||
let inputOrOutput = slot.input || slot.output;
|
||||
if (inputOrOutput) {
|
||||
if (inputOrOutput.removable) {
|
||||
menu_info.push(inputOrOutput.locked ? { content: "Cannot remove" } : { content: "Remove Slot", slot });
|
||||
}
|
||||
if (!inputOrOutput.nameLocked) {
|
||||
menu_info.push({ content: "Rename Slot", slot });
|
||||
}
|
||||
}
|
||||
return menu_info;
|
||||
}
|
||||
onRemoved() {
|
||||
var _a;
|
||||
(_a = super.onRemoved) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
this.removed = true;
|
||||
}
|
||||
static setUp(...args) {
|
||||
}
|
||||
getHelp() {
|
||||
return "";
|
||||
}
|
||||
showHelp() {
|
||||
const help = this.getHelp() || this.constructor.help;
|
||||
if (help) {
|
||||
this.helpDialog = new RgthreeHelpDialog(this, help).show();
|
||||
this.helpDialog.addEventListener("close", (e) => {
|
||||
this.helpDialog = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
onKeyDown(event) {
|
||||
KEY_EVENT_SERVICE.handleKeyDownOrUp(event);
|
||||
if (event.key == "?" && !this.helpDialog) {
|
||||
this.showHelp();
|
||||
}
|
||||
}
|
||||
onKeyUp(event) {
|
||||
KEY_EVENT_SERVICE.handleKeyDownOrUp(event);
|
||||
}
|
||||
getExtraMenuOptions(canvas, options) {
|
||||
var _a, _b, _c, _d, _e, _f;
|
||||
if (super.getExtraMenuOptions) {
|
||||
(_a = super.getExtraMenuOptions) === null || _a === void 0 ? void 0 : _a.apply(this, [canvas, options]);
|
||||
}
|
||||
else if ((_c = (_b = this.constructor.nodeType) === null || _b === void 0 ? void 0 : _b.prototype) === null || _c === void 0 ? void 0 : _c.getExtraMenuOptions) {
|
||||
(_f = (_e = (_d = this.constructor.nodeType) === null || _d === void 0 ? void 0 : _d.prototype) === null || _e === void 0 ? void 0 : _e.getExtraMenuOptions) === null || _f === void 0 ? void 0 : _f.apply(this, [canvas, options]);
|
||||
}
|
||||
const help = this.getHelp() || this.constructor.help;
|
||||
if (help) {
|
||||
addHelpMenuItem(this, help, options);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
RgthreeBaseNode.exposedActions = [];
|
||||
RgthreeBaseNode.title = "__NEED_CLASS_TITLE__";
|
||||
RgthreeBaseNode.type = "__NEED_CLASS_TYPE__";
|
||||
RgthreeBaseNode.category = "rgthree";
|
||||
RgthreeBaseNode._category = "rgthree";
|
||||
export class RgthreeBaseVirtualNode extends RgthreeBaseNode {
|
||||
constructor(title = RgthreeBaseNode.title) {
|
||||
super(title, false);
|
||||
this.isVirtualNode = true;
|
||||
}
|
||||
static setUp() {
|
||||
if (!this.type) {
|
||||
throw new Error(`Missing type for RgthreeBaseVirtualNode: ${this.title}`);
|
||||
}
|
||||
LiteGraph.registerNodeType(this.type, this);
|
||||
if (this._category) {
|
||||
this.category = this._category;
|
||||
}
|
||||
}
|
||||
}
|
||||
export class RgthreeBaseServerNode extends RgthreeBaseNode {
|
||||
constructor(title) {
|
||||
super(title, true);
|
||||
this.isDropEnabled = true;
|
||||
this.serialize_widgets = true;
|
||||
this.setupFromServerNodeData();
|
||||
this.onConstructed();
|
||||
}
|
||||
getWidgets() {
|
||||
return ComfyWidgets;
|
||||
}
|
||||
async setupFromServerNodeData() {
|
||||
var _a, _b, _c, _d, _e;
|
||||
const nodeData = this.constructor.nodeData;
|
||||
if (!nodeData) {
|
||||
throw Error("No node data");
|
||||
}
|
||||
this.comfyClass = nodeData.name;
|
||||
let inputs = nodeData["input"]["required"];
|
||||
if (nodeData["input"]["optional"] != undefined) {
|
||||
inputs = Object.assign({}, inputs, nodeData["input"]["optional"]);
|
||||
}
|
||||
const WIDGETS = this.getWidgets();
|
||||
const config = {
|
||||
minWidth: 1,
|
||||
minHeight: 1,
|
||||
widget: null,
|
||||
};
|
||||
for (const inputName in inputs) {
|
||||
const inputData = inputs[inputName];
|
||||
const type = inputData[0];
|
||||
if ((_a = inputData[1]) === null || _a === void 0 ? void 0 : _a.forceInput) {
|
||||
this.addInput(inputName, type);
|
||||
}
|
||||
else {
|
||||
let widgetCreated = true;
|
||||
if (Array.isArray(type)) {
|
||||
Object.assign(config, WIDGETS.COMBO(this, inputName, inputData, app) || {});
|
||||
}
|
||||
else if (`${type}:${inputName}` in WIDGETS) {
|
||||
Object.assign(config, WIDGETS[`${type}:${inputName}`](this, inputName, inputData, app) || {});
|
||||
}
|
||||
else if (type in WIDGETS) {
|
||||
Object.assign(config, WIDGETS[type](this, inputName, inputData, app) || {});
|
||||
}
|
||||
else {
|
||||
this.addInput(inputName, type);
|
||||
widgetCreated = false;
|
||||
}
|
||||
if (widgetCreated && ((_b = inputData[1]) === null || _b === void 0 ? void 0 : _b.forceInput) && (config === null || config === void 0 ? void 0 : config.widget)) {
|
||||
if (!config.widget.options)
|
||||
config.widget.options = {};
|
||||
config.widget.options.forceInput = inputData[1].forceInput;
|
||||
}
|
||||
if (widgetCreated && ((_c = inputData[1]) === null || _c === void 0 ? void 0 : _c.defaultInput) && (config === null || config === void 0 ? void 0 : config.widget)) {
|
||||
if (!config.widget.options)
|
||||
config.widget.options = {};
|
||||
config.widget.options.defaultInput = inputData[1].defaultInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const o in nodeData["output"]) {
|
||||
let output = nodeData["output"][o];
|
||||
if (output instanceof Array)
|
||||
output = "COMBO";
|
||||
const outputName = nodeData["output_name"][o] || output;
|
||||
const outputShape = nodeData["output_is_list"][o]
|
||||
? LiteGraph.GRID_SHAPE
|
||||
: LiteGraph.CIRCLE_SHAPE;
|
||||
this.addOutput(outputName, output, { shape: outputShape });
|
||||
}
|
||||
const s = this.computeSize();
|
||||
s[0] = Math.max((_d = config.minWidth) !== null && _d !== void 0 ? _d : 1, s[0] * 1.5);
|
||||
s[1] = Math.max((_e = config.minHeight) !== null && _e !== void 0 ? _e : 1, s[1]);
|
||||
this.size = s;
|
||||
this.serialize_widgets = true;
|
||||
}
|
||||
static registerForOverride(comfyClass, nodeData, rgthreeClass) {
|
||||
if (OVERRIDDEN_SERVER_NODES.has(comfyClass)) {
|
||||
throw Error(`Already have a class to override ${comfyClass.type || comfyClass.name || comfyClass.title}`);
|
||||
}
|
||||
OVERRIDDEN_SERVER_NODES.set(comfyClass, rgthreeClass);
|
||||
if (!rgthreeClass.__registeredForOverride__) {
|
||||
rgthreeClass.__registeredForOverride__ = true;
|
||||
rgthreeClass.nodeType = comfyClass;
|
||||
rgthreeClass.nodeData = nodeData;
|
||||
rgthreeClass.onRegisteredForOverride(comfyClass, rgthreeClass);
|
||||
}
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, rgthreeClass) {
|
||||
}
|
||||
}
|
||||
RgthreeBaseServerNode.nodeType = null;
|
||||
RgthreeBaseServerNode.nodeData = null;
|
||||
RgthreeBaseServerNode.__registeredForOverride__ = false;
|
||||
const OVERRIDDEN_SERVER_NODES = new Map();
|
||||
const oldregisterNodeType = LiteGraph.registerNodeType;
|
||||
LiteGraph.registerNodeType = async function (nodeId, baseClass) {
|
||||
var _a;
|
||||
const clazz = OVERRIDDEN_SERVER_NODES.get(baseClass) || baseClass;
|
||||
if (clazz !== baseClass) {
|
||||
const classLabel = clazz.type || clazz.name || clazz.title;
|
||||
const [n, v] = rgthree.logger.logParts(LogLevel.DEBUG, `${nodeId}: replacing default ComfyNode implementation with custom ${classLabel} class.`);
|
||||
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
|
||||
}
|
||||
return oldregisterNodeType.call(LiteGraph, nodeId, clazz);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
|
||||
import { PassThroughFollowing, getConnectedInputNodes, getConnectedInputNodesAndFilterPassThroughs, shouldPassThrough, } from "./utils.js";
|
||||
export class BaseCollectorNode extends BaseAnyInputConnectedNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
this.inputsPassThroughFollowing = PassThroughFollowing.REROUTE_ONLY;
|
||||
this.logger = rgthree.newLogSession("[BaseCollectorNode]");
|
||||
}
|
||||
clone() {
|
||||
const cloned = super.clone();
|
||||
return cloned;
|
||||
}
|
||||
handleLinkedNodesStabilization(linkedNodes) {
|
||||
return false;
|
||||
}
|
||||
onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
|
||||
var _a, _b, _c, _d;
|
||||
let canConnect = super.onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex);
|
||||
if (canConnect) {
|
||||
const allConnectedNodes = getConnectedInputNodes(this);
|
||||
const nodesAlreadyInSlot = getConnectedInputNodes(this, undefined, inputIndex);
|
||||
if (allConnectedNodes.includes(outputNode)) {
|
||||
const [n, v] = this.logger.debugParts(`${outputNode.title} is already connected to ${this.title}.`);
|
||||
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
|
||||
if (nodesAlreadyInSlot.includes(outputNode)) {
|
||||
const [n, v] = this.logger.debugParts(`... but letting it slide since it's for the same slot.`);
|
||||
(_b = console[n]) === null || _b === void 0 ? void 0 : _b.call(console, ...v);
|
||||
}
|
||||
else {
|
||||
canConnect = false;
|
||||
}
|
||||
}
|
||||
if (canConnect && shouldPassThrough(outputNode, PassThroughFollowing.REROUTE_ONLY)) {
|
||||
const connectedNode = getConnectedInputNodesAndFilterPassThroughs(outputNode, undefined, undefined, PassThroughFollowing.REROUTE_ONLY)[0];
|
||||
if (connectedNode && allConnectedNodes.includes(connectedNode)) {
|
||||
const [n, v] = this.logger.debugParts(`${connectedNode.title} is already connected to ${this.title}.`);
|
||||
(_c = console[n]) === null || _c === void 0 ? void 0 : _c.call(console, ...v);
|
||||
if (nodesAlreadyInSlot.includes(connectedNode)) {
|
||||
const [n, v] = this.logger.debugParts(`... but letting it slide since it's for the same slot.`);
|
||||
(_d = console[n]) === null || _d === void 0 ? void 0 : _d.call(console, ...v);
|
||||
}
|
||||
else {
|
||||
canConnect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return canConnect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
|
||||
import { changeModeOfNodes, PassThroughFollowing } from "./utils.js";
|
||||
import { wait } from "../../rgthree/common/shared_utils.js";
|
||||
export class BaseNodeModeChanger extends BaseAnyInputConnectedNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
this.inputsPassThroughFollowing = PassThroughFollowing.ALL;
|
||||
this.isVirtualNode = true;
|
||||
this.modeOn = -1;
|
||||
this.modeOff = -1;
|
||||
this.properties["toggleRestriction"] = "default";
|
||||
}
|
||||
onConstructed() {
|
||||
wait(10).then(() => {
|
||||
if (this.modeOn < 0 || this.modeOff < 0) {
|
||||
throw new Error("modeOn and modeOff must be overridden.");
|
||||
}
|
||||
});
|
||||
this.addOutput("OPT_CONNECTION", "*");
|
||||
return super.onConstructed();
|
||||
}
|
||||
handleLinkedNodesStabilization(linkedNodes) {
|
||||
let changed = false;
|
||||
for (const [index, node] of linkedNodes.entries()) {
|
||||
let widget = this.widgets && this.widgets[index];
|
||||
if (!widget) {
|
||||
this._tempWidth = this.size[0];
|
||||
widget = this.addWidget("toggle", "", false, "", { on: "yes", off: "no" });
|
||||
changed = true;
|
||||
}
|
||||
if (node) {
|
||||
changed = this.setWidget(widget, node) || changed;
|
||||
}
|
||||
}
|
||||
if (this.widgets && this.widgets.length > linkedNodes.length) {
|
||||
this.widgets.length = linkedNodes.length;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
setWidget(widget, linkedNode, forceValue) {
|
||||
let changed = false;
|
||||
const value = forceValue == null ? linkedNode.mode === this.modeOn : forceValue;
|
||||
let name = `Enable ${linkedNode.title}`;
|
||||
if (widget.name !== name) {
|
||||
widget.name = `Enable ${linkedNode.title}`;
|
||||
widget.options = { on: "yes", off: "no" };
|
||||
widget.value = value;
|
||||
widget.doModeChange = (forceValue, skipOtherNodeCheck) => {
|
||||
var _a, _b, _c;
|
||||
let newValue = forceValue == null ? linkedNode.mode === this.modeOff : forceValue;
|
||||
if (skipOtherNodeCheck !== true) {
|
||||
if (newValue && ((_b = (_a = this.properties) === null || _a === void 0 ? void 0 : _a["toggleRestriction"]) === null || _b === void 0 ? void 0 : _b.includes(" one"))) {
|
||||
for (const widget of this.widgets) {
|
||||
widget.doModeChange(false, true);
|
||||
}
|
||||
}
|
||||
else if (!newValue && ((_c = this.properties) === null || _c === void 0 ? void 0 : _c["toggleRestriction"]) === "always one") {
|
||||
newValue = this.widgets.every((w) => !w.value || w === widget);
|
||||
}
|
||||
}
|
||||
changeModeOfNodes(linkedNode, (newValue ? this.modeOn : this.modeOff));
|
||||
widget.value = newValue;
|
||||
};
|
||||
widget.callback = () => {
|
||||
widget.doModeChange();
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
if (forceValue != null) {
|
||||
const newMode = (forceValue ? this.modeOn : this.modeOff);
|
||||
if (linkedNode.mode !== newMode) {
|
||||
changeModeOfNodes(linkedNode, newMode);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
forceWidgetOff(widget, skipOtherNodeCheck) {
|
||||
widget.doModeChange(false, skipOtherNodeCheck);
|
||||
}
|
||||
forceWidgetOn(widget, skipOtherNodeCheck) {
|
||||
widget.doModeChange(true, skipOtherNodeCheck);
|
||||
}
|
||||
forceWidgetToggle(widget, skipOtherNodeCheck) {
|
||||
widget.doModeChange(!widget.value, skipOtherNodeCheck);
|
||||
}
|
||||
}
|
||||
BaseNodeModeChanger.collapsible = false;
|
||||
BaseNodeModeChanger["@toggleRestriction"] = {
|
||||
type: "combo",
|
||||
values: ["default", "max one", "always one"],
|
||||
};
|
||||
252
custom_nodes/rgthree-comfy/web/comfyui/base_power_prompt.js
Normal file
252
custom_nodes/rgthree-comfy/web/comfyui/base_power_prompt.js
Normal file
@@ -0,0 +1,252 @@
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { wait } from "../../rgthree/common/shared_utils.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
export class PowerPrompt {
|
||||
constructor(node, nodeData) {
|
||||
this.combos = {};
|
||||
this.combosValues = {};
|
||||
this.configuring = false;
|
||||
this.node = node;
|
||||
this.node.properties = this.node.properties || {};
|
||||
this.node.properties["combos_filter"] = "";
|
||||
this.nodeData = nodeData;
|
||||
this.isSimple = this.nodeData.name.includes("Simple");
|
||||
this.promptEl = node.widgets[0].inputEl;
|
||||
this.addAndHandleKeyboardLoraEditWeight();
|
||||
this.patchNodeRefresh();
|
||||
const oldConfigure = this.node.configure;
|
||||
this.node.configure = (info) => {
|
||||
this.configuring = true;
|
||||
oldConfigure === null || oldConfigure === void 0 ? void 0 : oldConfigure.apply(this.node, [info]);
|
||||
this.configuring = false;
|
||||
};
|
||||
const oldOnConnectionsChange = this.node.onConnectionsChange;
|
||||
this.node.onConnectionsChange = (type, slotIndex, isConnected, link_info, _ioSlot) => {
|
||||
oldOnConnectionsChange === null || oldOnConnectionsChange === void 0 ? void 0 : oldOnConnectionsChange.apply(this.node, [type, slotIndex, isConnected, link_info, _ioSlot]);
|
||||
this.onNodeConnectionsChange(type, slotIndex, isConnected, link_info, _ioSlot);
|
||||
};
|
||||
const oldOnConnectInput = this.node.onConnectInput;
|
||||
this.node.onConnectInput = (inputIndex, outputType, outputSlot, outputNode, outputIndex) => {
|
||||
let canConnect = true;
|
||||
if (oldOnConnectInput) {
|
||||
canConnect = oldOnConnectInput.apply(this.node, [
|
||||
inputIndex,
|
||||
outputType,
|
||||
outputSlot,
|
||||
outputNode,
|
||||
outputIndex,
|
||||
]);
|
||||
}
|
||||
return (this.configuring ||
|
||||
!!rgthree.loadingApiJson ||
|
||||
(canConnect && !this.node.inputs[inputIndex].disabled));
|
||||
};
|
||||
const oldOnConnectOutput = this.node.onConnectOutput;
|
||||
this.node.onConnectOutput = (outputIndex, inputType, inputSlot, inputNode, inputIndex) => {
|
||||
let canConnect = true;
|
||||
if (oldOnConnectOutput) {
|
||||
canConnect = oldOnConnectOutput === null || oldOnConnectOutput === void 0 ? void 0 : oldOnConnectOutput.apply(this.node, [
|
||||
outputIndex,
|
||||
inputType,
|
||||
inputSlot,
|
||||
inputNode,
|
||||
inputIndex,
|
||||
]);
|
||||
}
|
||||
return (this.configuring ||
|
||||
!!rgthree.loadingApiJson ||
|
||||
(canConnect && !this.node.outputs[outputIndex].disabled));
|
||||
};
|
||||
const onPropertyChanged = this.node.onPropertyChanged;
|
||||
this.node.onPropertyChanged = (property, value, prevValue) => {
|
||||
const v = onPropertyChanged && onPropertyChanged.call(this.node, property, value, prevValue);
|
||||
if (property === "combos_filter") {
|
||||
this.refreshCombos(this.nodeData);
|
||||
}
|
||||
return v !== null && v !== void 0 ? v : true;
|
||||
};
|
||||
for (let i = this.node.widgets.length - 1; i >= 0; i--) {
|
||||
if (this.shouldRemoveServerWidget(this.node.widgets[i])) {
|
||||
this.node.widgets.splice(i, 1);
|
||||
}
|
||||
}
|
||||
this.refreshCombos(nodeData);
|
||||
setTimeout(() => {
|
||||
this.stabilizeInputsOutputs();
|
||||
}, 32);
|
||||
}
|
||||
onNodeConnectionsChange(_type, _slotIndex, _isConnected, _linkInfo, _ioSlot) {
|
||||
this.stabilizeInputsOutputs();
|
||||
}
|
||||
stabilizeInputsOutputs() {
|
||||
if (this.configuring || rgthree.loadingApiJson) {
|
||||
return;
|
||||
}
|
||||
const clipLinked = this.node.inputs.some((i) => i.name.includes("clip") && !!i.link);
|
||||
const modelLinked = this.node.inputs.some((i) => i.name.includes("model") && !!i.link);
|
||||
for (const output of this.node.outputs) {
|
||||
const type = output.type.toLowerCase();
|
||||
if (type.includes("model")) {
|
||||
output.disabled = !modelLinked;
|
||||
}
|
||||
else if (type.includes("conditioning")) {
|
||||
output.disabled = !clipLinked;
|
||||
}
|
||||
else if (type.includes("clip")) {
|
||||
output.disabled = !clipLinked;
|
||||
}
|
||||
else if (type.includes("string")) {
|
||||
output.color_off = "#7F7";
|
||||
output.color_on = "#7F7";
|
||||
}
|
||||
if (output.disabled) {
|
||||
}
|
||||
}
|
||||
}
|
||||
onFreshNodeDefs(event) {
|
||||
this.refreshCombos(event.detail[this.nodeData.name]);
|
||||
}
|
||||
shouldRemoveServerWidget(widget) {
|
||||
var _a, _b, _c, _d;
|
||||
return (((_a = widget.name) === null || _a === void 0 ? void 0 : _a.startsWith("insert_")) ||
|
||||
((_b = widget.name) === null || _b === void 0 ? void 0 : _b.startsWith("target_")) ||
|
||||
((_c = widget.name) === null || _c === void 0 ? void 0 : _c.startsWith("crop_")) ||
|
||||
((_d = widget.name) === null || _d === void 0 ? void 0 : _d.startsWith("values_")));
|
||||
}
|
||||
refreshCombos(nodeData) {
|
||||
var _a, _b, _c;
|
||||
this.nodeData = nodeData;
|
||||
let filter = null;
|
||||
if ((_a = this.node.properties["combos_filter"]) === null || _a === void 0 ? void 0 : _a.trim()) {
|
||||
try {
|
||||
filter = new RegExp(this.node.properties["combos_filter"].trim(), "i");
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`Could not parse "${filter}" for Regular Expression`, e);
|
||||
filter = null;
|
||||
}
|
||||
}
|
||||
let data = Object.assign({}, ((_b = this.nodeData.input) === null || _b === void 0 ? void 0 : _b.optional) || {}, ((_c = this.nodeData.input) === null || _c === void 0 ? void 0 : _c.hidden) || {});
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (Array.isArray(value[0])) {
|
||||
let values = value[0];
|
||||
if (key.startsWith("insert")) {
|
||||
values = filter
|
||||
? values.filter((v, i) => i < 1 || (i == 1 && v.match(/^disable\s[a-z]/i)) || (filter === null || filter === void 0 ? void 0 : filter.test(v)))
|
||||
: values;
|
||||
const shouldShow = values.length > 2 || (values.length > 1 && !values[1].match(/^disable\s[a-z]/i));
|
||||
if (shouldShow) {
|
||||
if (!this.combos[key]) {
|
||||
this.combos[key] = this.node.addWidget("combo", key, values[0], (selected) => {
|
||||
if (selected !== values[0] && !selected.match(/^disable\s[a-z]/i)) {
|
||||
wait().then(() => {
|
||||
if (key.includes("embedding")) {
|
||||
this.insertSelectionText(`embedding:${selected}`);
|
||||
}
|
||||
else if (key.includes("saved")) {
|
||||
this.insertSelectionText(this.combosValues[`values_${key}`][values.indexOf(selected)]);
|
||||
}
|
||||
else if (key.includes("lora")) {
|
||||
this.insertSelectionText(`<lora:${selected}:1.0>`);
|
||||
}
|
||||
this.combos[key].value = values[0];
|
||||
});
|
||||
}
|
||||
}, {
|
||||
values,
|
||||
serialize: true,
|
||||
});
|
||||
this.combos[key].oldComputeSize = this.combos[key].computeSize;
|
||||
let node = this.node;
|
||||
this.combos[key].computeSize = function (width) {
|
||||
var _a, _b;
|
||||
const size = ((_b = (_a = this).oldComputeSize) === null || _b === void 0 ? void 0 : _b.call(_a, width)) || [
|
||||
width,
|
||||
LiteGraph.NODE_WIDGET_HEIGHT,
|
||||
];
|
||||
if (this === node.widgets[node.widgets.length - 1]) {
|
||||
size[1] += 10;
|
||||
}
|
||||
return size;
|
||||
};
|
||||
}
|
||||
this.combos[key].options.values = values;
|
||||
this.combos[key].value = values[0];
|
||||
}
|
||||
else if (!shouldShow && this.combos[key]) {
|
||||
this.node.widgets.splice(this.node.widgets.indexOf(this.combos[key]), 1);
|
||||
delete this.combos[key];
|
||||
}
|
||||
}
|
||||
else if (key.startsWith("values")) {
|
||||
this.combosValues[key] = values;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
insertSelectionText(text) {
|
||||
if (!this.promptEl) {
|
||||
console.error("Asked to insert text, but no textbox found.");
|
||||
return;
|
||||
}
|
||||
let prompt = this.promptEl.value;
|
||||
let first = prompt.substring(0, this.promptEl.selectionEnd).replace(/ +$/, "");
|
||||
first = first + (["\n"].includes(first[first.length - 1]) ? "" : first.length ? " " : "");
|
||||
let second = prompt.substring(this.promptEl.selectionEnd).replace(/^ +/, "");
|
||||
second = (["\n"].includes(second[0]) ? "" : second.length ? " " : "") + second;
|
||||
this.promptEl.value = first + text + second;
|
||||
this.promptEl.focus();
|
||||
this.promptEl.selectionStart = first.length;
|
||||
this.promptEl.selectionEnd = first.length + text.length;
|
||||
}
|
||||
addAndHandleKeyboardLoraEditWeight() {
|
||||
this.promptEl.addEventListener("keydown", (event) => {
|
||||
var _a, _b;
|
||||
if (!(event.key === "ArrowUp" || event.key === "ArrowDown"))
|
||||
return;
|
||||
if (!event.ctrlKey && !event.metaKey)
|
||||
return;
|
||||
const delta = event.shiftKey ? 0.01 : 0.1;
|
||||
let start = this.promptEl.selectionStart;
|
||||
let end = this.promptEl.selectionEnd;
|
||||
let fullText = this.promptEl.value;
|
||||
let selectedText = fullText.substring(start, end);
|
||||
if (!selectedText) {
|
||||
const stopOn = "<>()\r\n\t";
|
||||
if (fullText[start] == ">") {
|
||||
start -= 2;
|
||||
end -= 2;
|
||||
}
|
||||
if (fullText[end - 1] == "<") {
|
||||
start += 2;
|
||||
end += 2;
|
||||
}
|
||||
while (!stopOn.includes(fullText[start]) && start > 0) {
|
||||
start--;
|
||||
}
|
||||
while (!stopOn.includes(fullText[end - 1]) && end < fullText.length) {
|
||||
end++;
|
||||
}
|
||||
selectedText = fullText.substring(start, end);
|
||||
}
|
||||
if (!selectedText.startsWith("<lora:") || !selectedText.endsWith(">")) {
|
||||
return;
|
||||
}
|
||||
let weight = (_b = Number((_a = selectedText.match(/:(-?\d*(\.\d*)?)>$/)) === null || _a === void 0 ? void 0 : _a[1])) !== null && _b !== void 0 ? _b : 1;
|
||||
weight += event.key === "ArrowUp" ? delta : -delta;
|
||||
const updatedText = selectedText.replace(/(:-?\d*(\.\d*)?)?>$/, `:${weight.toFixed(2)}>`);
|
||||
this.promptEl.setRangeText(updatedText, start, end, "select");
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
}
|
||||
patchNodeRefresh() {
|
||||
this.boundOnFreshNodeDefs = this.onFreshNodeDefs.bind(this);
|
||||
api.addEventListener("fresh-node-defs", this.boundOnFreshNodeDefs);
|
||||
const oldNodeRemoved = this.node.onRemoved;
|
||||
this.node.onRemoved = () => {
|
||||
oldNodeRemoved === null || oldNodeRemoved === void 0 ? void 0 : oldNodeRemoved.call(this.node);
|
||||
api.removeEventListener("fresh-node-defs", this.boundOnFreshNodeDefs);
|
||||
};
|
||||
}
|
||||
}
|
||||
105
custom_nodes/rgthree-comfy/web/comfyui/bookmark.js
Normal file
105
custom_nodes/rgthree-comfy/web/comfyui/bookmark.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeBaseVirtualNode } from "./base_node.js";
|
||||
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
|
||||
import { SERVICE as BOOKMARKS_SERVICE } from "./services/bookmarks_services.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { getClosestOrSelf, query } from "../../rgthree/common/utils_dom.js";
|
||||
import { wait } from "../../rgthree/common/shared_utils.js";
|
||||
import { findFromNodeForSubgraph } from "./utils.js";
|
||||
export class Bookmark extends RgthreeBaseVirtualNode {
|
||||
get _collapsed_width() {
|
||||
return this.___collapsed_width;
|
||||
}
|
||||
set _collapsed_width(width) {
|
||||
const canvas = app.canvas;
|
||||
const ctx = canvas.canvas.getContext("2d");
|
||||
const oldFont = ctx.font;
|
||||
ctx.font = canvas.title_text_font;
|
||||
this.___collapsed_width = 40 + ctx.measureText(this.title).width;
|
||||
ctx.font = oldFont;
|
||||
}
|
||||
constructor(title = Bookmark.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.BOOKMARK;
|
||||
this.___collapsed_width = 0;
|
||||
this.isVirtualNode = true;
|
||||
this.serialize_widgets = true;
|
||||
const nextShortcutChar = BOOKMARKS_SERVICE.getNextShortcut();
|
||||
this.addWidget("text", "shortcut_key", nextShortcutChar, (value, ...args) => {
|
||||
value = value.trim()[0] || "1";
|
||||
}, {
|
||||
y: 8,
|
||||
});
|
||||
this.addWidget("number", "zoom", 1, (value) => { }, {
|
||||
y: 8 + LiteGraph.NODE_WIDGET_HEIGHT + 4,
|
||||
max: 2,
|
||||
min: 0.5,
|
||||
precision: 2,
|
||||
});
|
||||
this.keypressBound = this.onKeypress.bind(this);
|
||||
this.title = "🔖";
|
||||
this.onConstructed();
|
||||
}
|
||||
get shortcutKey() {
|
||||
var _a, _b, _c;
|
||||
return (_c = (_b = (_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.toLocaleLowerCase()) !== null && _c !== void 0 ? _c : "";
|
||||
}
|
||||
onAdded(graph) {
|
||||
KEY_EVENT_SERVICE.addEventListener("keydown", this.keypressBound);
|
||||
}
|
||||
onRemoved() {
|
||||
KEY_EVENT_SERVICE.removeEventListener("keydown", this.keypressBound);
|
||||
}
|
||||
onKeypress(event) {
|
||||
const originalEvent = event.detail.originalEvent;
|
||||
const target = originalEvent.target;
|
||||
if (getClosestOrSelf(target, 'input,textarea,[contenteditable="true"]')) {
|
||||
return;
|
||||
}
|
||||
if (KEY_EVENT_SERVICE.areOnlyKeysDown(this.widgets[0].value, true)) {
|
||||
this.canvasToBookmark();
|
||||
originalEvent.preventDefault();
|
||||
originalEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
onMouseDown(event, pos, graphCanvas) {
|
||||
var _a;
|
||||
const input = query(".graphdialog > input.value");
|
||||
if (input && input.value === ((_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value)) {
|
||||
input.addEventListener("keydown", (e) => {
|
||||
KEY_EVENT_SERVICE.handleKeyDownOrUp(e);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
input.value = Object.keys(KEY_EVENT_SERVICE.downKeys).join(" + ");
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
async canvasToBookmark() {
|
||||
var _a, _b;
|
||||
const canvas = app.canvas;
|
||||
if (this.graph !== app.canvas.getCurrentGraph()) {
|
||||
const subgraph = this.graph;
|
||||
const fromNode = findFromNodeForSubgraph(subgraph.id);
|
||||
canvas.openSubgraph(subgraph, fromNode);
|
||||
await wait(16);
|
||||
}
|
||||
if ((_a = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _a === void 0 ? void 0 : _a.offset) {
|
||||
canvas.ds.offset[0] = -this.pos[0] + 16;
|
||||
canvas.ds.offset[1] = -this.pos[1] + 40;
|
||||
}
|
||||
if (((_b = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _b === void 0 ? void 0 : _b.scale) != null) {
|
||||
canvas.ds.scale = Number(this.widgets[1].value || 1);
|
||||
}
|
||||
canvas.setDirty(true, true);
|
||||
}
|
||||
}
|
||||
Bookmark.type = NodeTypesString.BOOKMARK;
|
||||
Bookmark.title = NodeTypesString.BOOKMARK;
|
||||
Bookmark.slot_start_y = -20;
|
||||
app.registerExtension({
|
||||
name: "rgthree.Bookmark",
|
||||
registerCustomNodes() {
|
||||
Bookmark.setUp();
|
||||
},
|
||||
});
|
||||
45
custom_nodes/rgthree-comfy/web/comfyui/bypasser.js
Normal file
45
custom_nodes/rgthree-comfy/web/comfyui/bypasser.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { BaseNodeModeChanger } from "./base_node_mode_changer.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
const MODE_BYPASS = 4;
|
||||
const MODE_ALWAYS = 0;
|
||||
class BypasserNode extends BaseNodeModeChanger {
|
||||
constructor(title = BypasserNode.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.FAST_BYPASSER;
|
||||
this.modeOn = MODE_ALWAYS;
|
||||
this.modeOff = MODE_BYPASS;
|
||||
this.onConstructed();
|
||||
}
|
||||
async handleAction(action) {
|
||||
if (action === "Bypass all") {
|
||||
for (const widget of this.widgets || []) {
|
||||
this.forceWidgetOff(widget, true);
|
||||
}
|
||||
}
|
||||
else if (action === "Enable all") {
|
||||
for (const widget of this.widgets || []) {
|
||||
this.forceWidgetOn(widget, true);
|
||||
}
|
||||
}
|
||||
else if (action === "Toggle all") {
|
||||
for (const widget of this.widgets || []) {
|
||||
this.forceWidgetToggle(widget, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BypasserNode.exposedActions = ["Bypass all", "Enable all", "Toggle all"];
|
||||
BypasserNode.type = NodeTypesString.FAST_BYPASSER;
|
||||
BypasserNode.title = NodeTypesString.FAST_BYPASSER;
|
||||
app.registerExtension({
|
||||
name: "rgthree.Bypasser",
|
||||
registerCustomNodes() {
|
||||
BypasserNode.setUp();
|
||||
},
|
||||
loadedGraphNode(node) {
|
||||
if (node.type == BypasserNode.title) {
|
||||
node._tempWidth = node.size[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
197
custom_nodes/rgthree-comfy/web/comfyui/comfy_ui_bar.js
Normal file
197
custom_nodes/rgthree-comfy/web/comfyui/comfy_ui_bar.js
Normal file
@@ -0,0 +1,197 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { iconGear, iconStarFilled, logoRgthreeAsync } from "../../rgthree/common/media/svgs.js";
|
||||
import { $el, empty } from "../../rgthree/common/utils_dom.js";
|
||||
import { SERVICE as BOOKMARKS_SERVICE } from "./services/bookmarks_services.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
import { RgthreeConfigDialog } from "./config.js";
|
||||
import { wait } from "../../rgthree/common/shared_utils.js";
|
||||
let rgthreeButtonGroup = null;
|
||||
function addRgthreeTopBarButtons() {
|
||||
var _a, _b, _c;
|
||||
if (!CONFIG_SERVICE.getFeatureValue("comfy_top_bar_menu.enabled")) {
|
||||
if ((_a = rgthreeButtonGroup === null || rgthreeButtonGroup === void 0 ? void 0 : rgthreeButtonGroup.element) === null || _a === void 0 ? void 0 : _a.parentElement) {
|
||||
rgthreeButtonGroup.element.parentElement.removeChild(rgthreeButtonGroup.element);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (rgthreeButtonGroup) {
|
||||
(_b = app.menu) === null || _b === void 0 ? void 0 : _b.settingsGroup.element.before(rgthreeButtonGroup.element);
|
||||
return;
|
||||
}
|
||||
const buttons = [];
|
||||
const rgthreeButton = new RgthreeComfyButton({
|
||||
icon: "<svg></svg>",
|
||||
tooltip: "rgthree-comfy",
|
||||
primary: true,
|
||||
enabled: true,
|
||||
classList: "comfyui-button comfyui-menu-mobile-collapse primary",
|
||||
});
|
||||
buttons.push(rgthreeButton);
|
||||
logoRgthreeAsync().then((t) => {
|
||||
rgthreeButton.setIcon(t);
|
||||
});
|
||||
rgthreeButton.withPopup(new RgthreeComfyPopup({ target: rgthreeButton.element }, $el("menu.rgthree-menu.rgthree-top-menu", {
|
||||
children: [
|
||||
$el("li", {
|
||||
child: $el("button.rgthree-button-reset", {
|
||||
html: iconGear + "Settings (rgthree-comfy)",
|
||||
onclick: () => new RgthreeConfigDialog().show(),
|
||||
}),
|
||||
}),
|
||||
$el("li", {
|
||||
child: $el("button.rgthree-button-reset", {
|
||||
html: iconStarFilled + "Star on Github",
|
||||
onclick: () => window.open("https://github.com/rgthree/rgthree-comfy", "_blank"),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})), "click");
|
||||
if (CONFIG_SERVICE.getFeatureValue("comfy_top_bar_menu.button_bookmarks.enabled")) {
|
||||
const bookmarksListEl = $el("menu.rgthree-menu.rgthree-top-menu");
|
||||
bookmarksListEl.appendChild($el("li.rgthree-message", {
|
||||
child: $el("span", { text: "No bookmarks in current workflow." }),
|
||||
}));
|
||||
const bookmarksButton = new RgthreeComfyButton({
|
||||
icon: "bookmark",
|
||||
tooltip: "Workflow Bookmarks (rgthree-comfy)",
|
||||
});
|
||||
const bookmarksPopup = new RgthreeComfyPopup({ target: bookmarksButton.element, modal: false }, bookmarksListEl);
|
||||
bookmarksPopup.onOpen(() => {
|
||||
const bookmarks = BOOKMARKS_SERVICE.getCurrentBookmarks();
|
||||
empty(bookmarksListEl);
|
||||
if (bookmarks.length) {
|
||||
for (const b of bookmarks) {
|
||||
bookmarksListEl.appendChild($el("li", {
|
||||
child: $el("button.rgthree-button-reset", {
|
||||
text: `[${b.shortcutKey}] ${b.title}`,
|
||||
onclick: () => {
|
||||
b.canvasToBookmark();
|
||||
},
|
||||
}),
|
||||
}));
|
||||
}
|
||||
}
|
||||
else {
|
||||
bookmarksListEl.appendChild($el("li.rgthree-message", {
|
||||
child: $el("span", { text: "No bookmarks in current workflow." }),
|
||||
}));
|
||||
}
|
||||
});
|
||||
bookmarksButton.withPopup(bookmarksPopup, "hover");
|
||||
buttons.push(bookmarksButton);
|
||||
}
|
||||
rgthreeButtonGroup = new RgthreeComfyButtonGroup(...buttons);
|
||||
(_c = app.menu) === null || _c === void 0 ? void 0 : _c.settingsGroup.element.before(rgthreeButtonGroup.element);
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "rgthree.TopMenu",
|
||||
async setup() {
|
||||
addRgthreeTopBarButtons();
|
||||
CONFIG_SERVICE.addEventListener("config-change", ((e) => {
|
||||
var _a, _b;
|
||||
if ((_b = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.key) === null || _b === void 0 ? void 0 : _b.includes("features.comfy_top_bar_menu")) {
|
||||
addRgthreeTopBarButtons();
|
||||
}
|
||||
}));
|
||||
},
|
||||
});
|
||||
class RgthreeComfyButtonGroup {
|
||||
constructor(...buttons) {
|
||||
this.element = $el("div.rgthree-comfybar-top-button-group");
|
||||
this.buttons = buttons;
|
||||
this.update();
|
||||
}
|
||||
insert(button, index) {
|
||||
this.buttons.splice(index, 0, button);
|
||||
this.update();
|
||||
}
|
||||
append(button) {
|
||||
this.buttons.push(button);
|
||||
this.update();
|
||||
}
|
||||
remove(indexOrButton) {
|
||||
if (typeof indexOrButton !== "number") {
|
||||
indexOrButton = this.buttons.indexOf(indexOrButton);
|
||||
}
|
||||
if (indexOrButton > -1) {
|
||||
const btn = this.buttons.splice(indexOrButton, 1);
|
||||
this.update();
|
||||
return btn;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
update() {
|
||||
this.element.replaceChildren(...this.buttons.map((b) => { var _a; return (_a = b["element"]) !== null && _a !== void 0 ? _a : b; }));
|
||||
}
|
||||
}
|
||||
class RgthreeComfyButton {
|
||||
constructor(opts) {
|
||||
this.element = $el("button.rgthree-comfybar-top-button.rgthree-button-reset.rgthree-button");
|
||||
this.iconElement = $el("span.rgthree-button-icon");
|
||||
opts.icon && this.setIcon(opts.icon);
|
||||
opts.tooltip && this.element.setAttribute("title", opts.tooltip);
|
||||
opts.primary && this.element.classList.add("-primary");
|
||||
}
|
||||
setIcon(iconOrMarkup) {
|
||||
const markup = iconOrMarkup.startsWith("<")
|
||||
? iconOrMarkup
|
||||
: `<i class="mdi mdi-${iconOrMarkup}"></i>`;
|
||||
this.iconElement.innerHTML = markup;
|
||||
if (!this.iconElement.parentElement) {
|
||||
this.element.appendChild(this.iconElement);
|
||||
}
|
||||
}
|
||||
withPopup(popup, trigger) {
|
||||
if (trigger === "click") {
|
||||
this.element.addEventListener("click", () => {
|
||||
popup.open();
|
||||
});
|
||||
}
|
||||
if (trigger === "hover") {
|
||||
this.element.addEventListener("pointerenter", () => {
|
||||
popup.open();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
class RgthreeComfyPopup {
|
||||
constructor(opts, element) {
|
||||
this.onOpenFn = null;
|
||||
this.onWindowClickBound = this.onWindowClick.bind(this);
|
||||
this.element = element;
|
||||
this.opts = opts;
|
||||
opts.target && (this.target = opts.target);
|
||||
opts.modal && this.element.classList.add("-modal");
|
||||
}
|
||||
async open() {
|
||||
if (!this.target) {
|
||||
throw new Error("No target for RgthreeComfyPopup");
|
||||
}
|
||||
if (this.onOpenFn) {
|
||||
await this.onOpenFn();
|
||||
}
|
||||
await wait(16);
|
||||
const rect = this.target.getBoundingClientRect();
|
||||
this.element.setAttribute("state", "measuring");
|
||||
document.body.appendChild(this.element);
|
||||
this.element.style.position = "fixed";
|
||||
this.element.style.left = `${rect.left}px`;
|
||||
this.element.style.top = `${rect.top + rect.height}px`;
|
||||
this.element.setAttribute("state", "open");
|
||||
if (this.opts.modal) {
|
||||
document.body.classList.add("rgthree-modal-menu-open");
|
||||
}
|
||||
window.addEventListener("click", this.onWindowClickBound);
|
||||
}
|
||||
close() {
|
||||
this.element.remove();
|
||||
document.body.classList.remove("rgthree-modal-menu-open");
|
||||
window.removeEventListener("click", this.onWindowClickBound);
|
||||
}
|
||||
onOpen(fn) {
|
||||
this.onOpenFn = fn;
|
||||
}
|
||||
onWindowClick() {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
369
custom_nodes/rgthree-comfy/web/comfyui/config.js
Normal file
369
custom_nodes/rgthree-comfy/web/comfyui/config.js
Normal file
@@ -0,0 +1,369 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeDialog } from "../../rgthree/common/dialog.js";
|
||||
import { createElement as $el, queryAll as $$ } from "../../rgthree/common/utils_dom.js";
|
||||
import { checkmark, logoRgthree } from "../../rgthree/common/media/svgs.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
var ConfigType;
|
||||
(function (ConfigType) {
|
||||
ConfigType[ConfigType["UNKNOWN"] = 0] = "UNKNOWN";
|
||||
ConfigType[ConfigType["BOOLEAN"] = 1] = "BOOLEAN";
|
||||
ConfigType[ConfigType["STRING"] = 2] = "STRING";
|
||||
ConfigType[ConfigType["NUMBER"] = 3] = "NUMBER";
|
||||
ConfigType[ConfigType["ARRAY"] = 4] = "ARRAY";
|
||||
})(ConfigType || (ConfigType = {}));
|
||||
var ConfigInputType;
|
||||
(function (ConfigInputType) {
|
||||
ConfigInputType[ConfigInputType["UNKNOWN"] = 0] = "UNKNOWN";
|
||||
ConfigInputType[ConfigInputType["CHECKLIST"] = 1] = "CHECKLIST";
|
||||
})(ConfigInputType || (ConfigInputType = {}));
|
||||
const TYPE_TO_STRING = {
|
||||
[ConfigType.UNKNOWN]: "unknown",
|
||||
[ConfigType.BOOLEAN]: "boolean",
|
||||
[ConfigType.STRING]: "string",
|
||||
[ConfigType.NUMBER]: "number",
|
||||
[ConfigType.ARRAY]: "array",
|
||||
};
|
||||
const CONFIGURABLE = {
|
||||
features: [
|
||||
{
|
||||
key: "features.progress_bar.enabled",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "Prompt Progress Bar",
|
||||
description: `Shows a minimal progress bar for nodes and steps at the top of the app.`,
|
||||
subconfig: [
|
||||
{
|
||||
key: "features.progress_bar.height",
|
||||
type: ConfigType.NUMBER,
|
||||
label: "Height of the bar",
|
||||
},
|
||||
{
|
||||
key: "features.progress_bar.position",
|
||||
type: ConfigType.STRING,
|
||||
label: "Position at top or bottom of window",
|
||||
options: ["top", "bottom"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "features.import_individual_nodes.enabled",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "Import Individual Nodes Widgets",
|
||||
description: "Dragging & Dropping a similar image/JSON workflow onto (most) current workflow nodes" +
|
||||
"will allow you to import that workflow's node's widgets when it has the same " +
|
||||
"id and type. This is useful when you have several images and you'd like to import just " +
|
||||
"one part of a previous iteration, like a seed, or prompt.",
|
||||
},
|
||||
],
|
||||
menus: [
|
||||
{
|
||||
key: "features.comfy_top_bar_menu.enabled",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "Enable Top Bar Menu",
|
||||
description: "Have quick access from ComfyUI's new top bar to rgthree-comfy bookmarks, settings " +
|
||||
"(and more to come).",
|
||||
},
|
||||
{
|
||||
key: "features.menu_queue_selected_nodes",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "Show 'Queue Selected Output Nodes'",
|
||||
description: "Will show a menu item in the right-click context menus to queue (only) the selected " +
|
||||
"output nodes.",
|
||||
},
|
||||
{
|
||||
key: "features.menu_auto_nest.subdirs",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "Auto Nest Subdirectories in Menus",
|
||||
description: "When a large, flat list of values contain sub-directories, auto nest them. (Like, for " +
|
||||
"a large list of checkpoints).",
|
||||
subconfig: [
|
||||
{
|
||||
key: "features.menu_auto_nest.threshold",
|
||||
type: ConfigType.NUMBER,
|
||||
label: "Number of items needed to trigger nesting.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "features.menu_bookmarks.enabled",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "Show Bookmarks in context menu",
|
||||
description: "Will list bookmarks in the rgthree-comfy right-click context menu.",
|
||||
},
|
||||
],
|
||||
groups: [
|
||||
{
|
||||
key: "features.group_header_fast_toggle.enabled",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "Show fast toggles in Group Headers",
|
||||
description: "Show quick toggles in Groups' Headers to quickly mute, bypass or queue.",
|
||||
subconfig: [
|
||||
{
|
||||
key: "features.group_header_fast_toggle.toggles",
|
||||
type: ConfigType.ARRAY,
|
||||
label: "Which toggles to show.",
|
||||
inputType: ConfigInputType.CHECKLIST,
|
||||
options: [
|
||||
{ value: "queue", label: "queue" },
|
||||
{ value: "bypass", label: "bypass" },
|
||||
{ value: "mute", label: "mute" },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "features.group_header_fast_toggle.show",
|
||||
type: ConfigType.STRING,
|
||||
label: "When to show them.",
|
||||
options: [
|
||||
{ value: "hover", label: "on hover" },
|
||||
{ value: "always", label: "always" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
advanced: [
|
||||
{
|
||||
key: "features.show_alerts_for_corrupt_workflows",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "Detect Corrupt Workflows",
|
||||
description: "Will show a message at the top of the screen when loading a workflow that has " +
|
||||
"corrupt linking data.",
|
||||
},
|
||||
{
|
||||
key: "log_level",
|
||||
type: ConfigType.STRING,
|
||||
label: "Log level for browser dev console.",
|
||||
description: "Further down the list, the more verbose logs to the console will be. For instance, " +
|
||||
"selecting 'IMPORTANT' means only important message will be logged to the browser " +
|
||||
"console, while selecting 'WARN' will log all messages at or higher than WARN, including " +
|
||||
"'ERROR' and 'IMPORTANT' etc.",
|
||||
options: ["IMPORTANT", "ERROR", "WARN", "INFO", "DEBUG", "DEV"],
|
||||
isDevOnly: true,
|
||||
onSave: function (value) {
|
||||
rgthree.setLogLevel(value);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "features.invoke_extensions_async.node_created",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "Allow other extensions to call nodeCreated on rgthree-nodes.",
|
||||
isDevOnly: true,
|
||||
description: "Do not disable unless you are having trouble (and then file an issue at rgthree-comfy)." +
|
||||
"Prior to Apr 2024 it was not possible for other extensions to invoke their nodeCreated " +
|
||||
"event on some rgthree-comfy nodes. Now it's possible and this option is only here in " +
|
||||
"for easy if something is wrong.",
|
||||
},
|
||||
],
|
||||
};
|
||||
function fieldrow(item) {
|
||||
var _a;
|
||||
const initialValue = CONFIG_SERVICE.getConfigValue(item.key);
|
||||
const container = $el(`div.fieldrow.-type-${TYPE_TO_STRING[item.type]}`, {
|
||||
dataset: {
|
||||
name: item.key,
|
||||
initial: initialValue,
|
||||
type: item.type,
|
||||
},
|
||||
});
|
||||
$el(`label[for="${item.key}"]`, {
|
||||
children: [
|
||||
$el(`span[text="${item.label}"]`),
|
||||
item.description ? $el("small", { html: item.description }) : null,
|
||||
],
|
||||
parent: container,
|
||||
});
|
||||
let input;
|
||||
if ((_a = item.options) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
if (item.inputType === ConfigInputType.CHECKLIST) {
|
||||
const initialValueList = initialValue || [];
|
||||
input = $el(`fieldset.rgthree-checklist-group[id="${item.key}"]`, {
|
||||
parent: container,
|
||||
children: item.options.map((o) => {
|
||||
const label = o.label || String(o);
|
||||
const value = o.value || o;
|
||||
const id = `${item.key}_${value}`;
|
||||
return $el(`span.rgthree-checklist-item`, {
|
||||
children: [
|
||||
$el(`input[type="checkbox"][value="${value}"]`, {
|
||||
id,
|
||||
checked: initialValueList.includes(value),
|
||||
}),
|
||||
$el(`label`, {
|
||||
for: id,
|
||||
text: label,
|
||||
})
|
||||
]
|
||||
});
|
||||
}),
|
||||
});
|
||||
}
|
||||
else {
|
||||
input = $el(`select[id="${item.key}"]`, {
|
||||
parent: container,
|
||||
children: item.options.map((o) => {
|
||||
const label = o.label || String(o);
|
||||
const value = o.value || o;
|
||||
const valueSerialized = JSON.stringify({ value: value });
|
||||
return $el(`option[value="${valueSerialized}"]`, {
|
||||
text: label,
|
||||
selected: valueSerialized === JSON.stringify({ value: initialValue }),
|
||||
});
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (item.type === ConfigType.BOOLEAN) {
|
||||
container.classList.toggle("-checked", !!initialValue);
|
||||
input = $el(`input[type="checkbox"][id="${item.key}"]`, {
|
||||
parent: container,
|
||||
checked: initialValue,
|
||||
});
|
||||
}
|
||||
else {
|
||||
input = $el(`input[id="${item.key}"]`, {
|
||||
parent: container,
|
||||
value: initialValue,
|
||||
});
|
||||
}
|
||||
$el("div.fieldrow-value", { children: [input], parent: container });
|
||||
return container;
|
||||
}
|
||||
export class RgthreeConfigDialog extends RgthreeDialog {
|
||||
constructor() {
|
||||
const content = $el("div");
|
||||
content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["features"], "Features"));
|
||||
content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["menus"], "Menus"));
|
||||
content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["groups"], "Groups"));
|
||||
content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["advanced"], "Advanced"));
|
||||
content.addEventListener("input", (e) => {
|
||||
const changed = this.getChangedFormData();
|
||||
$$(".save-button", this.element)[0].disabled =
|
||||
!Object.keys(changed).length;
|
||||
});
|
||||
content.addEventListener("change", (e) => {
|
||||
const changed = this.getChangedFormData();
|
||||
$$(".save-button", this.element)[0].disabled =
|
||||
!Object.keys(changed).length;
|
||||
});
|
||||
const dialogOptions = {
|
||||
class: "-iconed -settings",
|
||||
title: logoRgthree + `<h2>Settings - rgthree-comfy</h2>`,
|
||||
content,
|
||||
onBeforeClose: () => {
|
||||
const changed = this.getChangedFormData();
|
||||
if (Object.keys(changed).length) {
|
||||
return confirm("Looks like there are unsaved changes. Are you sure you want close?");
|
||||
}
|
||||
return true;
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
label: "Save",
|
||||
disabled: true,
|
||||
className: "rgthree-button save-button -blue",
|
||||
callback: async (e) => {
|
||||
var _a, _b;
|
||||
const changed = this.getChangedFormData();
|
||||
if (!Object.keys(changed).length) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
const success = await CONFIG_SERVICE.setConfigValues(changed);
|
||||
if (success) {
|
||||
for (const key of Object.keys(changed)) {
|
||||
(_b = (_a = Object.values(CONFIGURABLE)
|
||||
.flat()
|
||||
.find((f) => f.key === key)) === null || _a === void 0 ? void 0 : _a.onSave) === null || _b === void 0 ? void 0 : _b.call(_a, changed[key]);
|
||||
}
|
||||
this.close();
|
||||
rgthree.showMessage({
|
||||
id: "config-success",
|
||||
message: `${checkmark} Successfully saved rgthree-comfy settings!`,
|
||||
timeout: 4000,
|
||||
});
|
||||
$$(".save-button", this.element)[0].disabled = true;
|
||||
}
|
||||
else {
|
||||
alert("There was an error saving rgthree-comfy configuration.");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
super(dialogOptions);
|
||||
}
|
||||
static buildFieldset(datas, label) {
|
||||
const fieldset = $el(`fieldset`, { children: [$el(`legend[text="${label}"]`)] });
|
||||
for (const data of datas) {
|
||||
if (data.isDevOnly && !rgthree.isDevMode()) {
|
||||
continue;
|
||||
}
|
||||
const container = $el("div.formrow");
|
||||
container.appendChild(fieldrow(data));
|
||||
if (data.subconfig) {
|
||||
for (const subfeature of data.subconfig) {
|
||||
container.appendChild(fieldrow(subfeature));
|
||||
}
|
||||
}
|
||||
fieldset.appendChild(container);
|
||||
}
|
||||
return fieldset;
|
||||
}
|
||||
getChangedFormData() {
|
||||
return $$("[data-name]", this.contentElement).reduce((acc, el) => {
|
||||
const name = el.dataset["name"];
|
||||
const type = el.dataset["type"];
|
||||
const initialValue = CONFIG_SERVICE.getConfigValue(name);
|
||||
let currentValueEl = $$("fieldset.rgthree-checklist-group, input, textarea, select", el)[0];
|
||||
let currentValue = null;
|
||||
if (type === String(ConfigType.BOOLEAN)) {
|
||||
currentValue = currentValueEl.checked;
|
||||
el.classList.toggle("-checked", currentValue);
|
||||
}
|
||||
else {
|
||||
currentValue = currentValueEl === null || currentValueEl === void 0 ? void 0 : currentValueEl.value;
|
||||
if (currentValueEl.nodeName === "SELECT") {
|
||||
currentValue = JSON.parse(currentValue).value;
|
||||
}
|
||||
else if (currentValueEl.classList.contains('rgthree-checklist-group')) {
|
||||
currentValue = [];
|
||||
for (const check of $$('input[type="checkbox"]', currentValueEl)) {
|
||||
if (check.checked) {
|
||||
currentValue.push(check.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type === String(ConfigType.NUMBER)) {
|
||||
currentValue = Number(currentValue) || initialValue;
|
||||
}
|
||||
}
|
||||
if (JSON.stringify(currentValue) !== JSON.stringify(initialValue)) {
|
||||
acc[name] = currentValue;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
app.ui.settings.addSetting({
|
||||
id: "rgthree.config",
|
||||
defaultValue: null,
|
||||
name: "Open rgthree-comfy config",
|
||||
type: () => {
|
||||
return $el("tr.rgthree-comfyui-settings-row", {
|
||||
children: [
|
||||
$el("td", {
|
||||
child: `<div>${logoRgthree} [rgthree-comfy] configuration / settings</div>`,
|
||||
}),
|
||||
$el("td", {
|
||||
child: $el('button.rgthree-button.-blue[text="rgthree-comfy settings"]', {
|
||||
events: {
|
||||
click: (e) => {
|
||||
new RgthreeConfigDialog().show();
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
62
custom_nodes/rgthree-comfy/web/comfyui/constants.js
Normal file
62
custom_nodes/rgthree-comfy/web/comfyui/constants.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
export function addRgthree(str) {
|
||||
return str + " (rgthree)";
|
||||
}
|
||||
export function stripRgthree(str) {
|
||||
return str.replace(/\s*\(rgthree\)$/, "");
|
||||
}
|
||||
export const NodeTypesString = {
|
||||
ANY_SWITCH: addRgthree("Any Switch"),
|
||||
CONTEXT: addRgthree("Context"),
|
||||
CONTEXT_BIG: addRgthree("Context Big"),
|
||||
CONTEXT_SWITCH: addRgthree("Context Switch"),
|
||||
CONTEXT_SWITCH_BIG: addRgthree("Context Switch Big"),
|
||||
CONTEXT_MERGE: addRgthree("Context Merge"),
|
||||
CONTEXT_MERGE_BIG: addRgthree("Context Merge Big"),
|
||||
DYNAMIC_CONTEXT: addRgthree("Dynamic Context"),
|
||||
DYNAMIC_CONTEXT_SWITCH: addRgthree("Dynamic Context Switch"),
|
||||
DISPLAY_ANY: addRgthree("Display Any"),
|
||||
IMAGE_OR_LATENT_SIZE: addRgthree("Image or Latent Size"),
|
||||
NODE_MODE_RELAY: addRgthree("Mute / Bypass Relay"),
|
||||
NODE_MODE_REPEATER: addRgthree("Mute / Bypass Repeater"),
|
||||
FAST_MUTER: addRgthree("Fast Muter"),
|
||||
FAST_BYPASSER: addRgthree("Fast Bypasser"),
|
||||
FAST_GROUPS_MUTER: addRgthree("Fast Groups Muter"),
|
||||
FAST_GROUPS_BYPASSER: addRgthree("Fast Groups Bypasser"),
|
||||
FAST_ACTIONS_BUTTON: addRgthree("Fast Actions Button"),
|
||||
LABEL: addRgthree("Label"),
|
||||
POWER_PRIMITIVE: addRgthree("Power Primitive"),
|
||||
POWER_PROMPT: addRgthree("Power Prompt"),
|
||||
POWER_PROMPT_SIMPLE: addRgthree("Power Prompt - Simple"),
|
||||
POWER_PUTER: addRgthree("Power Puter"),
|
||||
POWER_CONDUCTOR: addRgthree("Power Conductor"),
|
||||
SDXL_EMPTY_LATENT_IMAGE: addRgthree("SDXL Empty Latent Image"),
|
||||
SDXL_POWER_PROMPT_POSITIVE: addRgthree("SDXL Power Prompt - Positive"),
|
||||
SDXL_POWER_PROMPT_NEGATIVE: addRgthree("SDXL Power Prompt - Simple / Negative"),
|
||||
POWER_LORA_LOADER: addRgthree("Power Lora Loader"),
|
||||
KSAMPLER_CONFIG: addRgthree("KSampler Config"),
|
||||
NODE_COLLECTOR: addRgthree("Node Collector"),
|
||||
REROUTE: addRgthree("Reroute"),
|
||||
RANDOM_UNMUTER: addRgthree("Random Unmuter"),
|
||||
SEED: addRgthree("Seed"),
|
||||
BOOKMARK: addRgthree("Bookmark"),
|
||||
IMAGE_COMPARER: addRgthree("Image Comparer"),
|
||||
IMAGE_INSET_CROP: addRgthree("Image Inset Crop"),
|
||||
};
|
||||
const UNRELEASED_KEYS = {
|
||||
[NodeTypesString.DYNAMIC_CONTEXT]: "dynamic_context",
|
||||
[NodeTypesString.DYNAMIC_CONTEXT_SWITCH]: "dynamic_context",
|
||||
[NodeTypesString.POWER_CONDUCTOR]: "power_conductor",
|
||||
};
|
||||
export function getNodeTypeStrings() {
|
||||
const unreleasedKeys = Object.keys(UNRELEASED_KEYS);
|
||||
return Object.values(NodeTypesString)
|
||||
.map((i) => stripRgthree(i))
|
||||
.filter((i) => {
|
||||
if (unreleasedKeys.includes(i)) {
|
||||
return !!CONFIG_SERVICE.getConfigValue(`unreleased.${UNRELEASED_KEYS[i]}.enabled`);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.sort();
|
||||
}
|
||||
322
custom_nodes/rgthree-comfy/web/comfyui/context.js
Normal file
322
custom_nodes/rgthree-comfy/web/comfyui/context.js
Normal file
@@ -0,0 +1,322 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { IoDirection, addConnectionLayoutSupport, addMenuItem, matchLocalSlotsToServer, replaceNode, } from "./utils.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
|
||||
import { debounce, wait } from "../../rgthree/common/shared_utils.js";
|
||||
import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
function findMatchingIndexByTypeOrName(otherNode, otherSlot, ctxSlots) {
|
||||
const otherNodeType = (otherNode.type || "").toUpperCase();
|
||||
const otherNodeName = (otherNode.title || "").toUpperCase();
|
||||
let otherSlotType = otherSlot.type;
|
||||
if (Array.isArray(otherSlotType) || otherSlotType.includes(",")) {
|
||||
otherSlotType = "COMBO";
|
||||
}
|
||||
const otherSlotName = otherSlot.name.toUpperCase().replace("OPT_", "").replace("_NAME", "");
|
||||
let ctxSlotIndex = -1;
|
||||
if (["CONDITIONING", "INT", "STRING", "FLOAT", "COMBO"].includes(otherSlotType)) {
|
||||
ctxSlotIndex = ctxSlots.findIndex((ctxSlot) => {
|
||||
const ctxSlotName = ctxSlot.name.toUpperCase().replace("OPT_", "").replace("_NAME", "");
|
||||
let ctxSlotType = ctxSlot.type;
|
||||
if (Array.isArray(ctxSlotType) || ctxSlotType.includes(",")) {
|
||||
ctxSlotType = "COMBO";
|
||||
}
|
||||
if (ctxSlotType !== otherSlotType) {
|
||||
return false;
|
||||
}
|
||||
if (ctxSlotName === otherSlotName ||
|
||||
(ctxSlotName === "SEED" && otherSlotName.includes("SEED")) ||
|
||||
(ctxSlotName === "STEP_REFINER" && otherSlotName.includes("AT_STEP")) ||
|
||||
(ctxSlotName === "STEP_REFINER" && otherSlotName.includes("REFINER_STEP"))) {
|
||||
return true;
|
||||
}
|
||||
if ((otherNodeType.includes("POSITIVE") || otherNodeName.includes("POSITIVE")) &&
|
||||
((ctxSlotName === "POSITIVE" && otherSlotType === "CONDITIONING") ||
|
||||
(ctxSlotName === "TEXT_POS_G" && otherSlotName.includes("TEXT_G")) ||
|
||||
(ctxSlotName === "TEXT_POS_L" && otherSlotName.includes("TEXT_L")))) {
|
||||
return true;
|
||||
}
|
||||
if ((otherNodeType.includes("NEGATIVE") || otherNodeName.includes("NEGATIVE")) &&
|
||||
((ctxSlotName === "NEGATIVE" && otherSlotType === "CONDITIONING") ||
|
||||
(ctxSlotName === "TEXT_NEG_G" && otherSlotName.includes("TEXT_G")) ||
|
||||
(ctxSlotName === "TEXT_NEG_L" && otherSlotName.includes("TEXT_L")))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else {
|
||||
ctxSlotIndex = ctxSlots.map((s) => s.type).indexOf(otherSlotType);
|
||||
}
|
||||
return ctxSlotIndex;
|
||||
}
|
||||
export class BaseContextNode extends RgthreeBaseServerNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
this.___collapsed_width = 0;
|
||||
}
|
||||
get _collapsed_width() {
|
||||
return this.___collapsed_width;
|
||||
}
|
||||
set _collapsed_width(width) {
|
||||
const canvas = app.canvas;
|
||||
const ctx = canvas.canvas.getContext("2d");
|
||||
const oldFont = ctx.font;
|
||||
ctx.font = canvas.title_text_font;
|
||||
let title = this.title.trim();
|
||||
this.___collapsed_width = 30 + (title ? 10 + ctx.measureText(title).width : 0);
|
||||
ctx.font = oldFont;
|
||||
}
|
||||
connectByType(slot, targetNode, targetSlotType, optsIn) {
|
||||
var _a;
|
||||
let canConnect = (_a = super.connectByType) === null || _a === void 0 ? void 0 : _a.call(this, slot, targetNode, targetSlotType, optsIn);
|
||||
if (!super.connectByType) {
|
||||
canConnect = LGraphNode.prototype.connectByType.call(this, slot, targetNode, targetSlotType, optsIn);
|
||||
}
|
||||
if (!canConnect && slot === 0) {
|
||||
const ctrlKey = KEY_EVENT_SERVICE.ctrlKey;
|
||||
for (const [index, input] of (targetNode.inputs || []).entries()) {
|
||||
if (input.link && !ctrlKey) {
|
||||
continue;
|
||||
}
|
||||
const thisOutputSlot = findMatchingIndexByTypeOrName(targetNode, input, this.outputs);
|
||||
if (thisOutputSlot > -1) {
|
||||
this.connect(thisOutputSlot, targetNode, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn) {
|
||||
var _a, _b;
|
||||
let canConnect = (_a = super.connectByTypeOutput) === null || _a === void 0 ? void 0 : _a.call(this, slot, sourceNode, sourceSlotType, optsIn);
|
||||
if (!super.connectByType) {
|
||||
canConnect = LGraphNode.prototype.connectByTypeOutput.call(this, slot, sourceNode, sourceSlotType, optsIn);
|
||||
}
|
||||
if (!canConnect && slot === 0) {
|
||||
const ctrlKey = KEY_EVENT_SERVICE.ctrlKey;
|
||||
for (const [index, output] of (sourceNode.outputs || []).entries()) {
|
||||
if (((_b = output.links) === null || _b === void 0 ? void 0 : _b.length) && !ctrlKey) {
|
||||
continue;
|
||||
}
|
||||
const thisInputSlot = findMatchingIndexByTypeOrName(sourceNode, output, this.inputs);
|
||||
if (thisInputSlot > -1) {
|
||||
sourceNode.connect(index, this, thisInputSlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static setUp(comfyClass, nodeData, ctxClass) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, ctxClass);
|
||||
wait(500).then(() => {
|
||||
LiteGraph.slot_types_default_out["RGTHREE_CONTEXT"] =
|
||||
LiteGraph.slot_types_default_out["RGTHREE_CONTEXT"] || [];
|
||||
LiteGraph.slot_types_default_out["RGTHREE_CONTEXT"].push(comfyClass.comfyClass);
|
||||
});
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, ctxClass) {
|
||||
addConnectionLayoutSupport(ctxClass, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
setTimeout(() => {
|
||||
ctxClass.category = comfyClass.category;
|
||||
});
|
||||
}
|
||||
}
|
||||
class ContextNode extends BaseContextNode {
|
||||
constructor(title = ContextNode.title) {
|
||||
super(title);
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
BaseContextNode.setUp(comfyClass, nodeData, ContextNode);
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, ctxClass) {
|
||||
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
|
||||
addMenuItem(ContextNode, app, {
|
||||
name: "Convert To Context Big",
|
||||
callback: (node) => {
|
||||
replaceNode(node, ContextBigNode.type);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
ContextNode.title = NodeTypesString.CONTEXT;
|
||||
ContextNode.type = NodeTypesString.CONTEXT;
|
||||
ContextNode.comfyClass = NodeTypesString.CONTEXT;
|
||||
class ContextBigNode extends BaseContextNode {
|
||||
constructor(title = ContextBigNode.title) {
|
||||
super(title);
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
BaseContextNode.setUp(comfyClass, nodeData, ContextBigNode);
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, ctxClass) {
|
||||
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
|
||||
addMenuItem(ContextBigNode, app, {
|
||||
name: "Convert To Context (Original)",
|
||||
callback: (node) => {
|
||||
replaceNode(node, ContextNode.type);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
ContextBigNode.title = NodeTypesString.CONTEXT_BIG;
|
||||
ContextBigNode.type = NodeTypesString.CONTEXT_BIG;
|
||||
ContextBigNode.comfyClass = NodeTypesString.CONTEXT_BIG;
|
||||
class BaseContextMultiCtxInputNode extends BaseContextNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
this.stabilizeBound = this.stabilize.bind(this);
|
||||
this.addContextInput(5);
|
||||
}
|
||||
addContextInput(num = 1) {
|
||||
for (let i = 0; i < num; i++) {
|
||||
this.addInput(`ctx_${String(this.inputs.length + 1).padStart(2, "0")}`, "RGTHREE_CONTEXT");
|
||||
}
|
||||
}
|
||||
onConnectionsChange(type, slotIndex, isConnected, link, ioSlot) {
|
||||
var _a;
|
||||
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.apply(this, [...arguments]);
|
||||
if (type === LiteGraph.INPUT) {
|
||||
this.scheduleStabilize();
|
||||
}
|
||||
}
|
||||
scheduleStabilize(ms = 64) {
|
||||
return debounce(this.stabilizeBound, 64);
|
||||
}
|
||||
stabilize() {
|
||||
removeUnusedInputsFromEnd(this, 4);
|
||||
this.addContextInput();
|
||||
}
|
||||
}
|
||||
class ContextSwitchNode extends BaseContextMultiCtxInputNode {
|
||||
constructor(title = ContextSwitchNode.title) {
|
||||
super(title);
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
BaseContextNode.setUp(comfyClass, nodeData, ContextSwitchNode);
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, ctxClass) {
|
||||
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
|
||||
addMenuItem(ContextSwitchNode, app, {
|
||||
name: "Convert To Context Switch Big",
|
||||
callback: (node) => {
|
||||
replaceNode(node, ContextSwitchBigNode.type);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
ContextSwitchNode.title = NodeTypesString.CONTEXT_SWITCH;
|
||||
ContextSwitchNode.type = NodeTypesString.CONTEXT_SWITCH;
|
||||
ContextSwitchNode.comfyClass = NodeTypesString.CONTEXT_SWITCH;
|
||||
class ContextSwitchBigNode extends BaseContextMultiCtxInputNode {
|
||||
constructor(title = ContextSwitchBigNode.title) {
|
||||
super(title);
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
BaseContextNode.setUp(comfyClass, nodeData, ContextSwitchBigNode);
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, ctxClass) {
|
||||
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
|
||||
addMenuItem(ContextSwitchBigNode, app, {
|
||||
name: "Convert To Context Switch",
|
||||
callback: (node) => {
|
||||
replaceNode(node, ContextSwitchNode.type);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
ContextSwitchBigNode.title = NodeTypesString.CONTEXT_SWITCH_BIG;
|
||||
ContextSwitchBigNode.type = NodeTypesString.CONTEXT_SWITCH_BIG;
|
||||
ContextSwitchBigNode.comfyClass = NodeTypesString.CONTEXT_SWITCH_BIG;
|
||||
class ContextMergeNode extends BaseContextMultiCtxInputNode {
|
||||
constructor(title = ContextMergeNode.title) {
|
||||
super(title);
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
BaseContextNode.setUp(comfyClass, nodeData, ContextMergeNode);
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, ctxClass) {
|
||||
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
|
||||
addMenuItem(ContextMergeNode, app, {
|
||||
name: "Convert To Context Merge Big",
|
||||
callback: (node) => {
|
||||
replaceNode(node, ContextMergeBigNode.type);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
ContextMergeNode.title = NodeTypesString.CONTEXT_MERGE;
|
||||
ContextMergeNode.type = NodeTypesString.CONTEXT_MERGE;
|
||||
ContextMergeNode.comfyClass = NodeTypesString.CONTEXT_MERGE;
|
||||
class ContextMergeBigNode extends BaseContextMultiCtxInputNode {
|
||||
constructor(title = ContextMergeBigNode.title) {
|
||||
super(title);
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
BaseContextNode.setUp(comfyClass, nodeData, ContextMergeBigNode);
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, ctxClass) {
|
||||
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
|
||||
addMenuItem(ContextMergeBigNode, app, {
|
||||
name: "Convert To Context Switch",
|
||||
callback: (node) => {
|
||||
replaceNode(node, ContextMergeNode.type);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
ContextMergeBigNode.title = NodeTypesString.CONTEXT_MERGE_BIG;
|
||||
ContextMergeBigNode.type = NodeTypesString.CONTEXT_MERGE_BIG;
|
||||
ContextMergeBigNode.comfyClass = NodeTypesString.CONTEXT_MERGE_BIG;
|
||||
const contextNodes = [
|
||||
ContextNode,
|
||||
ContextBigNode,
|
||||
ContextSwitchNode,
|
||||
ContextSwitchBigNode,
|
||||
ContextMergeNode,
|
||||
ContextMergeBigNode,
|
||||
];
|
||||
const contextTypeToServerDef = {};
|
||||
function fixBadConfigs(node) {
|
||||
const wrongName = node.outputs.find((o, i) => o.name === "CLIP_HEIGTH");
|
||||
if (wrongName) {
|
||||
wrongName.name = "CLIP_HEIGHT";
|
||||
}
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "rgthree.Context",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
for (const ctxClass of contextNodes) {
|
||||
if (nodeData.name === ctxClass.type) {
|
||||
contextTypeToServerDef[ctxClass.type] = nodeData;
|
||||
ctxClass.setUp(nodeType, nodeData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
async nodeCreated(node) {
|
||||
const type = node.type || node.constructor.type;
|
||||
const serverDef = type && contextTypeToServerDef[type];
|
||||
if (serverDef) {
|
||||
fixBadConfigs(node);
|
||||
matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
|
||||
if (!type.includes("Switch") && !type.includes("Merge")) {
|
||||
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode(node) {
|
||||
const type = node.type || node.constructor.type;
|
||||
const serverDef = type && contextTypeToServerDef[type];
|
||||
if (serverDef) {
|
||||
fixBadConfigs(node);
|
||||
matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
|
||||
if (!type.includes("Switch") && !type.includes("Merge")) {
|
||||
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
292
custom_nodes/rgthree-comfy/web/comfyui/dialog_info.js
Normal file
292
custom_nodes/rgthree-comfy/web/comfyui/dialog_info.js
Normal file
@@ -0,0 +1,292 @@
|
||||
import { RgthreeDialog } from "../../rgthree/common/dialog.js";
|
||||
import { createElement as $el, empty, appendChildren, getClosestOrSelf, query, queryAll, setAttributes, } from "../../rgthree/common/utils_dom.js";
|
||||
import { logoCivitai, link, pencilColored, diskColored, dotdotdot, } from "../../rgthree/common/media/svgs.js";
|
||||
import { CHECKPOINT_INFO_SERVICE, LORA_INFO_SERVICE } from "../../rgthree/common/model_info_service.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { MenuButton } from "../../rgthree/common/menu.js";
|
||||
import { generateId, injectCss } from "../../rgthree/common/shared_utils.js";
|
||||
class RgthreeInfoDialog extends RgthreeDialog {
|
||||
constructor(file) {
|
||||
const dialogOptions = {
|
||||
class: "rgthree-info-dialog",
|
||||
title: `<h2>Loading...</h2>`,
|
||||
content: "<center>Loading..</center>",
|
||||
onBeforeClose: () => {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
super(dialogOptions);
|
||||
this.modifiedModelData = false;
|
||||
this.modelInfo = null;
|
||||
this.init(file);
|
||||
}
|
||||
async init(file) {
|
||||
var _a, _b;
|
||||
const cssPromise = injectCss("rgthree/common/css/dialog_model_info.css");
|
||||
this.modelInfo = await this.getModelInfo(file);
|
||||
await cssPromise;
|
||||
this.setContent(this.getInfoContent());
|
||||
this.setTitle(((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a["name"]) || ((_b = this.modelInfo) === null || _b === void 0 ? void 0 : _b["file"]) || "Unknown");
|
||||
this.attachEvents();
|
||||
}
|
||||
getCloseEventDetail() {
|
||||
const detail = {
|
||||
dirty: this.modifiedModelData,
|
||||
};
|
||||
return { detail };
|
||||
}
|
||||
attachEvents() {
|
||||
this.contentElement.addEventListener("click", async (e) => {
|
||||
const target = getClosestOrSelf(e.target, "[data-action]");
|
||||
const action = target === null || target === void 0 ? void 0 : target.getAttribute("data-action");
|
||||
if (!target || !action) {
|
||||
return;
|
||||
}
|
||||
await this.handleEventAction(action, target, e);
|
||||
});
|
||||
}
|
||||
async handleEventAction(action, target, e) {
|
||||
var _a, _b;
|
||||
const info = this.modelInfo;
|
||||
if (!(info === null || info === void 0 ? void 0 : info.file)) {
|
||||
return;
|
||||
}
|
||||
if (action === "fetch-civitai") {
|
||||
this.modelInfo = await this.refreshModelInfo(info.file);
|
||||
this.setContent(this.getInfoContent());
|
||||
this.setTitle(((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a["name"]) || ((_b = this.modelInfo) === null || _b === void 0 ? void 0 : _b["file"]) || "Unknown");
|
||||
}
|
||||
else if (action === "copy-trained-words") {
|
||||
const selected = queryAll(".-rgthree-is-selected", target.closest("tr"));
|
||||
const text = selected.map((el) => el.getAttribute("data-word")).join(", ");
|
||||
await navigator.clipboard.writeText(text);
|
||||
rgthree.showMessage({
|
||||
id: "copy-trained-words-" + generateId(4),
|
||||
type: "success",
|
||||
message: `Successfully copied ${selected.length} key word${selected.length === 1 ? "" : "s"}.`,
|
||||
timeout: 4000,
|
||||
});
|
||||
}
|
||||
else if (action === "toggle-trained-word") {
|
||||
target === null || target === void 0 ? void 0 : target.classList.toggle("-rgthree-is-selected");
|
||||
const tr = target.closest("tr");
|
||||
if (tr) {
|
||||
const span = query("td:first-child > *", tr);
|
||||
let small = query("small", span);
|
||||
if (!small) {
|
||||
small = $el("small", { parent: span });
|
||||
}
|
||||
const num = queryAll(".-rgthree-is-selected", tr).length;
|
||||
small.innerHTML = num
|
||||
? `${num} selected | <span role="button" data-action="copy-trained-words">Copy</span>`
|
||||
: "";
|
||||
}
|
||||
}
|
||||
else if (action === "edit-row") {
|
||||
const tr = target.closest("tr");
|
||||
const td = query("td:nth-child(2)", tr);
|
||||
const input = td.querySelector("input,textarea");
|
||||
if (!input) {
|
||||
const fieldName = tr.dataset["fieldName"];
|
||||
tr.classList.add("-rgthree-editing");
|
||||
const isTextarea = fieldName === "userNote";
|
||||
const input = $el(`${isTextarea ? "textarea" : 'input[type="text"]'}`, {
|
||||
value: td.textContent,
|
||||
});
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (!isTextarea && e.key === "Enter") {
|
||||
const modified = saveEditableRow(info, tr, true);
|
||||
this.modifiedModelData = this.modifiedModelData || modified;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
else if (e.key === "Escape") {
|
||||
const modified = saveEditableRow(info, tr, false);
|
||||
this.modifiedModelData = this.modifiedModelData || modified;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
appendChildren(empty(td), [input]);
|
||||
input.focus();
|
||||
}
|
||||
else if (target.nodeName.toLowerCase() === "button") {
|
||||
const modified = saveEditableRow(info, tr, true);
|
||||
this.modifiedModelData = this.modifiedModelData || modified;
|
||||
}
|
||||
e === null || e === void 0 ? void 0 : e.preventDefault();
|
||||
e === null || e === void 0 ? void 0 : e.stopPropagation();
|
||||
}
|
||||
}
|
||||
getInfoContent() {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
||||
const info = this.modelInfo || {};
|
||||
const civitaiLink = (_a = info.links) === null || _a === void 0 ? void 0 : _a.find((i) => i.includes("civitai.com/models"));
|
||||
const html = `
|
||||
<ul class="rgthree-info-area">
|
||||
<li title="Type" class="rgthree-info-tag -type -type-${(info.type || "").toLowerCase()}"><span>${info.type || ""}</span></li>
|
||||
<li title="Base Model" class="rgthree-info-tag -basemodel -basemodel-${(info.baseModel || "").toLowerCase()}"><span>${info.baseModel || ""}</span></li>
|
||||
<li class="rgthree-info-menu" stub="menu"></li>
|
||||
${""}
|
||||
</ul>
|
||||
|
||||
<table class="rgthree-info-table">
|
||||
${infoTableRow("File", info.file || "")}
|
||||
${infoTableRow("Hash (sha256)", info.sha256 || "")}
|
||||
${civitaiLink
|
||||
? infoTableRow("Civitai", `<a href="${civitaiLink}" target="_blank">${logoCivitai}View on Civitai</a>`)
|
||||
: ((_c = (_b = info.raw) === null || _b === void 0 ? void 0 : _b.civitai) === null || _c === void 0 ? void 0 : _c.error) === "Model not found"
|
||||
? infoTableRow("Civitai", '<i>Model not found</i> <span class="-help" title="The model was not found on civitai with the sha256 hash. It\'s possible the model was removed, re-uploaded, or was never on civitai to begin with."></span>')
|
||||
: ((_e = (_d = info.raw) === null || _d === void 0 ? void 0 : _d.civitai) === null || _e === void 0 ? void 0 : _e.error)
|
||||
? infoTableRow("Civitai", (_g = (_f = info.raw) === null || _f === void 0 ? void 0 : _f.civitai) === null || _g === void 0 ? void 0 : _g.error)
|
||||
: !((_h = info.raw) === null || _h === void 0 ? void 0 : _h.civitai)
|
||||
? infoTableRow("Civitai", `<button class="rgthree-button" data-action="fetch-civitai">Fetch info from civitai</button>`)
|
||||
: ""}
|
||||
|
||||
${infoTableRow("Name", info.name || ((_k = (_j = info.raw) === null || _j === void 0 ? void 0 : _j.metadata) === null || _k === void 0 ? void 0 : _k.ss_output_name) || "", "The name for display.", "name")}
|
||||
|
||||
${!info.baseModelFile && !info.baseModelFile
|
||||
? ""
|
||||
: infoTableRow("Base Model", (info.baseModel || "") + (info.baseModelFile ? ` (${info.baseModelFile})` : ""))}
|
||||
|
||||
|
||||
${!((_l = info.trainedWords) === null || _l === void 0 ? void 0 : _l.length)
|
||||
? ""
|
||||
: infoTableRow("Trained Words", (_m = getTrainedWordsMarkup(info.trainedWords)) !== null && _m !== void 0 ? _m : "", "Trained words from the metadata and/or civitai. Click to select for copy.")}
|
||||
|
||||
${!((_p = (_o = info.raw) === null || _o === void 0 ? void 0 : _o.metadata) === null || _p === void 0 ? void 0 : _p.ss_clip_skip) || ((_r = (_q = info.raw) === null || _q === void 0 ? void 0 : _q.metadata) === null || _r === void 0 ? void 0 : _r.ss_clip_skip) == "None"
|
||||
? ""
|
||||
: infoTableRow("Clip Skip", (_t = (_s = info.raw) === null || _s === void 0 ? void 0 : _s.metadata) === null || _t === void 0 ? void 0 : _t.ss_clip_skip)}
|
||||
${infoTableRow("Strength Min", (_u = info.strengthMin) !== null && _u !== void 0 ? _u : "", "The recommended minimum strength, In the Power Lora Loader node, strength will signal when it is below this threshold.", "strengthMin")}
|
||||
${infoTableRow("Strength Max", (_v = info.strengthMax) !== null && _v !== void 0 ? _v : "", "The recommended maximum strength. In the Power Lora Loader node, strength will signal when it is above this threshold.", "strengthMax")}
|
||||
${""}
|
||||
${infoTableRow("Additional Notes", (_w = info.userNote) !== null && _w !== void 0 ? _w : "", "Additional notes you'd like to keep and reference in the info dialog.", "userNote")}
|
||||
|
||||
</table>
|
||||
|
||||
<ul class="rgthree-info-images">${(_y = (_x = info.images) === null || _x === void 0 ? void 0 : _x.map((img) => `
|
||||
<li>
|
||||
<figure>${img.type === 'video'
|
||||
? `<video src="${img.url}" autoplay loop></video>`
|
||||
: `<img src="${img.url}" />`}
|
||||
<figcaption><!--
|
||||
-->${imgInfoField("", img.civitaiUrl
|
||||
? `<a href="${img.civitaiUrl}" target="_blank">civitai${link}</a>`
|
||||
: undefined)}<!--
|
||||
-->${imgInfoField("seed", img.seed)}<!--
|
||||
-->${imgInfoField("steps", img.steps)}<!--
|
||||
-->${imgInfoField("cfg", img.cfg)}<!--
|
||||
-->${imgInfoField("sampler", img.sampler)}<!--
|
||||
-->${imgInfoField("model", img.model)}<!--
|
||||
-->${imgInfoField("positive", img.positive)}<!--
|
||||
-->${imgInfoField("negative", img.negative)}<!--
|
||||
--><!--${""}--></figcaption>
|
||||
</figure>
|
||||
</li>`).join("")) !== null && _y !== void 0 ? _y : ""}</ul>
|
||||
`;
|
||||
const div = $el("div", { html });
|
||||
if (rgthree.isDevMode()) {
|
||||
setAttributes(query('[stub="menu"]', div), {
|
||||
children: [
|
||||
new MenuButton({
|
||||
icon: dotdotdot,
|
||||
options: [
|
||||
{ label: "More Actions", type: "title" },
|
||||
{
|
||||
label: "Open API JSON",
|
||||
callback: async (e) => {
|
||||
var _a;
|
||||
if ((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a.file) {
|
||||
window.open(`rgthree/api/loras/info?file=${encodeURIComponent(this.modelInfo.file)}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Clear all local info",
|
||||
callback: async (e) => {
|
||||
var _a, _b, _c;
|
||||
if ((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a.file) {
|
||||
this.modelInfo = await LORA_INFO_SERVICE.clearFetchedInfo(this.modelInfo.file);
|
||||
this.setContent(this.getInfoContent());
|
||||
this.setTitle(((_b = this.modelInfo) === null || _b === void 0 ? void 0 : _b["name"]) || ((_c = this.modelInfo) === null || _c === void 0 ? void 0 : _c["file"]) || "Unknown");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
return div;
|
||||
}
|
||||
}
|
||||
export class RgthreeLoraInfoDialog extends RgthreeInfoDialog {
|
||||
async getModelInfo(file) {
|
||||
return LORA_INFO_SERVICE.getInfo(file, false, false);
|
||||
}
|
||||
async refreshModelInfo(file) {
|
||||
return LORA_INFO_SERVICE.refreshInfo(file);
|
||||
}
|
||||
async clearModelInfo(file) {
|
||||
return LORA_INFO_SERVICE.clearFetchedInfo(file);
|
||||
}
|
||||
}
|
||||
export class RgthreeCheckpointInfoDialog extends RgthreeInfoDialog {
|
||||
async getModelInfo(file) {
|
||||
return CHECKPOINT_INFO_SERVICE.getInfo(file, false, false);
|
||||
}
|
||||
async refreshModelInfo(file) {
|
||||
return CHECKPOINT_INFO_SERVICE.refreshInfo(file);
|
||||
}
|
||||
async clearModelInfo(file) {
|
||||
return CHECKPOINT_INFO_SERVICE.clearFetchedInfo(file);
|
||||
}
|
||||
}
|
||||
function infoTableRow(name, value, help = "", editableFieldName = "") {
|
||||
return `
|
||||
<tr class="${editableFieldName ? "editable" : ""}" ${editableFieldName ? `data-field-name="${editableFieldName}"` : ""}>
|
||||
<td><span>${name} ${help ? `<span class="-help" title="${help}"></span>` : ""}<span></td>
|
||||
<td ${editableFieldName ? "" : 'colspan="2"'}>${String(value).startsWith("<") ? value : `<span>${value}<span>`}</td>
|
||||
${editableFieldName
|
||||
? `<td style="width: 24px;"><button class="rgthree-button-reset rgthree-button-edit" data-action="edit-row">${pencilColored}${diskColored}</button></td>`
|
||||
: ""}
|
||||
</tr>`;
|
||||
}
|
||||
function getTrainedWordsMarkup(words) {
|
||||
let markup = `<ul class="rgthree-info-trained-words-list">`;
|
||||
for (const wordData of words || []) {
|
||||
markup += `<li title="${wordData.word}" data-word="${wordData.word}" class="rgthree-info-trained-words-list-item" data-action="toggle-trained-word">
|
||||
<span>${wordData.word}</span>
|
||||
${wordData.civitai ? logoCivitai : ""}
|
||||
${wordData.count != null ? `<small>${wordData.count}</small>` : ""}
|
||||
</li>`;
|
||||
}
|
||||
markup += `</ul>`;
|
||||
return markup;
|
||||
}
|
||||
function saveEditableRow(info, tr, saving = true) {
|
||||
var _a;
|
||||
const fieldName = tr.dataset["fieldName"];
|
||||
const input = query("input,textarea", tr);
|
||||
let newValue = (_a = info[fieldName]) !== null && _a !== void 0 ? _a : "";
|
||||
let modified = false;
|
||||
if (saving) {
|
||||
newValue = input.value;
|
||||
if (fieldName.startsWith("strength")) {
|
||||
if (Number.isNaN(Number(newValue))) {
|
||||
alert(`You must enter a number into the ${fieldName} field.`);
|
||||
return false;
|
||||
}
|
||||
newValue = (Math.round(Number(newValue) * 100) / 100).toFixed(2);
|
||||
}
|
||||
LORA_INFO_SERVICE.savePartialInfo(info.file, { [fieldName]: newValue });
|
||||
modified = true;
|
||||
}
|
||||
tr.classList.remove("-rgthree-editing");
|
||||
const td = query("td:nth-child(2)", tr);
|
||||
appendChildren(empty(td), [$el("span", { text: newValue })]);
|
||||
return modified;
|
||||
}
|
||||
function imgInfoField(label, value) {
|
||||
return value != null ? `<span>${label ? `<label>${label} </label>` : ""}${value}</span>` : "";
|
||||
}
|
||||
35
custom_nodes/rgthree-comfy/web/comfyui/display_any.js
Normal file
35
custom_nodes/rgthree-comfy/web/comfyui/display_any.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
import { addConnectionLayoutSupport } from "./utils.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
let hasShownAlertForUpdatingInt = false;
|
||||
app.registerExtension({
|
||||
name: "rgthree.DisplayAny",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "Display Any (rgthree)" || nodeData.name === "Display Int (rgthree)") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
|
||||
this.showValueWidget = ComfyWidgets["STRING"](this, "output", ["STRING", { multiline: true }], app).widget;
|
||||
this.showValueWidget.inputEl.readOnly = true;
|
||||
this.showValueWidget.serializeValue = async (node, index) => {
|
||||
const n = rgthree.getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node);
|
||||
if (n) {
|
||||
n.widgets_values[index] = "";
|
||||
}
|
||||
else {
|
||||
console.warn("No serialized node found in workflow. May be attributed to " +
|
||||
"https://github.com/comfyanonymous/ComfyUI/issues/2193");
|
||||
}
|
||||
return "";
|
||||
};
|
||||
};
|
||||
addConnectionLayoutSupport(nodeType, app, [["Left"], ["Right"]]);
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted === null || onExecuted === void 0 ? void 0 : onExecuted.apply(this, [message]);
|
||||
this.showValueWidget.value = message.text[0];
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
253
custom_nodes/rgthree-comfy/web/comfyui/dynamic_context.js
Normal file
253
custom_nodes/rgthree-comfy/web/comfyui/dynamic_context.js
Normal file
@@ -0,0 +1,253 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { IoDirection, followConnectionUntilType, getConnectedInputInfosAndFilterPassThroughs, } from "./utils.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { SERVICE as CONTEXT_SERVICE, InputMutationOperation, } from "./services/context_service.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
|
||||
import { DynamicContextNodeBase } from "./dynamic_context_base.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
const OWNED_PREFIX = "+";
|
||||
const REGEX_OWNED_PREFIX = /^\+\s*/;
|
||||
const REGEX_EMPTY_INPUT = /^\+\s*$/;
|
||||
export class DynamicContextNode extends DynamicContextNodeBase {
|
||||
constructor(title = DynamicContextNode.title) {
|
||||
super(title);
|
||||
}
|
||||
onNodeCreated() {
|
||||
this.addInput("base_ctx", "RGTHREE_DYNAMIC_CONTEXT");
|
||||
this.ensureOneRemainingNewInputSlot();
|
||||
super.onNodeCreated();
|
||||
}
|
||||
onConnectionsChange(type, slotIndex, isConnected, link, ioSlot) {
|
||||
var _a;
|
||||
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, link, ioSlot);
|
||||
if (this.configuring) {
|
||||
return;
|
||||
}
|
||||
if (type === LiteGraph.INPUT) {
|
||||
if (isConnected) {
|
||||
this.handleInputConnected(slotIndex);
|
||||
}
|
||||
else {
|
||||
this.handleInputDisconnected(slotIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
|
||||
var _a;
|
||||
let canConnect = true;
|
||||
if (super.onConnectInput) {
|
||||
canConnect = super.onConnectInput.apply(this, [...arguments]);
|
||||
}
|
||||
if (canConnect &&
|
||||
outputNode instanceof DynamicContextNode &&
|
||||
outputIndex === 0 &&
|
||||
inputIndex !== 0) {
|
||||
const [n, v] = rgthree.logger.warnParts("Currently, you can only connect a context node in the first slot.");
|
||||
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
|
||||
canConnect = false;
|
||||
}
|
||||
return canConnect;
|
||||
}
|
||||
handleInputConnected(slotIndex) {
|
||||
const ioSlot = this.inputs[slotIndex];
|
||||
const connectedIndexes = [];
|
||||
if (slotIndex === 0) {
|
||||
let baseNodeInfos = getConnectedInputInfosAndFilterPassThroughs(this, this, 0);
|
||||
const baseNodes = baseNodeInfos.map((n) => n.node);
|
||||
const baseNodesDynamicCtx = baseNodes[0];
|
||||
if (baseNodesDynamicCtx === null || baseNodesDynamicCtx === void 0 ? void 0 : baseNodesDynamicCtx.provideInputsData) {
|
||||
const inputsData = CONTEXT_SERVICE.getDynamicContextInputsData(baseNodesDynamicCtx);
|
||||
console.log("inputsData", inputsData);
|
||||
for (const input of baseNodesDynamicCtx.provideInputsData()) {
|
||||
if (input.name === "base_ctx" || input.name === "+") {
|
||||
continue;
|
||||
}
|
||||
this.addContextInput(input.name, input.type, input.index);
|
||||
this.stabilizeNames();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (this.isInputSlotForNewInput(slotIndex)) {
|
||||
this.handleNewInputConnected(slotIndex);
|
||||
}
|
||||
}
|
||||
isInputSlotForNewInput(slotIndex) {
|
||||
const ioSlot = this.inputs[slotIndex];
|
||||
return ioSlot && ioSlot.name === "+" && ioSlot.type === "*";
|
||||
}
|
||||
handleNewInputConnected(slotIndex) {
|
||||
if (!this.isInputSlotForNewInput(slotIndex)) {
|
||||
throw new Error('Expected the incoming slot index to be the "new input" input.');
|
||||
}
|
||||
const ioSlot = this.inputs[slotIndex];
|
||||
let cxn = null;
|
||||
if (ioSlot.link != null) {
|
||||
cxn = followConnectionUntilType(this, IoDirection.INPUT, slotIndex, true);
|
||||
}
|
||||
if ((cxn === null || cxn === void 0 ? void 0 : cxn.type) && (cxn === null || cxn === void 0 ? void 0 : cxn.name)) {
|
||||
let name = this.addOwnedPrefix(this.getNextUniqueNameForThisNode(cxn.name));
|
||||
if (name.match(/^\+\s*[A-Z_]+(\.\d+)?$/)) {
|
||||
name = name.toLowerCase();
|
||||
}
|
||||
ioSlot.name = name;
|
||||
ioSlot.type = cxn.type;
|
||||
ioSlot.removable = true;
|
||||
while (!this.outputs[slotIndex]) {
|
||||
this.addOutput("*", "*");
|
||||
}
|
||||
this.outputs[slotIndex].type = cxn.type;
|
||||
this.outputs[slotIndex].name = this.stripOwnedPrefix(name).toLocaleUpperCase();
|
||||
if (cxn.type === "COMBO" || cxn.type.includes(",") || Array.isArray(cxn.type)) {
|
||||
this.outputs[slotIndex].widget = true;
|
||||
}
|
||||
this.inputsMutated({
|
||||
operation: InputMutationOperation.ADDED,
|
||||
node: this,
|
||||
slotIndex,
|
||||
slot: ioSlot,
|
||||
});
|
||||
this.stabilizeNames();
|
||||
this.ensureOneRemainingNewInputSlot();
|
||||
}
|
||||
}
|
||||
handleInputDisconnected(slotIndex) {
|
||||
var _a, _b;
|
||||
const inputs = this.getContextInputsList();
|
||||
if (slotIndex === 0) {
|
||||
for (let index = inputs.length - 1; index > 0; index--) {
|
||||
if (index === 0 || index === inputs.length - 1) {
|
||||
continue;
|
||||
}
|
||||
const input = inputs[index];
|
||||
if (!this.isOwnedInput(input.name)) {
|
||||
if (input.link || ((_b = (_a = this.outputs[index]) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length)) {
|
||||
this.renameContextInput(index, input.name, true);
|
||||
}
|
||||
else {
|
||||
this.removeContextInput(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setSize(this.computeSize());
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
ensureOneRemainingNewInputSlot() {
|
||||
removeUnusedInputsFromEnd(this, 1, REGEX_EMPTY_INPUT);
|
||||
this.addInput(OWNED_PREFIX, "*");
|
||||
}
|
||||
getNextUniqueNameForThisNode(desiredName) {
|
||||
const inputs = this.getContextInputsList();
|
||||
const allExistingKeys = inputs.map((i) => this.stripOwnedPrefix(i.name).toLocaleUpperCase());
|
||||
desiredName = this.stripOwnedPrefix(desiredName);
|
||||
let newName = desiredName;
|
||||
let n = 0;
|
||||
while (allExistingKeys.includes(newName.toLocaleUpperCase())) {
|
||||
newName = `${desiredName}.${++n}`;
|
||||
}
|
||||
return newName;
|
||||
}
|
||||
removeInput(slotIndex) {
|
||||
const slot = this.inputs[slotIndex];
|
||||
super.removeInput(slotIndex);
|
||||
if (this.outputs[slotIndex]) {
|
||||
this.removeOutput(slotIndex);
|
||||
}
|
||||
this.inputsMutated({ operation: InputMutationOperation.REMOVED, node: this, slotIndex, slot });
|
||||
this.stabilizeNames();
|
||||
}
|
||||
stabilizeNames() {
|
||||
const inputs = this.getContextInputsList();
|
||||
const names = [];
|
||||
for (const [index, input] of inputs.entries()) {
|
||||
if (index === 0 || index === inputs.length - 1) {
|
||||
continue;
|
||||
}
|
||||
input.label = undefined;
|
||||
this.outputs[index].label = undefined;
|
||||
let origName = this.stripOwnedPrefix(input.name).replace(/\.\d+$/, "");
|
||||
let name = input.name;
|
||||
if (!this.isOwnedInput(name)) {
|
||||
names.push(name.toLocaleUpperCase());
|
||||
}
|
||||
else {
|
||||
let n = 0;
|
||||
name = this.addOwnedPrefix(origName);
|
||||
while (names.includes(this.stripOwnedPrefix(name).toLocaleUpperCase())) {
|
||||
name = `${this.addOwnedPrefix(origName)}.${++n}`;
|
||||
}
|
||||
names.push(this.stripOwnedPrefix(name).toLocaleUpperCase());
|
||||
if (input.name !== name) {
|
||||
this.renameContextInput(index, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getSlotMenuOptions(slot) {
|
||||
const editable = this.isOwnedInput(slot.input.name) && this.type !== "*";
|
||||
return [
|
||||
{
|
||||
content: "✏️ Rename Input",
|
||||
disabled: !editable,
|
||||
callback: () => {
|
||||
var dialog = app.canvas.createDialog("<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>", {});
|
||||
var dialogInput = dialog.querySelector("input");
|
||||
if (dialogInput) {
|
||||
dialogInput.value = this.stripOwnedPrefix(slot.input.name || "");
|
||||
}
|
||||
var inner = () => {
|
||||
this.handleContextMenuRenameInputDialog(slot.slot, dialogInput.value);
|
||||
dialog.close();
|
||||
};
|
||||
dialog.querySelector("button").addEventListener("click", inner);
|
||||
dialogInput.addEventListener("keydown", (e) => {
|
||||
var _a;
|
||||
dialog.is_modified = true;
|
||||
if (e.keyCode == 27) {
|
||||
dialog.close();
|
||||
}
|
||||
else if (e.keyCode == 13) {
|
||||
inner();
|
||||
}
|
||||
else if (e.keyCode != 13 && ((_a = e.target) === null || _a === void 0 ? void 0 : _a.localName) != "textarea") {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
dialogInput.focus();
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "🗑️ Delete Input",
|
||||
disabled: !editable,
|
||||
callback: () => {
|
||||
this.removeInput(slot.slot);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
handleContextMenuRenameInputDialog(slotIndex, value) {
|
||||
app.graph.beforeChange();
|
||||
this.renameContextInput(slotIndex, value);
|
||||
this.stabilizeNames();
|
||||
this.setDirtyCanvas(true, true);
|
||||
app.graph.afterChange();
|
||||
}
|
||||
}
|
||||
DynamicContextNode.title = NodeTypesString.DYNAMIC_CONTEXT;
|
||||
DynamicContextNode.type = NodeTypesString.DYNAMIC_CONTEXT;
|
||||
DynamicContextNode.comfyClass = NodeTypesString.DYNAMIC_CONTEXT;
|
||||
const contextDynamicNodes = [DynamicContextNode];
|
||||
app.registerExtension({
|
||||
name: "rgthree.DynamicContext",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (!CONFIG_SERVICE.getConfigValue("unreleased.dynamic_context.enabled")) {
|
||||
return;
|
||||
}
|
||||
if (nodeData.name === DynamicContextNode.type) {
|
||||
DynamicContextNode.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
192
custom_nodes/rgthree-comfy/web/comfyui/dynamic_context_base.js
Normal file
192
custom_nodes/rgthree-comfy/web/comfyui/dynamic_context_base.js
Normal file
@@ -0,0 +1,192 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { BaseContextNode } from "./context.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { moveArrayItem, wait } from "../../rgthree/common/shared_utils.js";
|
||||
import { RgthreeInvisibleWidget } from "./utils_widgets.js";
|
||||
import { getContextOutputName, InputMutationOperation, } from "./services/context_service.js";
|
||||
import { SERVICE as CONTEXT_SERVICE } from "./services/context_service.js";
|
||||
const OWNED_PREFIX = "+";
|
||||
const REGEX_OWNED_PREFIX = /^\+\s*/;
|
||||
const REGEX_EMPTY_INPUT = /^\+\s*$/;
|
||||
export class DynamicContextNodeBase extends BaseContextNode {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.hasShadowInputs = false;
|
||||
}
|
||||
getContextInputsList() {
|
||||
return this.inputs;
|
||||
}
|
||||
provideInputsData() {
|
||||
const inputs = this.getContextInputsList();
|
||||
return inputs
|
||||
.map((input, index) => ({
|
||||
name: this.stripOwnedPrefix(input.name),
|
||||
type: String(input.type),
|
||||
index,
|
||||
}))
|
||||
.filter((i) => i.type !== "*");
|
||||
}
|
||||
addOwnedPrefix(name) {
|
||||
return `+ ${this.stripOwnedPrefix(name)}`;
|
||||
}
|
||||
isOwnedInput(inputOrName) {
|
||||
const name = typeof inputOrName == "string" ? inputOrName : (inputOrName === null || inputOrName === void 0 ? void 0 : inputOrName.name) || "";
|
||||
return REGEX_OWNED_PREFIX.test(name);
|
||||
}
|
||||
stripOwnedPrefix(name) {
|
||||
return name.replace(REGEX_OWNED_PREFIX, "");
|
||||
}
|
||||
handleUpstreamMutation(mutation) {
|
||||
console.log(`[node ${this.id}] handleUpstreamMutation`, mutation);
|
||||
if (mutation.operation === InputMutationOperation.ADDED) {
|
||||
const slot = mutation.slot;
|
||||
if (!slot) {
|
||||
throw new Error("Cannot have an ADDED mutation without a provided slot data.");
|
||||
}
|
||||
this.addContextInput(this.stripOwnedPrefix(slot.name), slot.type, mutation.slotIndex);
|
||||
return;
|
||||
}
|
||||
if (mutation.operation === InputMutationOperation.REMOVED) {
|
||||
const slot = mutation.slot;
|
||||
if (!slot) {
|
||||
throw new Error("Cannot have an REMOVED mutation without a provided slot data.");
|
||||
}
|
||||
this.removeContextInput(mutation.slotIndex);
|
||||
return;
|
||||
}
|
||||
if (mutation.operation === InputMutationOperation.RENAMED) {
|
||||
const slot = mutation.slot;
|
||||
if (!slot) {
|
||||
throw new Error("Cannot have an RENAMED mutation without a provided slot data.");
|
||||
}
|
||||
this.renameContextInput(mutation.slotIndex, slot.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
clone() {
|
||||
const cloned = super.clone();
|
||||
while (cloned.inputs.length > 1) {
|
||||
cloned.removeInput(cloned.inputs.length - 1);
|
||||
}
|
||||
while (cloned.widgets.length > 1) {
|
||||
cloned.removeWidget(cloned.widgets.length - 1);
|
||||
}
|
||||
while (cloned.outputs.length > 1) {
|
||||
cloned.removeOutput(cloned.outputs.length - 1);
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
onNodeCreated() {
|
||||
const node = this;
|
||||
this.addCustomWidget(new RgthreeInvisibleWidget("output_keys", "RGTHREE_DYNAMIC_CONTEXT_OUTPUTS", "", () => {
|
||||
return (node.outputs || [])
|
||||
.map((o, i) => i > 0 && o.name)
|
||||
.filter((n) => n !== false)
|
||||
.join(",");
|
||||
}));
|
||||
}
|
||||
addContextInput(name, type, slot = -1) {
|
||||
const inputs = this.getContextInputsList();
|
||||
if (this.hasShadowInputs) {
|
||||
inputs.push({ name, type, link: null, boundingRect: null });
|
||||
}
|
||||
else {
|
||||
this.addInput(name, type);
|
||||
}
|
||||
if (slot > -1) {
|
||||
moveArrayItem(inputs, inputs.length - 1, slot);
|
||||
}
|
||||
else {
|
||||
slot = inputs.length - 1;
|
||||
}
|
||||
if (type !== "*") {
|
||||
const output = this.addOutput(getContextOutputName(name), type);
|
||||
if (type === "COMBO" || String(type).includes(",") || Array.isArray(type)) {
|
||||
output.widget = true;
|
||||
}
|
||||
if (slot > -1) {
|
||||
moveArrayItem(this.outputs, this.outputs.length - 1, slot);
|
||||
}
|
||||
}
|
||||
this.fixInputsOutputsLinkSlots();
|
||||
this.inputsMutated({
|
||||
operation: InputMutationOperation.ADDED,
|
||||
node: this,
|
||||
slotIndex: slot,
|
||||
slot: inputs[slot],
|
||||
});
|
||||
}
|
||||
removeContextInput(slotIndex) {
|
||||
if (this.hasShadowInputs) {
|
||||
const inputs = this.getContextInputsList();
|
||||
const input = inputs.splice(slotIndex, 1)[0];
|
||||
if (this.outputs[slotIndex]) {
|
||||
this.removeOutput(slotIndex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.removeInput(slotIndex);
|
||||
}
|
||||
}
|
||||
renameContextInput(index, newName, forceOwnBool = null) {
|
||||
const inputs = this.getContextInputsList();
|
||||
const input = inputs[index];
|
||||
const oldName = input.name;
|
||||
newName = this.stripOwnedPrefix(newName.trim() || this.getSlotDefaultInputLabel(index));
|
||||
if (forceOwnBool === true || (this.isOwnedInput(oldName) && forceOwnBool !== false)) {
|
||||
newName = this.addOwnedPrefix(newName);
|
||||
}
|
||||
if (oldName !== newName) {
|
||||
input.name = newName;
|
||||
input.removable = this.isOwnedInput(newName);
|
||||
this.outputs[index].name = getContextOutputName(inputs[index].name);
|
||||
this.inputsMutated({
|
||||
node: this,
|
||||
operation: InputMutationOperation.RENAMED,
|
||||
slotIndex: index,
|
||||
slot: input,
|
||||
});
|
||||
}
|
||||
}
|
||||
getSlotDefaultInputLabel(slotIndex) {
|
||||
const inputs = this.getContextInputsList();
|
||||
const input = inputs[slotIndex];
|
||||
let defaultLabel = this.stripOwnedPrefix(input.name).toLowerCase();
|
||||
return defaultLabel.toLocaleLowerCase();
|
||||
}
|
||||
inputsMutated(mutation) {
|
||||
CONTEXT_SERVICE.onInputChanges(this, mutation);
|
||||
}
|
||||
fixInputsOutputsLinkSlots() {
|
||||
if (!this.hasShadowInputs) {
|
||||
const inputs = this.getContextInputsList();
|
||||
for (let index = inputs.length - 1; index > 0; index--) {
|
||||
const input = inputs[index];
|
||||
if ((input === null || input === void 0 ? void 0 : input.link) != null) {
|
||||
app.graph.links[input.link].target_slot = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
const outputs = this.outputs;
|
||||
for (let index = outputs.length - 1; index > 0; index--) {
|
||||
const output = outputs[index];
|
||||
if (output) {
|
||||
output.nameLocked = true;
|
||||
for (const link of output.links || []) {
|
||||
app.graph.links[link].origin_slot = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, this);
|
||||
wait(500).then(() => {
|
||||
LiteGraph.slot_types_default_out["RGTHREE_DYNAMIC_CONTEXT"] =
|
||||
LiteGraph.slot_types_default_out["RGTHREE_DYNAMIC_CONTEXT"] || [];
|
||||
const comfyClassStr = comfyClass.comfyClass;
|
||||
if (comfyClassStr) {
|
||||
LiteGraph.slot_types_default_out["RGTHREE_DYNAMIC_CONTEXT"].push(comfyClassStr);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
147
custom_nodes/rgthree-comfy/web/comfyui/dynamic_context_switch.js
Normal file
147
custom_nodes/rgthree-comfy/web/comfyui/dynamic_context_switch.js
Normal file
@@ -0,0 +1,147 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { DynamicContextNodeBase } from "./dynamic_context_base.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { SERVICE as CONTEXT_SERVICE, getContextOutputName, } from "./services/context_service.js";
|
||||
import { getConnectedInputNodesAndFilterPassThroughs } from "./utils.js";
|
||||
import { debounce, moveArrayItem } from "../../rgthree/common/shared_utils.js";
|
||||
import { measureText } from "./utils_canvas.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
class DynamicContextSwitchNode extends DynamicContextNodeBase {
|
||||
constructor(title = DynamicContextSwitchNode.title) {
|
||||
super(title);
|
||||
this.hasShadowInputs = true;
|
||||
this.lastInputsList = [];
|
||||
this.shadowInputs = [
|
||||
{ name: "base_ctx", type: "RGTHREE_DYNAMIC_CONTEXT", link: null, count: 0, boundingRect: null },
|
||||
];
|
||||
}
|
||||
getContextInputsList() {
|
||||
return this.shadowInputs;
|
||||
}
|
||||
handleUpstreamMutation(mutation) {
|
||||
this.scheduleHardRefresh();
|
||||
}
|
||||
onConnectionsChange(type, slotIndex, isConnected, link, inputOrOutput) {
|
||||
var _a;
|
||||
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, link, inputOrOutput);
|
||||
if (this.configuring) {
|
||||
return;
|
||||
}
|
||||
if (type === LiteGraph.INPUT) {
|
||||
this.scheduleHardRefresh();
|
||||
}
|
||||
}
|
||||
scheduleHardRefresh(ms = 64) {
|
||||
return debounce(() => {
|
||||
this.refreshInputsAndOutputs();
|
||||
}, ms);
|
||||
}
|
||||
onNodeCreated() {
|
||||
this.addInput("ctx_1", "RGTHREE_DYNAMIC_CONTEXT");
|
||||
this.addInput("ctx_2", "RGTHREE_DYNAMIC_CONTEXT");
|
||||
this.addInput("ctx_3", "RGTHREE_DYNAMIC_CONTEXT");
|
||||
this.addInput("ctx_4", "RGTHREE_DYNAMIC_CONTEXT");
|
||||
this.addInput("ctx_5", "RGTHREE_DYNAMIC_CONTEXT");
|
||||
super.onNodeCreated();
|
||||
}
|
||||
addContextInput(name, type, slot) { }
|
||||
refreshInputsAndOutputs() {
|
||||
var _a;
|
||||
const inputs = [
|
||||
{ name: "base_ctx", type: "RGTHREE_DYNAMIC_CONTEXT", link: null, count: 0, boundingRect: null },
|
||||
];
|
||||
let numConnected = 0;
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
const childCtxs = getConnectedInputNodesAndFilterPassThroughs(this, this, i);
|
||||
if (childCtxs.length > 1) {
|
||||
throw new Error("How is there more than one input?");
|
||||
}
|
||||
const ctx = childCtxs[0];
|
||||
if (!ctx)
|
||||
continue;
|
||||
numConnected++;
|
||||
const slotsData = CONTEXT_SERVICE.getDynamicContextInputsData(ctx);
|
||||
console.log(slotsData);
|
||||
for (const slotData of slotsData) {
|
||||
const found = inputs.find((n) => getContextOutputName(slotData.name) === getContextOutputName(n.name));
|
||||
if (found) {
|
||||
found.count += 1;
|
||||
continue;
|
||||
}
|
||||
inputs.push({
|
||||
name: slotData.name,
|
||||
type: slotData.type,
|
||||
link: null,
|
||||
count: 1,
|
||||
boundingRect: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.shadowInputs = inputs;
|
||||
let i = 0;
|
||||
for (i; i < this.shadowInputs.length; i++) {
|
||||
const data = this.shadowInputs[i];
|
||||
let existing = this.outputs.find((o) => getContextOutputName(o.name) === getContextOutputName(data.name));
|
||||
if (!existing) {
|
||||
existing = this.addOutput(getContextOutputName(data.name), data.type);
|
||||
}
|
||||
moveArrayItem(this.outputs, existing, i);
|
||||
delete existing.rgthree_status;
|
||||
if (data.count !== numConnected) {
|
||||
existing.rgthree_status = "WARN";
|
||||
}
|
||||
}
|
||||
while (this.outputs[i]) {
|
||||
const output = this.outputs[i];
|
||||
if ((_a = output === null || output === void 0 ? void 0 : output.links) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
output.rgthree_status = "ERROR";
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
this.removeOutput(i);
|
||||
}
|
||||
}
|
||||
this.fixInputsOutputsLinkSlots();
|
||||
}
|
||||
onDrawForeground(ctx, canvas) {
|
||||
var _a, _b;
|
||||
const low_quality = ((_b = (_a = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1) < 0.6;
|
||||
if (low_quality || this.size[0] <= 10) {
|
||||
return;
|
||||
}
|
||||
let y = LiteGraph.NODE_SLOT_HEIGHT - 1;
|
||||
const w = this.size[0];
|
||||
ctx.save();
|
||||
ctx.font = "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial";
|
||||
ctx.textAlign = "right";
|
||||
for (const output of this.outputs) {
|
||||
if (!output.rgthree_status) {
|
||||
y += LiteGraph.NODE_SLOT_HEIGHT;
|
||||
continue;
|
||||
}
|
||||
const x = w - 20 - measureText(ctx, output.name);
|
||||
if (output.rgthree_status === "ERROR") {
|
||||
ctx.fillText("🛑", x, y);
|
||||
}
|
||||
else if (output.rgthree_status === "WARN") {
|
||||
ctx.fillText("⚠️", x, y);
|
||||
}
|
||||
y += LiteGraph.NODE_SLOT_HEIGHT;
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
DynamicContextSwitchNode.title = NodeTypesString.DYNAMIC_CONTEXT_SWITCH;
|
||||
DynamicContextSwitchNode.type = NodeTypesString.DYNAMIC_CONTEXT_SWITCH;
|
||||
DynamicContextSwitchNode.comfyClass = NodeTypesString.DYNAMIC_CONTEXT_SWITCH;
|
||||
app.registerExtension({
|
||||
name: "rgthree.DynamicContextSwitch",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (!CONFIG_SERVICE.getConfigValue("unreleased.dynamic_context.enabled")) {
|
||||
return;
|
||||
}
|
||||
if (nodeData.name === DynamicContextSwitchNode.type) {
|
||||
DynamicContextSwitchNode.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
276
custom_nodes/rgthree-comfy/web/comfyui/fast_actions_button.js
Normal file
276
custom_nodes/rgthree-comfy/web/comfyui/fast_actions_button.js
Normal file
@@ -0,0 +1,276 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { addMenuItem, changeModeOfNodes } from "./utils.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
const MODE_ALWAYS = 0;
|
||||
const MODE_MUTE = 2;
|
||||
const MODE_BYPASS = 4;
|
||||
class FastActionsButton extends BaseAnyInputConnectedNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.FAST_ACTIONS_BUTTON;
|
||||
this.logger = rgthree.newLogSession("[FastActionsButton]");
|
||||
this.isVirtualNode = true;
|
||||
this.serialize_widgets = true;
|
||||
this.widgetToData = new Map();
|
||||
this.nodeIdtoFunctionCache = new Map();
|
||||
this.executingFromShortcut = false;
|
||||
this.properties["buttonText"] = "🎬 Action!";
|
||||
this.properties["shortcutModifier"] = "alt";
|
||||
this.properties["shortcutKey"] = "";
|
||||
this.buttonWidget = this.addWidget("button", this.properties["buttonText"], "", () => {
|
||||
this.executeConnectedNodes();
|
||||
}, { serialize: false });
|
||||
this.keypressBound = this.onKeypress.bind(this);
|
||||
this.keyupBound = this.onKeyup.bind(this);
|
||||
this.onConstructed();
|
||||
}
|
||||
configure(info) {
|
||||
super.configure(info);
|
||||
setTimeout(() => {
|
||||
if (info.widgets_values) {
|
||||
for (let [index, value] of info.widgets_values.entries()) {
|
||||
if (index > 0) {
|
||||
if (typeof value === "string" && value.startsWith("comfy_action:")) {
|
||||
value = value.replace("comfy_action:", "");
|
||||
this.addComfyActionWidget(index, value);
|
||||
}
|
||||
if (this.widgets[index]) {
|
||||
this.widgets[index].value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
clone() {
|
||||
const cloned = super.clone();
|
||||
cloned.properties["buttonText"] = "🎬 Action!";
|
||||
cloned.properties["shortcutKey"] = "";
|
||||
return cloned;
|
||||
}
|
||||
onAdded(graph) {
|
||||
window.addEventListener("keydown", this.keypressBound);
|
||||
window.addEventListener("keyup", this.keyupBound);
|
||||
}
|
||||
onRemoved() {
|
||||
window.removeEventListener("keydown", this.keypressBound);
|
||||
window.removeEventListener("keyup", this.keyupBound);
|
||||
}
|
||||
async onKeypress(event) {
|
||||
const target = event.target;
|
||||
if (this.executingFromShortcut ||
|
||||
target.localName == "input" ||
|
||||
target.localName == "textarea") {
|
||||
return;
|
||||
}
|
||||
if (this.properties["shortcutKey"].trim() &&
|
||||
this.properties["shortcutKey"].toLowerCase() === event.key.toLowerCase()) {
|
||||
const shortcutModifier = this.properties["shortcutModifier"];
|
||||
let good = shortcutModifier === "ctrl" && event.ctrlKey;
|
||||
good = good || (shortcutModifier === "alt" && event.altKey);
|
||||
good = good || (shortcutModifier === "shift" && event.shiftKey);
|
||||
good = good || (shortcutModifier === "meta" && event.metaKey);
|
||||
if (good) {
|
||||
setTimeout(() => {
|
||||
this.executeConnectedNodes();
|
||||
}, 20);
|
||||
this.executingFromShortcut = true;
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
app.canvas.dirty_canvas = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
onKeyup(event) {
|
||||
const target = event.target;
|
||||
if (target.localName == "input" || target.localName == "textarea") {
|
||||
return;
|
||||
}
|
||||
this.executingFromShortcut = false;
|
||||
}
|
||||
onPropertyChanged(property, value, prevValue) {
|
||||
var _a, _b;
|
||||
if (property == "buttonText" && typeof value === "string") {
|
||||
this.buttonWidget.name = value;
|
||||
}
|
||||
if (property == "shortcutKey" && typeof value === "string") {
|
||||
this.properties["shortcutKey"] = (_b = (_a = value.trim()[0]) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : "";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
handleLinkedNodesStabilization(linkedNodes) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h;
|
||||
let changed = false;
|
||||
for (const [widget, data] of this.widgetToData.entries()) {
|
||||
if (!data.node) {
|
||||
continue;
|
||||
}
|
||||
if (!linkedNodes.includes(data.node)) {
|
||||
const index = this.widgets.indexOf(widget);
|
||||
if (index > -1) {
|
||||
this.widgetToData.delete(widget);
|
||||
this.removeWidget(widget);
|
||||
changed = true;
|
||||
}
|
||||
else {
|
||||
const [m, a] = this.logger.debugParts("Connected widget is not in widgets... weird.");
|
||||
(_a = console[m]) === null || _a === void 0 ? void 0 : _a.call(console, ...a);
|
||||
}
|
||||
}
|
||||
}
|
||||
const badNodes = [];
|
||||
let indexOffset = 1;
|
||||
for (const [index, node] of linkedNodes.entries()) {
|
||||
if (!node) {
|
||||
const [m, a] = this.logger.debugParts("linkedNode provided that does not exist. ");
|
||||
(_b = console[m]) === null || _b === void 0 ? void 0 : _b.call(console, ...a);
|
||||
badNodes.push(node);
|
||||
continue;
|
||||
}
|
||||
let widgetAtSlot = this.widgets[index + indexOffset];
|
||||
if (widgetAtSlot && ((_c = this.widgetToData.get(widgetAtSlot)) === null || _c === void 0 ? void 0 : _c.comfy)) {
|
||||
indexOffset++;
|
||||
widgetAtSlot = this.widgets[index + indexOffset];
|
||||
}
|
||||
if (!widgetAtSlot || ((_e = (_d = this.widgetToData.get(widgetAtSlot)) === null || _d === void 0 ? void 0 : _d.node) === null || _e === void 0 ? void 0 : _e.id) !== node.id) {
|
||||
let widget = null;
|
||||
for (let i = index + indexOffset; i < this.widgets.length; i++) {
|
||||
if (((_g = (_f = this.widgetToData.get(this.widgets[i])) === null || _f === void 0 ? void 0 : _f.node) === null || _g === void 0 ? void 0 : _g.id) === node.id) {
|
||||
widget = this.widgets.splice(i, 1)[0];
|
||||
this.widgets.splice(index + indexOffset, 0, widget);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!widget) {
|
||||
const exposedActions = node.constructor.exposedActions || [];
|
||||
widget = this.addWidget("combo", node.title, "None", "", {
|
||||
values: ["None", "Mute", "Bypass", "Enable", ...exposedActions],
|
||||
});
|
||||
widget.serializeValue = async (_node, _index) => {
|
||||
return widget === null || widget === void 0 ? void 0 : widget.value;
|
||||
};
|
||||
this.widgetToData.set(widget, { node });
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = this.widgets.length - 1; i > linkedNodes.length + indexOffset - 1; i--) {
|
||||
const widgetAtSlot = this.widgets[i];
|
||||
if (widgetAtSlot && ((_h = this.widgetToData.get(widgetAtSlot)) === null || _h === void 0 ? void 0 : _h.comfy)) {
|
||||
continue;
|
||||
}
|
||||
this.removeWidget(widgetAtSlot);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
removeWidget(widget) {
|
||||
widget = typeof widget === "number" ? this.widgets[widget] : widget;
|
||||
if (widget && this.widgetToData.has(widget)) {
|
||||
this.widgetToData.delete(widget);
|
||||
}
|
||||
super.removeWidget(widget);
|
||||
}
|
||||
async executeConnectedNodes() {
|
||||
var _a, _b;
|
||||
for (const widget of this.widgets) {
|
||||
if (widget == this.buttonWidget) {
|
||||
continue;
|
||||
}
|
||||
const action = widget.value;
|
||||
const { comfy, node } = (_a = this.widgetToData.get(widget)) !== null && _a !== void 0 ? _a : {};
|
||||
if (comfy) {
|
||||
if (action === "Queue Prompt") {
|
||||
await comfy.queuePrompt(0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (node) {
|
||||
if (action === "Mute") {
|
||||
changeModeOfNodes(node, MODE_MUTE);
|
||||
}
|
||||
else if (action === "Bypass") {
|
||||
changeModeOfNodes(node, MODE_BYPASS);
|
||||
}
|
||||
else if (action === "Enable") {
|
||||
changeModeOfNodes(node, MODE_ALWAYS);
|
||||
}
|
||||
if (node.handleAction) {
|
||||
if (typeof action !== "string") {
|
||||
throw new Error("Fast Actions Button action should be a string: " + action);
|
||||
}
|
||||
await node.handleAction(action);
|
||||
}
|
||||
(_b = this.graph) === null || _b === void 0 ? void 0 : _b.change();
|
||||
continue;
|
||||
}
|
||||
console.warn("Fast Actions Button has a widget without correct data.");
|
||||
}
|
||||
}
|
||||
addComfyActionWidget(slot, value) {
|
||||
let widget = this.addWidget("combo", "Comfy Action", "None", () => {
|
||||
if (String(widget.value).startsWith("MOVE ")) {
|
||||
this.widgets.push(this.widgets.splice(this.widgets.indexOf(widget), 1)[0]);
|
||||
widget.value = String(widget.rgthree_lastValue);
|
||||
}
|
||||
else if (String(widget.value).startsWith("REMOVE ")) {
|
||||
this.removeWidget(widget);
|
||||
}
|
||||
widget.rgthree_lastValue = widget.value;
|
||||
}, {
|
||||
values: ["None", "Queue Prompt", "REMOVE Comfy Action", "MOVE to end"],
|
||||
});
|
||||
widget.rgthree_lastValue = value;
|
||||
widget.serializeValue = async (_node, _index) => {
|
||||
return `comfy_app:${widget === null || widget === void 0 ? void 0 : widget.value}`;
|
||||
};
|
||||
this.widgetToData.set(widget, { comfy: app });
|
||||
if (slot != null) {
|
||||
this.widgets.splice(slot, 0, this.widgets.splice(this.widgets.indexOf(widget), 1)[0]);
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
onSerialize(serialised) {
|
||||
var _a, _b;
|
||||
(_a = super.onSerialize) === null || _a === void 0 ? void 0 : _a.call(this, serialised);
|
||||
for (let [index, value] of (serialised.widgets_values || []).entries()) {
|
||||
if (((_b = this.widgets[index]) === null || _b === void 0 ? void 0 : _b.name) === "Comfy Action") {
|
||||
serialised.widgets_values[index] = `comfy_action:${value}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
static setUp() {
|
||||
super.setUp();
|
||||
addMenuItem(this, app, {
|
||||
name: "➕ Append a Comfy Action",
|
||||
callback: (nodeArg) => {
|
||||
nodeArg.addComfyActionWidget();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
FastActionsButton.type = NodeTypesString.FAST_ACTIONS_BUTTON;
|
||||
FastActionsButton.title = NodeTypesString.FAST_ACTIONS_BUTTON;
|
||||
FastActionsButton["@buttonText"] = { type: "string" };
|
||||
FastActionsButton["@shortcutModifier"] = {
|
||||
type: "combo",
|
||||
values: ["ctrl", "alt", "shift"],
|
||||
};
|
||||
FastActionsButton["@shortcutKey"] = { type: "string" };
|
||||
FastActionsButton.collapsible = false;
|
||||
app.registerExtension({
|
||||
name: "rgthree.FastActionsButton",
|
||||
registerCustomNodes() {
|
||||
FastActionsButton.setUp();
|
||||
},
|
||||
loadedGraphNode(node) {
|
||||
if (node.type == FastActionsButton.title) {
|
||||
node._tempWidth = node.size[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { BaseFastGroupsModeChanger } from "./fast_groups_muter.js";
|
||||
export class FastGroupsBypasser extends BaseFastGroupsModeChanger {
|
||||
constructor(title = FastGroupsBypasser.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.FAST_GROUPS_BYPASSER;
|
||||
this.helpActions = "bypass and enable";
|
||||
this.modeOn = LiteGraph.ALWAYS;
|
||||
this.modeOff = 4;
|
||||
this.onConstructed();
|
||||
}
|
||||
}
|
||||
FastGroupsBypasser.type = NodeTypesString.FAST_GROUPS_BYPASSER;
|
||||
FastGroupsBypasser.title = NodeTypesString.FAST_GROUPS_BYPASSER;
|
||||
FastGroupsBypasser.exposedActions = ["Bypass all", "Enable all", "Toggle all"];
|
||||
app.registerExtension({
|
||||
name: "rgthree.FastGroupsBypasser",
|
||||
registerCustomNodes() {
|
||||
FastGroupsBypasser.setUp();
|
||||
},
|
||||
loadedGraphNode(node) {
|
||||
if (node.type == FastGroupsBypasser.title) {
|
||||
node.tempSize = [...node.size];
|
||||
}
|
||||
},
|
||||
});
|
||||
437
custom_nodes/rgthree-comfy/web/comfyui/fast_groups_muter.js
Normal file
437
custom_nodes/rgthree-comfy/web/comfyui/fast_groups_muter.js
Normal file
@@ -0,0 +1,437 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeBaseVirtualNode } from "./base_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { SERVICE as FAST_GROUPS_SERVICE } from "./services/fast_groups_service.js";
|
||||
import { drawNodeWidget, fitString } from "./utils_canvas.js";
|
||||
import { RgthreeBaseWidget } from "./utils_widgets.js";
|
||||
import { changeModeOfNodes, getGroupNodes } from "./utils.js";
|
||||
const PROPERTY_SORT = "sort";
|
||||
const PROPERTY_SORT_CUSTOM_ALPHA = "customSortAlphabet";
|
||||
const PROPERTY_MATCH_COLORS = "matchColors";
|
||||
const PROPERTY_MATCH_TITLE = "matchTitle";
|
||||
const PROPERTY_SHOW_NAV = "showNav";
|
||||
const PROPERTY_SHOW_ALL_GRAPHS = "showAllGraphs";
|
||||
const PROPERTY_RESTRICTION = "toggleRestriction";
|
||||
export class BaseFastGroupsModeChanger extends RgthreeBaseVirtualNode {
|
||||
constructor(title = FastGroupsMuter.title) {
|
||||
super(title);
|
||||
this.modeOn = LiteGraph.ALWAYS;
|
||||
this.modeOff = LiteGraph.NEVER;
|
||||
this.debouncerTempWidth = 0;
|
||||
this.tempSize = null;
|
||||
this.serialize_widgets = false;
|
||||
this.helpActions = "mute and unmute";
|
||||
this.properties[PROPERTY_MATCH_COLORS] = "";
|
||||
this.properties[PROPERTY_MATCH_TITLE] = "";
|
||||
this.properties[PROPERTY_SHOW_NAV] = true;
|
||||
this.properties[PROPERTY_SHOW_ALL_GRAPHS] = true;
|
||||
this.properties[PROPERTY_SORT] = "position";
|
||||
this.properties[PROPERTY_SORT_CUSTOM_ALPHA] = "";
|
||||
this.properties[PROPERTY_RESTRICTION] = "default";
|
||||
}
|
||||
onConstructed() {
|
||||
this.addOutput("OPT_CONNECTION", "*");
|
||||
return super.onConstructed();
|
||||
}
|
||||
onAdded(graph) {
|
||||
FAST_GROUPS_SERVICE.addFastGroupNode(this);
|
||||
}
|
||||
onRemoved() {
|
||||
FAST_GROUPS_SERVICE.removeFastGroupNode(this);
|
||||
}
|
||||
refreshWidgets() {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
||||
const canvas = app.canvas;
|
||||
let sort = ((_a = this.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_SORT]) || "position";
|
||||
let customAlphabet = null;
|
||||
if (sort === "custom alphabet") {
|
||||
const customAlphaStr = (_c = (_b = this.properties) === null || _b === void 0 ? void 0 : _b[PROPERTY_SORT_CUSTOM_ALPHA]) === null || _c === void 0 ? void 0 : _c.replace(/\n/g, "");
|
||||
if (customAlphaStr && customAlphaStr.trim()) {
|
||||
customAlphabet = customAlphaStr.includes(",")
|
||||
? customAlphaStr.toLocaleLowerCase().split(",")
|
||||
: customAlphaStr.toLocaleLowerCase().trim().split("");
|
||||
}
|
||||
if (!(customAlphabet === null || customAlphabet === void 0 ? void 0 : customAlphabet.length)) {
|
||||
sort = "alphanumeric";
|
||||
customAlphabet = null;
|
||||
}
|
||||
}
|
||||
const groups = [...FAST_GROUPS_SERVICE.getGroups(sort)];
|
||||
if (customAlphabet === null || customAlphabet === void 0 ? void 0 : customAlphabet.length) {
|
||||
groups.sort((a, b) => {
|
||||
let aIndex = -1;
|
||||
let bIndex = -1;
|
||||
for (const [index, alpha] of customAlphabet.entries()) {
|
||||
aIndex =
|
||||
aIndex < 0 ? (a.title.toLocaleLowerCase().startsWith(alpha) ? index : -1) : aIndex;
|
||||
bIndex =
|
||||
bIndex < 0 ? (b.title.toLocaleLowerCase().startsWith(alpha) ? index : -1) : bIndex;
|
||||
if (aIndex > -1 && bIndex > -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (aIndex > -1 && bIndex > -1) {
|
||||
const ret = aIndex - bIndex;
|
||||
if (ret === 0) {
|
||||
return a.title.localeCompare(b.title);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
else if (aIndex > -1) {
|
||||
return -1;
|
||||
}
|
||||
else if (bIndex > -1) {
|
||||
return 1;
|
||||
}
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
}
|
||||
let filterColors = (((_e = (_d = this.properties) === null || _d === void 0 ? void 0 : _d[PROPERTY_MATCH_COLORS]) === null || _e === void 0 ? void 0 : _e.split(",")) || []).filter((c) => c.trim());
|
||||
if (filterColors.length) {
|
||||
filterColors = filterColors.map((color) => {
|
||||
color = color.trim().toLocaleLowerCase();
|
||||
if (LGraphCanvas.node_colors[color]) {
|
||||
color = LGraphCanvas.node_colors[color].groupcolor;
|
||||
}
|
||||
color = color.replace("#", "").toLocaleLowerCase();
|
||||
if (color.length === 3) {
|
||||
color = color.replace(/(.)(.)(.)/, "$1$1$2$2$3$3");
|
||||
}
|
||||
return `#${color}`;
|
||||
});
|
||||
}
|
||||
let index = 0;
|
||||
for (const group of groups) {
|
||||
if (filterColors.length) {
|
||||
let groupColor = (_f = group.color) === null || _f === void 0 ? void 0 : _f.replace("#", "").trim().toLocaleLowerCase();
|
||||
if (!groupColor) {
|
||||
continue;
|
||||
}
|
||||
if (groupColor.length === 3) {
|
||||
groupColor = groupColor.replace(/(.)(.)(.)/, "$1$1$2$2$3$3");
|
||||
}
|
||||
groupColor = `#${groupColor}`;
|
||||
if (!filterColors.includes(groupColor)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((_h = (_g = this.properties) === null || _g === void 0 ? void 0 : _g[PROPERTY_MATCH_TITLE]) === null || _h === void 0 ? void 0 : _h.trim()) {
|
||||
try {
|
||||
if (!new RegExp(this.properties[PROPERTY_MATCH_TITLE], "i").exec(group.title)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const showAllGraphs = (_j = this.properties) === null || _j === void 0 ? void 0 : _j[PROPERTY_SHOW_ALL_GRAPHS];
|
||||
if (!showAllGraphs && group.graph !== app.canvas.getCurrentGraph()) {
|
||||
continue;
|
||||
}
|
||||
let isDirty = false;
|
||||
const widgetLabel = `Enable ${group.title}`;
|
||||
let widget = this.widgets.find((w) => w.label === widgetLabel);
|
||||
if (!widget) {
|
||||
this.tempSize = [...this.size];
|
||||
widget = this.addCustomWidget(new FastGroupsToggleRowWidget(group, this));
|
||||
this.setSize(this.computeSize());
|
||||
isDirty = true;
|
||||
}
|
||||
if (widget.label != widgetLabel) {
|
||||
widget.label = widgetLabel;
|
||||
isDirty = true;
|
||||
}
|
||||
if (group.rgthree_hasAnyActiveNode != null &&
|
||||
widget.toggled != group.rgthree_hasAnyActiveNode) {
|
||||
widget.toggled = group.rgthree_hasAnyActiveNode;
|
||||
isDirty = true;
|
||||
}
|
||||
if (this.widgets[index] !== widget) {
|
||||
const oldIndex = this.widgets.findIndex((w) => w === widget);
|
||||
this.widgets.splice(index, 0, this.widgets.splice(oldIndex, 1)[0]);
|
||||
isDirty = true;
|
||||
}
|
||||
if (isDirty) {
|
||||
this.setDirtyCanvas(true, false);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
while ((this.widgets || [])[index]) {
|
||||
this.removeWidget(index++);
|
||||
}
|
||||
}
|
||||
computeSize(out) {
|
||||
let size = super.computeSize(out);
|
||||
if (this.tempSize) {
|
||||
size[0] = Math.max(this.tempSize[0], size[0]);
|
||||
size[1] = Math.max(this.tempSize[1], size[1]);
|
||||
this.debouncerTempWidth && clearTimeout(this.debouncerTempWidth);
|
||||
this.debouncerTempWidth = setTimeout(() => {
|
||||
this.tempSize = null;
|
||||
}, 32);
|
||||
}
|
||||
setTimeout(() => {
|
||||
var _a;
|
||||
(_a = this.graph) === null || _a === void 0 ? void 0 : _a.setDirtyCanvas(true, true);
|
||||
}, 16);
|
||||
return size;
|
||||
}
|
||||
async handleAction(action) {
|
||||
var _a, _b, _c, _d, _e;
|
||||
if (action === "Mute all" || action === "Bypass all") {
|
||||
const alwaysOne = ((_a = this.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_RESTRICTION]) === "always one";
|
||||
for (const [index, widget] of this.widgets.entries()) {
|
||||
widget === null || widget === void 0 ? void 0 : widget.doModeChange(alwaysOne && !index ? true : false, true);
|
||||
}
|
||||
}
|
||||
else if (action === "Enable all") {
|
||||
const onlyOne = (_b = this.properties) === null || _b === void 0 ? void 0 : _b[PROPERTY_RESTRICTION].includes(" one");
|
||||
for (const [index, widget] of this.widgets.entries()) {
|
||||
widget === null || widget === void 0 ? void 0 : widget.doModeChange(onlyOne && index > 0 ? false : true, true);
|
||||
}
|
||||
}
|
||||
else if (action === "Toggle all") {
|
||||
const onlyOne = (_c = this.properties) === null || _c === void 0 ? void 0 : _c[PROPERTY_RESTRICTION].includes(" one");
|
||||
let foundOne = false;
|
||||
for (const [index, widget] of this.widgets.entries()) {
|
||||
let newValue = onlyOne && foundOne ? false : !widget.value;
|
||||
foundOne = foundOne || newValue;
|
||||
widget === null || widget === void 0 ? void 0 : widget.doModeChange(newValue, true);
|
||||
}
|
||||
if (!foundOne && ((_d = this.properties) === null || _d === void 0 ? void 0 : _d[PROPERTY_RESTRICTION]) === "always one") {
|
||||
(_e = this.widgets[this.widgets.length - 1]) === null || _e === void 0 ? void 0 : _e.doModeChange(true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
getHelp() {
|
||||
return `
|
||||
<p>The ${this.type.replace("(rgthree)", "")} is an input-less node that automatically collects all groups in your current
|
||||
workflow and allows you to quickly ${this.helpActions} all nodes within the group.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Properties.</strong> You can change the following properties (by right-clicking
|
||||
on the node, and select "Properties" or "Properties Panel" from the menu):
|
||||
</p>
|
||||
<ul>
|
||||
<li><p>
|
||||
<code>${PROPERTY_MATCH_COLORS}</code> - Only add groups that match the provided
|
||||
colors. Can be ComfyUI colors (red, pale_blue) or hex codes (#a4d399). Multiple can be
|
||||
added, comma delimited.
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>${PROPERTY_MATCH_TITLE}</code> - Filter the list of toggles by title match
|
||||
(string match, or regular expression).
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>${PROPERTY_SHOW_NAV}</code> - Add / remove a quick navigation arrow to take you
|
||||
to the group. <i>(default: true)</i>
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>${PROPERTY_SHOW_ALL_GRAPHS}</code> - Show groups from all [sub]graphs in the
|
||||
workflow. <i>(default: true)</i>
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>${PROPERTY_SORT}</code> - Sort the toggles' order by "alphanumeric", graph
|
||||
"position", or "custom alphabet". <i>(default: "position")</i>
|
||||
</p></li>
|
||||
<li>
|
||||
<p>
|
||||
<code>${PROPERTY_SORT_CUSTOM_ALPHA}</code> - When the
|
||||
<code>${PROPERTY_SORT}</code> property is "custom alphabet" you can define the
|
||||
alphabet to use here, which will match the <i>beginning</i> of each group name and
|
||||
sort against it. If group titles do not match any custom alphabet entry, then they
|
||||
will be put after groups that do, ordered alphanumerically.
|
||||
</p>
|
||||
<p>
|
||||
This can be a list of single characters, like "zyxw..." or comma delimited strings
|
||||
for more control, like "sdxl,pro,sd,n,p".
|
||||
</p>
|
||||
<p>
|
||||
Note, when two group title match the same custom alphabet entry, the <i>normal
|
||||
alphanumeric alphabet</i> breaks the tie. For instance, a custom alphabet of
|
||||
"e,s,d" will order groups names like "SDXL, SEGS, Detailer" eventhough the custom
|
||||
alphabet has an "e" before "d" (where one may expect "SE" to be before "SD").
|
||||
</p>
|
||||
<p>
|
||||
To have "SEGS" appear before "SDXL" you can use longer strings. For instance, the
|
||||
custom alphabet value of "se,s,f" would work here.
|
||||
</p>
|
||||
</li>
|
||||
<li><p>
|
||||
<code>${PROPERTY_RESTRICTION}</code> - Optionally, attempt to restrict the number of
|
||||
widgets that can be enabled to a maximum of one, or always one.
|
||||
</p>
|
||||
<p><em><strong>Note:</strong> If using "max one" or "always one" then this is only
|
||||
enforced when clicking a toggle on this node; if nodes within groups are changed
|
||||
outside of the initial toggle click, then these restriction will not be enforced, and
|
||||
could result in a state where more than one toggle is enabled. This could also happen
|
||||
if nodes are overlapped with multiple groups.
|
||||
</p></li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
</ul>`;
|
||||
}
|
||||
}
|
||||
BaseFastGroupsModeChanger.type = NodeTypesString.FAST_GROUPS_MUTER;
|
||||
BaseFastGroupsModeChanger.title = NodeTypesString.FAST_GROUPS_MUTER;
|
||||
BaseFastGroupsModeChanger.exposedActions = ["Mute all", "Enable all", "Toggle all"];
|
||||
BaseFastGroupsModeChanger["@matchColors"] = { type: "string" };
|
||||
BaseFastGroupsModeChanger["@matchTitle"] = { type: "string" };
|
||||
BaseFastGroupsModeChanger["@showNav"] = { type: "boolean" };
|
||||
BaseFastGroupsModeChanger["@showAllGraphs"] = { type: "boolean" };
|
||||
BaseFastGroupsModeChanger["@sort"] = {
|
||||
type: "combo",
|
||||
values: ["position", "alphanumeric", "custom alphabet"],
|
||||
};
|
||||
BaseFastGroupsModeChanger["@customSortAlphabet"] = { type: "string" };
|
||||
BaseFastGroupsModeChanger["@toggleRestriction"] = {
|
||||
type: "combo",
|
||||
values: ["default", "max one", "always one"],
|
||||
};
|
||||
export class FastGroupsMuter extends BaseFastGroupsModeChanger {
|
||||
constructor(title = FastGroupsMuter.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.FAST_GROUPS_MUTER;
|
||||
this.helpActions = "mute and unmute";
|
||||
this.modeOn = LiteGraph.ALWAYS;
|
||||
this.modeOff = LiteGraph.NEVER;
|
||||
this.onConstructed();
|
||||
}
|
||||
}
|
||||
FastGroupsMuter.type = NodeTypesString.FAST_GROUPS_MUTER;
|
||||
FastGroupsMuter.title = NodeTypesString.FAST_GROUPS_MUTER;
|
||||
FastGroupsMuter.exposedActions = ["Bypass all", "Enable all", "Toggle all"];
|
||||
class FastGroupsToggleRowWidget extends RgthreeBaseWidget {
|
||||
constructor(group, node) {
|
||||
super("RGTHREE_TOGGLE_AND_NAV");
|
||||
this.value = { toggled: false };
|
||||
this.options = { on: "yes", off: "no" };
|
||||
this.type = "custom";
|
||||
this.label = "";
|
||||
this.group = group;
|
||||
this.node = node;
|
||||
}
|
||||
doModeChange(force, skipOtherNodeCheck) {
|
||||
var _a, _b, _c, _d;
|
||||
this.group.recomputeInsideNodes();
|
||||
const hasAnyActiveNodes = getGroupNodes(this.group).some((n) => n.mode === LiteGraph.ALWAYS);
|
||||
let newValue = force != null ? force : !hasAnyActiveNodes;
|
||||
if (skipOtherNodeCheck !== true) {
|
||||
if (newValue && ((_b = (_a = this.node.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_RESTRICTION]) === null || _b === void 0 ? void 0 : _b.includes(" one"))) {
|
||||
for (const widget of this.node.widgets) {
|
||||
if (widget instanceof FastGroupsToggleRowWidget) {
|
||||
widget.doModeChange(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!newValue && ((_c = this.node.properties) === null || _c === void 0 ? void 0 : _c[PROPERTY_RESTRICTION]) === "always one") {
|
||||
newValue = this.node.widgets.every((w) => !w.value || w === this);
|
||||
}
|
||||
}
|
||||
changeModeOfNodes(getGroupNodes(this.group), (newValue ? this.node.modeOn : this.node.modeOff));
|
||||
this.group.rgthree_hasAnyActiveNode = newValue;
|
||||
this.toggled = newValue;
|
||||
(_d = this.group.graph) === null || _d === void 0 ? void 0 : _d.setDirtyCanvas(true, false);
|
||||
}
|
||||
get toggled() {
|
||||
return this.value.toggled;
|
||||
}
|
||||
set toggled(value) {
|
||||
this.value.toggled = value;
|
||||
}
|
||||
toggle(value) {
|
||||
value = value == null ? !this.toggled : value;
|
||||
if (value !== this.toggled) {
|
||||
this.value.toggled = value;
|
||||
this.doModeChange();
|
||||
}
|
||||
}
|
||||
draw(ctx, node, width, posY, height) {
|
||||
var _a;
|
||||
const widgetData = drawNodeWidget(ctx, { size: [width, height], pos: [15, posY] });
|
||||
const showNav = ((_a = node.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_SHOW_NAV]) !== false;
|
||||
let currentX = widgetData.width - widgetData.margin;
|
||||
if (!widgetData.lowQuality && showNav) {
|
||||
currentX -= 7;
|
||||
const midY = widgetData.posY + widgetData.height * 0.5;
|
||||
ctx.fillStyle = ctx.strokeStyle = "#89A";
|
||||
ctx.lineJoin = "round";
|
||||
ctx.lineCap = "round";
|
||||
const arrow = new Path2D(`M${currentX} ${midY} l -7 6 v -3 h -7 v -6 h 7 v -3 z`);
|
||||
ctx.fill(arrow);
|
||||
ctx.stroke(arrow);
|
||||
currentX -= 14;
|
||||
currentX -= 7;
|
||||
ctx.strokeStyle = widgetData.colorOutline;
|
||||
ctx.stroke(new Path2D(`M ${currentX} ${widgetData.posY} v ${widgetData.height}`));
|
||||
}
|
||||
else if (widgetData.lowQuality && showNav) {
|
||||
currentX -= 28;
|
||||
}
|
||||
currentX -= 7;
|
||||
ctx.fillStyle = this.toggled ? "#89A" : "#333";
|
||||
ctx.beginPath();
|
||||
const toggleRadius = height * 0.36;
|
||||
ctx.arc(currentX - toggleRadius, posY + height * 0.5, toggleRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
currentX -= toggleRadius * 2;
|
||||
if (!widgetData.lowQuality) {
|
||||
currentX -= 4;
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillStyle = this.toggled ? widgetData.colorText : widgetData.colorTextSecondary;
|
||||
const label = this.label;
|
||||
const toggleLabelOn = this.options.on || "true";
|
||||
const toggleLabelOff = this.options.off || "false";
|
||||
ctx.fillText(this.toggled ? toggleLabelOn : toggleLabelOff, currentX, posY + height * 0.7);
|
||||
currentX -= Math.max(ctx.measureText(toggleLabelOn).width, ctx.measureText(toggleLabelOff).width);
|
||||
currentX -= 7;
|
||||
ctx.textAlign = "left";
|
||||
let maxLabelWidth = widgetData.width - widgetData.margin - 10 - (widgetData.width - currentX);
|
||||
if (label != null) {
|
||||
ctx.fillText(fitString(ctx, label, maxLabelWidth), widgetData.margin + 10, posY + height * 0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
serializeValue(node, index) {
|
||||
return this.value;
|
||||
}
|
||||
mouse(event, pos, node) {
|
||||
var _a, _b, _c;
|
||||
if (event.type == "pointerdown") {
|
||||
if (((_a = node.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_SHOW_NAV]) !== false && pos[0] >= node.size[0] - 15 - 28 - 1) {
|
||||
const canvas = app.canvas;
|
||||
const lowQuality = (((_b = canvas.ds) === null || _b === void 0 ? void 0 : _b.scale) || 1) <= 0.5;
|
||||
if (!lowQuality) {
|
||||
canvas.centerOnNode(this.group);
|
||||
const zoomCurrent = ((_c = canvas.ds) === null || _c === void 0 ? void 0 : _c.scale) || 1;
|
||||
const zoomX = canvas.canvas.width / this.group._size[0] - 0.02;
|
||||
const zoomY = canvas.canvas.height / this.group._size[1] - 0.02;
|
||||
canvas.setZoom(Math.min(zoomCurrent, zoomX, zoomY), [
|
||||
canvas.canvas.width / 2,
|
||||
canvas.canvas.height / 2,
|
||||
]);
|
||||
canvas.setDirty(true, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "rgthree.FastGroupsMuter",
|
||||
registerCustomNodes() {
|
||||
FastGroupsMuter.setUp();
|
||||
},
|
||||
loadedGraphNode(node) {
|
||||
if (node.type == FastGroupsMuter.title) {
|
||||
node.tempSize = [...node.size];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,220 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { changeModeOfNodes, getGroupNodes, getOutputNodes } from "./utils.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
const BTN_SIZE = 20;
|
||||
const BTN_MARGIN = [6, 6];
|
||||
const BTN_SPACING = 8;
|
||||
const BTN_GRID = BTN_SIZE / 8;
|
||||
const TOGGLE_TO_MODE = new Map([
|
||||
["MUTE", LiteGraph.NEVER],
|
||||
["BYPASS", 4],
|
||||
]);
|
||||
function getToggles() {
|
||||
return [...CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.toggles", [])].reverse();
|
||||
}
|
||||
function clickedOnToggleButton(e, group) {
|
||||
const toggles = getToggles();
|
||||
const pos = group.pos;
|
||||
const size = group.size;
|
||||
for (let i = 0; i < toggles.length; i++) {
|
||||
const toggle = toggles[i];
|
||||
if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, pos[0] + size[0] - (BTN_SIZE + BTN_MARGIN[0]) * (i + 1), pos[1] + BTN_MARGIN[1], BTN_SIZE, BTN_SIZE)) {
|
||||
return toggle;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "rgthree.GroupHeaderToggles",
|
||||
async setup() {
|
||||
setInterval(() => {
|
||||
if (CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled") &&
|
||||
CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.show") !== "always") {
|
||||
app.canvas.setDirty(true, true);
|
||||
}
|
||||
}, 250);
|
||||
rgthree.addEventListener("on-process-mouse-down", ((e) => {
|
||||
if (!CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled"))
|
||||
return;
|
||||
const canvas = app.canvas;
|
||||
if (canvas.selected_group) {
|
||||
const originalEvent = e.detail.originalEvent;
|
||||
const group = canvas.selected_group;
|
||||
const clickedOnToggle = clickedOnToggleButton(originalEvent, group) || "";
|
||||
const toggleAction = clickedOnToggle === null || clickedOnToggle === void 0 ? void 0 : clickedOnToggle.toLocaleUpperCase();
|
||||
if (toggleAction) {
|
||||
console.log(toggleAction);
|
||||
const nodes = getGroupNodes(group);
|
||||
if (toggleAction === "QUEUE") {
|
||||
const outputNodes = getOutputNodes(nodes);
|
||||
if (!(outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length)) {
|
||||
rgthree.showMessage({
|
||||
id: "no-output-in-group",
|
||||
type: "warn",
|
||||
timeout: 4000,
|
||||
message: "No output nodes for group!",
|
||||
});
|
||||
}
|
||||
else {
|
||||
rgthree.queueOutputNodes(outputNodes);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const toggleMode = TOGGLE_TO_MODE.get(toggleAction);
|
||||
if (toggleMode) {
|
||||
group.recomputeInsideNodes();
|
||||
const hasAnyActiveNodes = nodes.some((n) => n.mode === LiteGraph.ALWAYS);
|
||||
const isAllMuted = !hasAnyActiveNodes && nodes.every((n) => n.mode === LiteGraph.NEVER);
|
||||
const isAllBypassed = !hasAnyActiveNodes && !isAllMuted && nodes.every((n) => n.mode === 4);
|
||||
let newMode = LiteGraph.ALWAYS;
|
||||
if (toggleMode === LiteGraph.NEVER) {
|
||||
newMode = isAllMuted ? LiteGraph.ALWAYS : LiteGraph.NEVER;
|
||||
}
|
||||
else {
|
||||
newMode = isAllBypassed ? LiteGraph.ALWAYS : 4;
|
||||
}
|
||||
changeModeOfNodes(nodes, newMode);
|
||||
}
|
||||
}
|
||||
canvas.selected_group = null;
|
||||
canvas.dragging_canvas = false;
|
||||
}
|
||||
}
|
||||
}));
|
||||
const drawGroups = LGraphCanvas.prototype.drawGroups;
|
||||
LGraphCanvas.prototype.drawGroups = function (canvasEl, ctx) {
|
||||
drawGroups.apply(this, [...arguments]);
|
||||
if (!CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled") ||
|
||||
!rgthree.lastCanvasMouseEvent) {
|
||||
return;
|
||||
}
|
||||
const graph = app.canvas.graph;
|
||||
let groups;
|
||||
if (CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.show") !== "always") {
|
||||
const hoverGroup = graph.getGroupOnPos(rgthree.lastCanvasMouseEvent.canvasX, rgthree.lastCanvasMouseEvent.canvasY);
|
||||
groups = hoverGroup ? [hoverGroup] : [];
|
||||
}
|
||||
else {
|
||||
groups = graph._groups || [];
|
||||
}
|
||||
if (!groups.length) {
|
||||
return;
|
||||
}
|
||||
const toggles = getToggles();
|
||||
ctx.save();
|
||||
for (const group of groups || []) {
|
||||
const nodes = getGroupNodes(group);
|
||||
let anyActive = false;
|
||||
let allMuted = !!nodes.length;
|
||||
let allBypassed = allMuted;
|
||||
for (const node of nodes) {
|
||||
if (!(node instanceof LGraphNode))
|
||||
continue;
|
||||
anyActive = anyActive || node.mode === LiteGraph.ALWAYS;
|
||||
allMuted = allMuted && node.mode === LiteGraph.NEVER;
|
||||
allBypassed = allBypassed && node.mode === 4;
|
||||
if (anyActive || (!allMuted && !allBypassed)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < toggles.length; i++) {
|
||||
const toggle = toggles[i];
|
||||
const pos = group._pos;
|
||||
const size = group._size;
|
||||
ctx.fillStyle = ctx.strokeStyle = group.color || "#335";
|
||||
const x = pos[0] + size[0] - BTN_MARGIN[0] - BTN_SIZE - (BTN_SPACING + BTN_SIZE) * i;
|
||||
const y = pos[1] + BTN_MARGIN[1];
|
||||
const midX = x + BTN_SIZE / 2;
|
||||
const midY = y + BTN_SIZE / 2;
|
||||
if (toggle === "queue") {
|
||||
const outputNodes = getOutputNodes(nodes);
|
||||
const oldGlobalAlpha = ctx.globalAlpha;
|
||||
if (!(outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length)) {
|
||||
ctx.globalAlpha = 0.5;
|
||||
}
|
||||
ctx.lineJoin = "round";
|
||||
ctx.lineCap = "round";
|
||||
const arrowSizeX = BTN_SIZE * 0.6;
|
||||
const arrowSizeY = BTN_SIZE * 0.7;
|
||||
const arrow = new Path2D(`M ${x + arrowSizeX / 2} ${midY} l 0 -${arrowSizeY / 2} l ${arrowSizeX} ${arrowSizeY / 2} l -${arrowSizeX} ${arrowSizeY / 2} z`);
|
||||
ctx.stroke(arrow);
|
||||
if (outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length) {
|
||||
ctx.fill(arrow);
|
||||
}
|
||||
ctx.globalAlpha = oldGlobalAlpha;
|
||||
}
|
||||
else {
|
||||
const on = toggle === "bypass" ? allBypassed : allMuted;
|
||||
ctx.beginPath();
|
||||
ctx.lineJoin = "round";
|
||||
ctx.rect(x, y, BTN_SIZE, BTN_SIZE);
|
||||
ctx.lineWidth = 2;
|
||||
if (toggle === "mute") {
|
||||
ctx.lineJoin = "round";
|
||||
ctx.lineCap = "round";
|
||||
if (on) {
|
||||
ctx.stroke(new Path2D(`
|
||||
${eyeFrame(midX, midY)}
|
||||
${eyeLashes(midX, midY)}
|
||||
`));
|
||||
}
|
||||
else {
|
||||
const radius = BTN_GRID * 1.5;
|
||||
ctx.fill(new Path2D(`
|
||||
${eyeFrame(midX, midY)}
|
||||
${eyeFrame(midX, midY, -1)}
|
||||
${circlePath(midX, midY, radius)}
|
||||
${circlePath(midX + BTN_GRID / 2, midY - BTN_GRID / 2, BTN_GRID * 0.375)}
|
||||
`), "evenodd");
|
||||
ctx.stroke(new Path2D(`${eyeFrame(midX, midY)} ${eyeFrame(midX, midY, -1)}`));
|
||||
ctx.globalAlpha = this.editor_alpha * 0.5;
|
||||
ctx.stroke(new Path2D(`${eyeLashes(midX, midY)} ${eyeLashes(midX, midY, -1)}`));
|
||||
ctx.globalAlpha = this.editor_alpha;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const lineChanges = on
|
||||
? `a ${BTN_GRID * 3}, ${BTN_GRID * 3} 0 1, 1 ${BTN_GRID * 3 * 2},0
|
||||
l ${BTN_GRID * 2.0} 0`
|
||||
: `l ${BTN_GRID * 8} 0`;
|
||||
ctx.stroke(new Path2D(`
|
||||
M ${x} ${midY}
|
||||
${lineChanges}
|
||||
M ${x + BTN_SIZE} ${midY} l -2 2
|
||||
M ${x + BTN_SIZE} ${midY} l -2 -2
|
||||
`));
|
||||
ctx.fill(new Path2D(`${circlePath(x + BTN_GRID * 3, midY, BTN_GRID * 1.8)}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
};
|
||||
},
|
||||
});
|
||||
function eyeFrame(midX, midY, yFlip = 1) {
|
||||
return `
|
||||
M ${midX - BTN_SIZE / 2} ${midY}
|
||||
c ${BTN_GRID * 1.5} ${yFlip * BTN_GRID * 2.5}, ${BTN_GRID * (8 - 1.5)} ${yFlip * BTN_GRID * 2.5}, ${BTN_GRID * 8} 0
|
||||
`;
|
||||
}
|
||||
function eyeLashes(midX, midY, yFlip = 1) {
|
||||
return `
|
||||
M ${midX - BTN_GRID * 3.46} ${midY + yFlip * BTN_GRID * 0.9} l -1.15 ${1.25 * yFlip}
|
||||
M ${midX - BTN_GRID * 2.38} ${midY + yFlip * BTN_GRID * 1.6} l -0.90 ${1.5 * yFlip}
|
||||
M ${midX - BTN_GRID * 1.15} ${midY + yFlip * BTN_GRID * 1.95} l -0.50 ${1.75 * yFlip}
|
||||
M ${midX + BTN_GRID * 0.0} ${midY + yFlip * BTN_GRID * 2.0} l 0.00 ${2.0 * yFlip}
|
||||
M ${midX + BTN_GRID * 1.15} ${midY + yFlip * BTN_GRID * 1.95} l 0.50 ${1.75 * yFlip}
|
||||
M ${midX + BTN_GRID * 2.38} ${midY + yFlip * BTN_GRID * 1.6} l 0.90 ${1.5 * yFlip}
|
||||
M ${midX + BTN_GRID * 3.46} ${midY + yFlip * BTN_GRID * 0.9} l 1.15 ${1.25 * yFlip}
|
||||
`;
|
||||
}
|
||||
function circlePath(cx, cy, radius) {
|
||||
return `
|
||||
M ${cx} ${cy}
|
||||
m ${radius}, 0
|
||||
a ${radius},${radius} 0 1, 1 -${radius * 2},0
|
||||
a ${radius},${radius} 0 1, 1 ${radius * 2},0
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { tryToGetWorkflowDataFromEvent } from "../../rgthree/common/utils_workflow.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
app.registerExtension({
|
||||
name: "rgthree.ImportIndividualNodes",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
const onDragOver = nodeType.prototype.onDragOver;
|
||||
nodeType.prototype.onDragOver = function (e) {
|
||||
var _a;
|
||||
let handled = (_a = onDragOver === null || onDragOver === void 0 ? void 0 : onDragOver.apply) === null || _a === void 0 ? void 0 : _a.call(onDragOver, this, [...arguments]);
|
||||
if (handled != null) {
|
||||
return handled;
|
||||
}
|
||||
return importIndividualNodesInnerOnDragOver(this, e);
|
||||
};
|
||||
const onDragDrop = nodeType.prototype.onDragDrop;
|
||||
nodeType.prototype.onDragDrop = async function (e) {
|
||||
var _a;
|
||||
const alreadyHandled = await ((_a = onDragDrop === null || onDragDrop === void 0 ? void 0 : onDragDrop.apply) === null || _a === void 0 ? void 0 : _a.call(onDragDrop, this, [...arguments]));
|
||||
if (alreadyHandled) {
|
||||
return alreadyHandled;
|
||||
}
|
||||
return importIndividualNodesInnerOnDragDrop(this, e);
|
||||
};
|
||||
},
|
||||
});
|
||||
export function importIndividualNodesInnerOnDragOver(node, e) {
|
||||
var _a;
|
||||
return ((((_a = node.widgets) === null || _a === void 0 ? void 0 : _a.length) && !!CONFIG_SERVICE.getFeatureValue("import_individual_nodes.enabled")) ||
|
||||
false);
|
||||
}
|
||||
export async function importIndividualNodesInnerOnDragDrop(node, e) {
|
||||
var _a, _b;
|
||||
if (!((_a = node.widgets) === null || _a === void 0 ? void 0 : _a.length) || !CONFIG_SERVICE.getFeatureValue("import_individual_nodes.enabled")) {
|
||||
return false;
|
||||
}
|
||||
const dynamicWidgetLengthNodes = [NodeTypesString.POWER_LORA_LOADER];
|
||||
let handled = false;
|
||||
const { workflow, prompt } = await tryToGetWorkflowDataFromEvent(e);
|
||||
const exact = ((workflow === null || workflow === void 0 ? void 0 : workflow.nodes) || []).find((n) => {
|
||||
var _a, _b;
|
||||
return n.id === node.id &&
|
||||
n.type === node.type &&
|
||||
(dynamicWidgetLengthNodes.includes(node.type) ||
|
||||
((_a = n.widgets_values) === null || _a === void 0 ? void 0 : _a.length) === ((_b = node.widgets_values) === null || _b === void 0 ? void 0 : _b.length));
|
||||
});
|
||||
if (!exact) {
|
||||
handled = !confirm("[rgthree-comfy] Could not find a matching node (same id & type) in the dropped workflow." +
|
||||
" Would you like to continue with the default drop behaviour instead?");
|
||||
}
|
||||
else if (!((_b = exact.widgets_values) === null || _b === void 0 ? void 0 : _b.length)) {
|
||||
handled = !confirm("[rgthree-comfy] Matching node found (same id & type) but there's no widgets to set." +
|
||||
" Would you like to continue with the default drop behaviour instead?");
|
||||
}
|
||||
else if (confirm("[rgthree-comfy] Found a matching node (same id & type) in the dropped workflow." +
|
||||
" Would you like to set the widget values?")) {
|
||||
node.configure({
|
||||
title: node.title,
|
||||
widgets_values: [...((exact === null || exact === void 0 ? void 0 : exact.widgets_values) || [])],
|
||||
mode: exact.mode,
|
||||
});
|
||||
handled = true;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
365
custom_nodes/rgthree-comfy/web/comfyui/image_comparer.js
Normal file
365
custom_nodes/rgthree-comfy/web/comfyui/image_comparer.js
Normal file
@@ -0,0 +1,365 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { addConnectionLayoutSupport } from "./utils.js";
|
||||
import { RgthreeBaseWidget } from "./utils_widgets.js";
|
||||
import { measureText } from "./utils_canvas.js";
|
||||
function imageDataToUrl(data) {
|
||||
return api.apiURL(`/view?filename=${encodeURIComponent(data.filename)}&type=${data.type}&subfolder=${data.subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`);
|
||||
}
|
||||
export class RgthreeImageComparer extends RgthreeBaseServerNode {
|
||||
constructor(title = RgthreeImageComparer.title) {
|
||||
super(title);
|
||||
this.imageIndex = 0;
|
||||
this.imgs = [];
|
||||
this.serialize_widgets = true;
|
||||
this.isPointerDown = false;
|
||||
this.isPointerOver = false;
|
||||
this.pointerOverPos = [0, 0];
|
||||
this.canvasWidget = null;
|
||||
this.properties["comparer_mode"] = "Slide";
|
||||
}
|
||||
onExecuted(output) {
|
||||
var _a;
|
||||
(_a = super.onExecuted) === null || _a === void 0 ? void 0 : _a.call(this, output);
|
||||
if ("images" in output) {
|
||||
this.canvasWidget.value = {
|
||||
images: (output.images || []).map((d, i) => {
|
||||
return {
|
||||
name: i === 0 ? "A" : "B",
|
||||
selected: true,
|
||||
url: imageDataToUrl(d),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
else {
|
||||
output.a_images = output.a_images || [];
|
||||
output.b_images = output.b_images || [];
|
||||
const imagesToChoose = [];
|
||||
const multiple = output.a_images.length + output.b_images.length > 2;
|
||||
for (const [i, d] of output.a_images.entries()) {
|
||||
imagesToChoose.push({
|
||||
name: output.a_images.length > 1 || multiple ? `A${i + 1}` : "A",
|
||||
selected: i === 0,
|
||||
url: imageDataToUrl(d),
|
||||
});
|
||||
}
|
||||
for (const [i, d] of output.b_images.entries()) {
|
||||
imagesToChoose.push({
|
||||
name: output.b_images.length > 1 || multiple ? `B${i + 1}` : "B",
|
||||
selected: i === 0,
|
||||
url: imageDataToUrl(d),
|
||||
});
|
||||
}
|
||||
this.canvasWidget.value = { images: imagesToChoose };
|
||||
}
|
||||
}
|
||||
onSerialize(serialised) {
|
||||
var _a;
|
||||
super.onSerialize && super.onSerialize(serialised);
|
||||
for (let [index, widget_value] of (serialised.widgets_values || []).entries()) {
|
||||
if (((_a = this.widgets[index]) === null || _a === void 0 ? void 0 : _a.name) === "rgthree_comparer") {
|
||||
serialised.widgets_values[index] = this.widgets[index].value.images.map((d) => {
|
||||
d = { ...d };
|
||||
delete d.img;
|
||||
return d;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
onNodeCreated() {
|
||||
this.canvasWidget = this.addCustomWidget(new RgthreeImageComparerWidget("rgthree_comparer", this));
|
||||
this.setSize(this.computeSize());
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
setIsPointerDown(down = this.isPointerDown) {
|
||||
const newIsDown = down && !!app.canvas.pointer_is_down;
|
||||
if (this.isPointerDown !== newIsDown) {
|
||||
this.isPointerDown = newIsDown;
|
||||
this.setDirtyCanvas(true, false);
|
||||
}
|
||||
this.imageIndex = this.isPointerDown ? 1 : 0;
|
||||
if (this.isPointerDown) {
|
||||
requestAnimationFrame(() => {
|
||||
this.setIsPointerDown();
|
||||
});
|
||||
}
|
||||
}
|
||||
onMouseDown(event, pos, canvas) {
|
||||
var _a;
|
||||
(_a = super.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, canvas);
|
||||
this.setIsPointerDown(true);
|
||||
return false;
|
||||
}
|
||||
onMouseEnter(event) {
|
||||
var _a;
|
||||
(_a = super.onMouseEnter) === null || _a === void 0 ? void 0 : _a.call(this, event);
|
||||
this.setIsPointerDown(!!app.canvas.pointer_is_down);
|
||||
this.isPointerOver = true;
|
||||
}
|
||||
onMouseLeave(event) {
|
||||
var _a;
|
||||
(_a = super.onMouseLeave) === null || _a === void 0 ? void 0 : _a.call(this, event);
|
||||
this.setIsPointerDown(false);
|
||||
this.isPointerOver = false;
|
||||
}
|
||||
onMouseMove(event, pos, canvas) {
|
||||
var _a;
|
||||
(_a = super.onMouseMove) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, canvas);
|
||||
this.pointerOverPos = [...pos];
|
||||
this.imageIndex = this.pointerOverPos[0] > this.size[0] / 2 ? 1 : 0;
|
||||
}
|
||||
getHelp() {
|
||||
return `
|
||||
<p>
|
||||
The ${this.type.replace("(rgthree)", "")} node compares two images on top of each other.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Notes</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li><p>
|
||||
The right-click menu may show image options (Open Image, Save Image, etc.) which will
|
||||
correspond to the first image (image_a) if clicked on the left-half of the node, or
|
||||
the second image if on the right half of the node.
|
||||
</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Inputs</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li><p>
|
||||
<code>image_a</code> <i>Optional.</i> The first image to use to compare.
|
||||
image_a.
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>image_b</code> <i>Optional.</i> The second image to use to compare.
|
||||
</p></li>
|
||||
<li><p>
|
||||
<b>Note</b> <code>image_a</code> and <code>image_b</code> work best when a single
|
||||
image is provided. However, if each/either are a batch, you can choose which item
|
||||
from each batch are chosen to be compared. If either <code>image_a</code> or
|
||||
<code>image_b</code> are not provided, the node will choose the first two from the
|
||||
provided input if it's a batch, otherwise only show the single image (just as
|
||||
Preview Image would).
|
||||
</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Properties.</strong> You can change the following properties (by right-clicking
|
||||
on the node, and select "Properties" or "Properties Panel" from the menu):
|
||||
</p>
|
||||
<ul>
|
||||
<li><p>
|
||||
<code>comparer_mode</code> - Choose between "Slide" and "Click". Defaults to "Slide".
|
||||
</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>`;
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, RgthreeImageComparer);
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass) {
|
||||
addConnectionLayoutSupport(RgthreeImageComparer, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
setTimeout(() => {
|
||||
RgthreeImageComparer.category = comfyClass.category;
|
||||
});
|
||||
}
|
||||
}
|
||||
RgthreeImageComparer.title = NodeTypesString.IMAGE_COMPARER;
|
||||
RgthreeImageComparer.type = NodeTypesString.IMAGE_COMPARER;
|
||||
RgthreeImageComparer.comfyClass = NodeTypesString.IMAGE_COMPARER;
|
||||
RgthreeImageComparer["@comparer_mode"] = {
|
||||
type: "combo",
|
||||
values: ["Slide", "Click"],
|
||||
};
|
||||
class RgthreeImageComparerWidget extends RgthreeBaseWidget {
|
||||
constructor(name, node) {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
this.hitAreas = {};
|
||||
this.selected = [];
|
||||
this._value = { images: [] };
|
||||
this.node = node;
|
||||
}
|
||||
set value(v) {
|
||||
let cleanedVal;
|
||||
if (Array.isArray(v)) {
|
||||
cleanedVal = v.map((d, i) => {
|
||||
if (!d || typeof d === "string") {
|
||||
d = { url: d, name: i == 0 ? "A" : "B", selected: true };
|
||||
}
|
||||
return d;
|
||||
});
|
||||
}
|
||||
else {
|
||||
cleanedVal = v.images || [];
|
||||
}
|
||||
if (cleanedVal.length > 2) {
|
||||
const hasAAndB = cleanedVal.some((i) => i.name.startsWith("A")) &&
|
||||
cleanedVal.some((i) => i.name.startsWith("B"));
|
||||
if (!hasAAndB) {
|
||||
cleanedVal = [cleanedVal[0], cleanedVal[1]];
|
||||
}
|
||||
}
|
||||
let selected = cleanedVal.filter((d) => d.selected);
|
||||
if (!selected.length && cleanedVal.length) {
|
||||
cleanedVal[0].selected = true;
|
||||
}
|
||||
selected = cleanedVal.filter((d) => d.selected);
|
||||
if (selected.length === 1 && cleanedVal.length > 1) {
|
||||
cleanedVal.find((d) => !d.selected).selected = true;
|
||||
}
|
||||
this._value.images = cleanedVal;
|
||||
selected = cleanedVal.filter((d) => d.selected);
|
||||
this.setSelected(selected);
|
||||
}
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
setSelected(selected) {
|
||||
this._value.images.forEach((d) => (d.selected = false));
|
||||
this.node.imgs.length = 0;
|
||||
for (const sel of selected) {
|
||||
if (!sel.img) {
|
||||
sel.img = new Image();
|
||||
sel.img.src = sel.url;
|
||||
this.node.imgs.push(sel.img);
|
||||
}
|
||||
sel.selected = true;
|
||||
}
|
||||
this.selected = selected;
|
||||
}
|
||||
draw(ctx, node, width, y) {
|
||||
var _a;
|
||||
this.hitAreas = {};
|
||||
if (this.value.images.length > 2) {
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.font = `14px Arial`;
|
||||
const drawData = [];
|
||||
const spacing = 5;
|
||||
let x = 0;
|
||||
for (const img of this.value.images) {
|
||||
const width = measureText(ctx, img.name);
|
||||
drawData.push({
|
||||
img,
|
||||
text: img.name,
|
||||
x,
|
||||
width: measureText(ctx, img.name),
|
||||
});
|
||||
x += width + spacing;
|
||||
}
|
||||
x = (node.size[0] - (x - spacing)) / 2;
|
||||
for (const d of drawData) {
|
||||
ctx.fillStyle = d.img.selected ? "rgba(180, 180, 180, 1)" : "rgba(180, 180, 180, 0.5)";
|
||||
ctx.fillText(d.text, x, y);
|
||||
this.hitAreas[d.text] = {
|
||||
bounds: [x, y, d.width, 14],
|
||||
data: d.img,
|
||||
onDown: this.onSelectionDown,
|
||||
};
|
||||
x += d.width + spacing;
|
||||
}
|
||||
y += 20;
|
||||
}
|
||||
if (((_a = node.properties) === null || _a === void 0 ? void 0 : _a["comparer_mode"]) === "Click") {
|
||||
this.drawImage(ctx, this.selected[this.node.isPointerDown ? 1 : 0], y);
|
||||
}
|
||||
else {
|
||||
this.drawImage(ctx, this.selected[0], y);
|
||||
if (node.isPointerOver) {
|
||||
this.drawImage(ctx, this.selected[1], y, this.node.pointerOverPos[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
onSelectionDown(event, pos, node, bounds) {
|
||||
const selected = [...this.selected];
|
||||
if (bounds === null || bounds === void 0 ? void 0 : bounds.data.name.startsWith("A")) {
|
||||
selected[0] = bounds.data;
|
||||
}
|
||||
else if (bounds === null || bounds === void 0 ? void 0 : bounds.data.name.startsWith("B")) {
|
||||
selected[1] = bounds.data;
|
||||
}
|
||||
this.setSelected(selected);
|
||||
}
|
||||
drawImage(ctx, image, y, cropX) {
|
||||
var _a, _b;
|
||||
if (!((_a = image === null || image === void 0 ? void 0 : image.img) === null || _a === void 0 ? void 0 : _a.naturalWidth) || !((_b = image === null || image === void 0 ? void 0 : image.img) === null || _b === void 0 ? void 0 : _b.naturalHeight)) {
|
||||
return;
|
||||
}
|
||||
let [nodeWidth, nodeHeight] = this.node.size;
|
||||
const imageAspect = (image === null || image === void 0 ? void 0 : image.img.naturalWidth) / (image === null || image === void 0 ? void 0 : image.img.naturalHeight);
|
||||
let height = nodeHeight - y;
|
||||
const widgetAspect = nodeWidth / height;
|
||||
let targetWidth, targetHeight;
|
||||
let offsetX = 0;
|
||||
if (imageAspect > widgetAspect) {
|
||||
targetWidth = nodeWidth;
|
||||
targetHeight = nodeWidth / imageAspect;
|
||||
}
|
||||
else {
|
||||
targetHeight = height;
|
||||
targetWidth = height * imageAspect;
|
||||
offsetX = (nodeWidth - targetWidth) / 2;
|
||||
}
|
||||
const widthMultiplier = (image === null || image === void 0 ? void 0 : image.img.naturalWidth) / targetWidth;
|
||||
const sourceX = 0;
|
||||
const sourceY = 0;
|
||||
const sourceWidth = cropX != null ? (cropX - offsetX) * widthMultiplier : image === null || image === void 0 ? void 0 : image.img.naturalWidth;
|
||||
const sourceHeight = image === null || image === void 0 ? void 0 : image.img.naturalHeight;
|
||||
const destX = (nodeWidth - targetWidth) / 2;
|
||||
const destY = y + (height - targetHeight) / 2;
|
||||
const destWidth = cropX != null ? cropX - offsetX : targetWidth;
|
||||
const destHeight = targetHeight;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
let globalCompositeOperation = ctx.globalCompositeOperation;
|
||||
if (cropX) {
|
||||
ctx.rect(destX, destY, destWidth, destHeight);
|
||||
ctx.clip();
|
||||
}
|
||||
ctx.drawImage(image === null || image === void 0 ? void 0 : image.img, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
|
||||
if (cropX != null && cropX >= (nodeWidth - targetWidth) / 2 && cropX <= targetWidth + offsetX) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cropX, destY);
|
||||
ctx.lineTo(cropX, destY + destHeight);
|
||||
ctx.globalCompositeOperation = "difference";
|
||||
ctx.strokeStyle = "rgba(255,255,255, 1)";
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.globalCompositeOperation = globalCompositeOperation;
|
||||
ctx.restore();
|
||||
}
|
||||
computeSize(width) {
|
||||
return [width, 20];
|
||||
}
|
||||
serializeValue(node, index) {
|
||||
const v = [];
|
||||
for (const data of this._value.images) {
|
||||
const d = { ...data };
|
||||
delete d.img;
|
||||
v.push(d);
|
||||
}
|
||||
return { images: v };
|
||||
}
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "rgthree.ImageComparer",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name === RgthreeImageComparer.type) {
|
||||
RgthreeImageComparer.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
59
custom_nodes/rgthree-comfy/web/comfyui/image_inset_crop.js
Normal file
59
custom_nodes/rgthree-comfy/web/comfyui/image_inset_crop.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
class ImageInsetCrop extends RgthreeBaseServerNode {
|
||||
constructor(title = ImageInsetCrop.title) {
|
||||
super(title);
|
||||
}
|
||||
onAdded(graph) {
|
||||
const measurementWidget = this.widgets[0];
|
||||
let callback = measurementWidget.callback;
|
||||
measurementWidget.callback = (...args) => {
|
||||
this.setWidgetStep();
|
||||
callback && callback.apply(measurementWidget, [...args]);
|
||||
};
|
||||
this.setWidgetStep();
|
||||
}
|
||||
configure(info) {
|
||||
super.configure(info);
|
||||
this.setWidgetStep();
|
||||
}
|
||||
setWidgetStep() {
|
||||
const measurementWidget = this.widgets[0];
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
if (measurementWidget.value === "Pixels") {
|
||||
this.widgets[i].options.step = 80;
|
||||
this.widgets[i].options.max = ImageInsetCrop.maxResolution;
|
||||
}
|
||||
else {
|
||||
this.widgets[i].options.step = 10;
|
||||
this.widgets[i].options.max = 99;
|
||||
}
|
||||
}
|
||||
}
|
||||
async handleAction(action) {
|
||||
if (action === "Reset Crop") {
|
||||
for (const widget of this.widgets) {
|
||||
if (["left", "right", "top", "bottom"].includes(widget.name)) {
|
||||
widget.value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, ImageInsetCrop);
|
||||
}
|
||||
}
|
||||
ImageInsetCrop.title = NodeTypesString.IMAGE_INSET_CROP;
|
||||
ImageInsetCrop.type = NodeTypesString.IMAGE_INSET_CROP;
|
||||
ImageInsetCrop.comfyClass = NodeTypesString.IMAGE_INSET_CROP;
|
||||
ImageInsetCrop.exposedActions = ["Reset Crop"];
|
||||
ImageInsetCrop.maxResolution = 8192;
|
||||
app.registerExtension({
|
||||
name: "rgthree.ImageInsetCrop",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name === NodeTypesString.IMAGE_INSET_CROP) {
|
||||
ImageInsetCrop.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
class RgthreeImageOrLatentSize extends RgthreeBaseServerNode {
|
||||
static setUp(comfyClass, nodeData) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, NODE_CLASS);
|
||||
}
|
||||
constructor(title = NODE_CLASS.title) {
|
||||
super(title);
|
||||
}
|
||||
onNodeCreated() {
|
||||
var _a;
|
||||
(_a = super.onNodeCreated) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
this.addInput("input", ["IMAGE", "LATENT", "MASK"]);
|
||||
}
|
||||
configure(info) {
|
||||
var _a;
|
||||
super.configure(info);
|
||||
if ((_a = this.inputs) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
this.inputs[0].type = ["IMAGE", "LATENT", "MASK"];
|
||||
}
|
||||
}
|
||||
}
|
||||
RgthreeImageOrLatentSize.title = NodeTypesString.IMAGE_OR_LATENT_SIZE;
|
||||
RgthreeImageOrLatentSize.type = NodeTypesString.IMAGE_OR_LATENT_SIZE;
|
||||
RgthreeImageOrLatentSize.comfyClass = NodeTypesString.IMAGE_OR_LATENT_SIZE;
|
||||
const NODE_CLASS = RgthreeImageOrLatentSize;
|
||||
app.registerExtension({
|
||||
name: "rgthree.ImageOrLatentSize",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name === NODE_CLASS.type) {
|
||||
NODE_CLASS.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
163
custom_nodes/rgthree-comfy/web/comfyui/label.js
Normal file
163
custom_nodes/rgthree-comfy/web/comfyui/label.js
Normal file
@@ -0,0 +1,163 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeBaseVirtualNode } from "./base_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
export class Label extends RgthreeBaseVirtualNode {
|
||||
constructor(title = Label.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.LABEL;
|
||||
this.resizable = false;
|
||||
this.properties["fontSize"] = 12;
|
||||
this.properties["fontFamily"] = "Arial";
|
||||
this.properties["fontColor"] = "#ffffff";
|
||||
this.properties["textAlign"] = "left";
|
||||
this.properties["backgroundColor"] = "transparent";
|
||||
this.properties["padding"] = 0;
|
||||
this.properties["borderRadius"] = 0;
|
||||
this.properties["angle"] = 0;
|
||||
this.color = "#fff0";
|
||||
this.bgcolor = "#fff0";
|
||||
this.onConstructed();
|
||||
}
|
||||
draw(ctx) {
|
||||
var _a, _b, _c, _d;
|
||||
this.flags = this.flags || {};
|
||||
this.flags.allow_interaction = !this.flags.pinned;
|
||||
ctx.save();
|
||||
this.color = "#fff0";
|
||||
this.bgcolor = "#fff0";
|
||||
const fontColor = this.properties["fontColor"] || "#ffffff";
|
||||
const backgroundColor = this.properties["backgroundColor"] || "";
|
||||
ctx.font = `${Math.max(this.properties["fontSize"] || 0, 1)}px ${(_a = this.properties["fontFamily"]) !== null && _a !== void 0 ? _a : "Arial"}`;
|
||||
const padding = (_b = Number(this.properties["padding"])) !== null && _b !== void 0 ? _b : 0;
|
||||
const processedTitle = ((_c = this.title) !== null && _c !== void 0 ? _c : "").replace(/\\n/g, "\n").replace(/\n*$/, "");
|
||||
const lines = processedTitle.split("\n");
|
||||
const maxWidth = Math.max(...lines.map((s) => ctx.measureText(s).width));
|
||||
this.size[0] = maxWidth + padding * 2;
|
||||
this.size[1] = this.properties["fontSize"] * lines.length + padding * 2;
|
||||
const angleDeg = parseInt(String((_d = this.properties["angle"]) !== null && _d !== void 0 ? _d : 0)) || 0;
|
||||
if (angleDeg) {
|
||||
const cx = this.size[0] / 2;
|
||||
const cy = this.size[1] / 2;
|
||||
ctx.translate(cx, cy);
|
||||
ctx.rotate((angleDeg * Math.PI) / 180);
|
||||
ctx.translate(-cx, -cy);
|
||||
}
|
||||
if (backgroundColor) {
|
||||
ctx.beginPath();
|
||||
const borderRadius = Number(this.properties["borderRadius"]) || 0;
|
||||
ctx.roundRect(0, 0, this.size[0], this.size[1], [borderRadius]);
|
||||
ctx.fillStyle = backgroundColor;
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.textAlign = "left";
|
||||
let textX = padding;
|
||||
if (this.properties["textAlign"] === "center") {
|
||||
ctx.textAlign = "center";
|
||||
textX = this.size[0] / 2;
|
||||
}
|
||||
else if (this.properties["textAlign"] === "right") {
|
||||
ctx.textAlign = "right";
|
||||
textX = this.size[0] - padding;
|
||||
}
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = fontColor;
|
||||
let currentY = padding;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
ctx.fillText(lines[i] || " ", textX, currentY);
|
||||
currentY += this.properties["fontSize"];
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
onDblClick(event, pos, canvas) {
|
||||
LGraphCanvas.active_canvas.showShowNodePanel(this);
|
||||
}
|
||||
onShowCustomPanelInfo(panel) {
|
||||
var _a, _b;
|
||||
(_a = panel.querySelector('div.property[data-property="Mode"]')) === null || _a === void 0 ? void 0 : _a.remove();
|
||||
(_b = panel.querySelector('div.property[data-property="Color"]')) === null || _b === void 0 ? void 0 : _b.remove();
|
||||
}
|
||||
inResizeCorner(x, y) {
|
||||
return this.resizable;
|
||||
}
|
||||
getHelp() {
|
||||
return `
|
||||
<p>
|
||||
The rgthree-comfy ${this.type.replace("(rgthree)", "")} node allows you to add a floating
|
||||
label to your workflow.
|
||||
</p>
|
||||
<p>
|
||||
The text shown is the "Title" of the node and you can adjust the the font size, font family,
|
||||
font color, text alignment as well as a background color, padding, and background border
|
||||
radius from the node's properties. You can double-click the node to open the properties
|
||||
panel.
|
||||
<p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Pro tip #1:</strong> You can add multiline text from the properties panel
|
||||
<i>(because ComfyUI let's you shift + enter there, only)</i>.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Pro tip #2:</strong> You can use ComfyUI's native "pin" option in the
|
||||
right-click menu to make the label stick to the workflow and clicks to "go through".
|
||||
You can right-click at any time to unpin.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Pro tip #3:</strong> Color values are hexidecimal strings, like "#FFFFFF" for
|
||||
white, or "#660000" for dark red. You can supply a 7th & 8th value (or 5th if using
|
||||
shorthand) to create a transluscent color. For instance, "#FFFFFF88" is semi-transparent
|
||||
white.
|
||||
</p>
|
||||
</li>
|
||||
</ul>`;
|
||||
}
|
||||
}
|
||||
Label.type = NodeTypesString.LABEL;
|
||||
Label.title = NodeTypesString.LABEL;
|
||||
Label.title_mode = LiteGraph.NO_TITLE;
|
||||
Label.collapsable = false;
|
||||
Label["@fontSize"] = { type: "number" };
|
||||
Label["@fontFamily"] = { type: "string" };
|
||||
Label["@fontColor"] = { type: "string" };
|
||||
Label["@textAlign"] = { type: "combo", values: ["left", "center", "right"] };
|
||||
Label["@backgroundColor"] = { type: "string" };
|
||||
Label["@padding"] = { type: "number" };
|
||||
Label["@borderRadius"] = { type: "number" };
|
||||
Label["@angle"] = { type: "number" };
|
||||
const oldDrawNode = LGraphCanvas.prototype.drawNode;
|
||||
LGraphCanvas.prototype.drawNode = function (node, ctx) {
|
||||
if (node.constructor === Label.prototype.constructor) {
|
||||
node.bgcolor = "transparent";
|
||||
node.color = "transparent";
|
||||
const v = oldDrawNode.apply(this, arguments);
|
||||
node.draw(ctx);
|
||||
return v;
|
||||
}
|
||||
const v = oldDrawNode.apply(this, arguments);
|
||||
return v;
|
||||
};
|
||||
const oldGetNodeOnPos = LGraph.prototype.getNodeOnPos;
|
||||
LGraph.prototype.getNodeOnPos = function (x, y, nodes_list) {
|
||||
var _a, _b;
|
||||
if (nodes_list &&
|
||||
rgthree.processingMouseDown &&
|
||||
((_a = rgthree.lastCanvasMouseEvent) === null || _a === void 0 ? void 0 : _a.type.includes("down")) &&
|
||||
((_b = rgthree.lastCanvasMouseEvent) === null || _b === void 0 ? void 0 : _b.which) === 1) {
|
||||
let isDoubleClick = LiteGraph.getTime() - LGraphCanvas.active_canvas.last_mouseclick < 300;
|
||||
if (!isDoubleClick) {
|
||||
nodes_list = [...nodes_list].filter((n) => { var _a; return !(n instanceof Label) || !((_a = n.flags) === null || _a === void 0 ? void 0 : _a.pinned); });
|
||||
}
|
||||
}
|
||||
return oldGetNodeOnPos.apply(this, [x, y, nodes_list]);
|
||||
};
|
||||
app.registerExtension({
|
||||
name: "rgthree.Label",
|
||||
registerCustomNodes() {
|
||||
Label.setUp();
|
||||
},
|
||||
});
|
||||
113
custom_nodes/rgthree-comfy/web/comfyui/menu_auto_nest.js
Normal file
113
custom_nodes/rgthree-comfy/web/comfyui/menu_auto_nest.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
const SPECIAL_ENTRIES = [/^(CHOOSE|NONE|DISABLE|OPEN)(\s|$)/i, /^\p{Extended_Pictographic}/gu];
|
||||
app.registerExtension({
|
||||
name: "rgthree.ContextMenuAutoNest",
|
||||
async setup() {
|
||||
const logger = rgthree.newLogSession("[ContextMenuAutoNest]");
|
||||
const existingContextMenu = LiteGraph.ContextMenu;
|
||||
LiteGraph.ContextMenu = function (values, options) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h;
|
||||
const threshold = CONFIG_SERVICE.getConfigValue("features.menu_auto_nest.threshold", 20);
|
||||
const enabled = CONFIG_SERVICE.getConfigValue("features.menu_auto_nest.subdirs", false);
|
||||
let incompatible = !enabled || !!((_a = options === null || options === void 0 ? void 0 : options.extra) === null || _a === void 0 ? void 0 : _a.rgthree_doNotNest);
|
||||
if (!incompatible) {
|
||||
if (values.length <= threshold) {
|
||||
incompatible = `Skipping context menu auto nesting b/c threshold is not met (${threshold})`;
|
||||
}
|
||||
if (!((_c = (_b = options.parentMenu) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.rgthree_originalCallback)) {
|
||||
if (!(options === null || options === void 0 ? void 0 : options.callback)) {
|
||||
incompatible = `Skipping context menu auto nesting b/c a callback was expected.`;
|
||||
}
|
||||
else if (values.some((i) => typeof i !== "string")) {
|
||||
incompatible = `Skipping context menu auto nesting b/c not all values were strings.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (incompatible) {
|
||||
if (enabled) {
|
||||
const [n, v] = logger.infoParts("Skipping context menu auto nesting for incompatible menu.");
|
||||
(_d = console[n]) === null || _d === void 0 ? void 0 : _d.call(console, ...v);
|
||||
}
|
||||
return existingContextMenu.apply(this, [...arguments]);
|
||||
}
|
||||
const folders = {};
|
||||
const specialOps = [];
|
||||
const folderless = [];
|
||||
for (const value of values) {
|
||||
if (!value) {
|
||||
folderless.push(value);
|
||||
continue;
|
||||
}
|
||||
const newValue = typeof value === "string" ? { content: value } : Object.assign({}, value);
|
||||
newValue.rgthree_originalValue = value.rgthree_originalValue || value;
|
||||
const valueContent = newValue.content || "";
|
||||
const splitBy = valueContent.indexOf("/") > -1 ? "/" : "\\";
|
||||
const valueSplit = valueContent.split(splitBy);
|
||||
if (valueSplit.length > 1) {
|
||||
const key = valueSplit.shift();
|
||||
newValue.content = valueSplit.join(splitBy);
|
||||
folders[key] = folders[key] || [];
|
||||
folders[key].push(newValue);
|
||||
}
|
||||
else if (SPECIAL_ENTRIES.some((r) => r.test(valueContent))) {
|
||||
specialOps.push(newValue);
|
||||
}
|
||||
else {
|
||||
folderless.push(newValue);
|
||||
}
|
||||
}
|
||||
const foldersCount = Object.values(folders).length;
|
||||
if (foldersCount > 0) {
|
||||
options.rgthree_originalCallback =
|
||||
options.rgthree_originalCallback ||
|
||||
((_f = (_e = options.parentMenu) === null || _e === void 0 ? void 0 : _e.options) === null || _f === void 0 ? void 0 : _f.rgthree_originalCallback) ||
|
||||
options.callback;
|
||||
const oldCallback = options === null || options === void 0 ? void 0 : options.rgthree_originalCallback;
|
||||
options.callback = undefined;
|
||||
const newCallback = (item, options, event, parentMenu, node) => {
|
||||
oldCallback === null || oldCallback === void 0 ? void 0 : oldCallback(item === null || item === void 0 ? void 0 : item.rgthree_originalValue, options, event, undefined, node);
|
||||
};
|
||||
const [n, v] = logger.infoParts(`Nested folders found (${foldersCount}).`);
|
||||
(_g = console[n]) === null || _g === void 0 ? void 0 : _g.call(console, ...v);
|
||||
const newValues = [];
|
||||
for (const [folderName, folderValues] of Object.entries(folders)) {
|
||||
newValues.push({
|
||||
content: `📁 ${folderName}`,
|
||||
has_submenu: true,
|
||||
callback: () => {
|
||||
},
|
||||
submenu: {
|
||||
options: folderValues.map((value) => {
|
||||
value.callback = newCallback;
|
||||
return value;
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
values = [].concat(specialOps.map((f) => {
|
||||
if (typeof f === "string") {
|
||||
f = { content: f };
|
||||
}
|
||||
f.callback = newCallback;
|
||||
return f;
|
||||
}), newValues, folderless.map((f) => {
|
||||
if (typeof f === "string") {
|
||||
f = { content: f };
|
||||
}
|
||||
f.callback = newCallback;
|
||||
return f;
|
||||
}));
|
||||
}
|
||||
if (options.scale == null) {
|
||||
options.scale = Math.max(((_h = app.canvas.ds) === null || _h === void 0 ? void 0 : _h.scale) || 1, 1);
|
||||
}
|
||||
const oldCtrResponse = existingContextMenu.call(this, values, options);
|
||||
if (oldCtrResponse === null || oldCtrResponse === void 0 ? void 0 : oldCtrResponse.constructor) {
|
||||
oldCtrResponse.constructor = LiteGraph.ContextMenu;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
},
|
||||
});
|
||||
62
custom_nodes/rgthree-comfy/web/comfyui/menu_copy_image.js
Normal file
62
custom_nodes/rgthree-comfy/web/comfyui/menu_copy_image.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
const clipboardSupportedPromise = new Promise(async (resolve) => {
|
||||
try {
|
||||
const result = await navigator.permissions.query({ name: "clipboard-write" });
|
||||
resolve(result.state === "granted");
|
||||
return;
|
||||
}
|
||||
catch (e) {
|
||||
try {
|
||||
if (!navigator.clipboard.write) {
|
||||
throw new Error();
|
||||
}
|
||||
new ClipboardItem({ "image/png": new Blob([], { type: "image/png" }) });
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
catch (e) {
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
app.registerExtension({
|
||||
name: "rgthree.CopyImageToClipboard",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name.toLowerCase().includes("image")) {
|
||||
if (await clipboardSupportedPromise) {
|
||||
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||
nodeType.prototype.getExtraMenuOptions = function (canvas, options) {
|
||||
var _a, _b;
|
||||
options = (_a = getExtraMenuOptions === null || getExtraMenuOptions === void 0 ? void 0 : getExtraMenuOptions.call(this, canvas, options)) !== null && _a !== void 0 ? _a : options;
|
||||
if ((_b = this.imgs) === null || _b === void 0 ? void 0 : _b.length) {
|
||||
let img = this.imgs[this.imageIndex || 0] || this.imgs[this.overIndex || 0] || this.imgs[0];
|
||||
const foundIdx = options.findIndex((option) => { var _a; return (_a = option === null || option === void 0 ? void 0 : option.content) === null || _a === void 0 ? void 0 : _a.includes("Copy Image"); });
|
||||
if (img && foundIdx === -1) {
|
||||
const menuItem = {
|
||||
content: "Copy Image (rgthree)",
|
||||
callback: () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = img.naturalWidth;
|
||||
canvas.height = img.naturalHeight;
|
||||
ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
|
||||
canvas.toBlob((blob) => {
|
||||
navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
|
||||
});
|
||||
},
|
||||
};
|
||||
let idx = options.findIndex((option) => { var _a; return (_a = option === null || option === void 0 ? void 0 : option.content) === null || _a === void 0 ? void 0 : _a.includes("Open Image"); }) + 1;
|
||||
if (idx != null) {
|
||||
options.splice(idx, 0, menuItem);
|
||||
}
|
||||
else {
|
||||
options.unshift(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
65
custom_nodes/rgthree-comfy/web/comfyui/menu_queue_node.js
Normal file
65
custom_nodes/rgthree-comfy/web/comfyui/menu_queue_node.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { getGroupNodes, getOutputNodes } from "./utils.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
function showQueueNodesMenuIfOutputNodesAreSelected(existingOptions) {
|
||||
if (CONFIG_SERVICE.getConfigValue("features.menu_queue_selected_nodes") === false) {
|
||||
return;
|
||||
}
|
||||
const outputNodes = getOutputNodes(Object.values(app.canvas.selected_nodes));
|
||||
const menuItem = {
|
||||
content: `Queue Selected Output Nodes (rgthree) `,
|
||||
className: "rgthree-contextmenu-item",
|
||||
callback: () => {
|
||||
rgthree.queueOutputNodes(outputNodes);
|
||||
},
|
||||
disabled: !outputNodes.length,
|
||||
};
|
||||
let idx = existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Outputs") + 1;
|
||||
idx = idx || existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Align") + 1;
|
||||
idx = idx || 3;
|
||||
existingOptions.splice(idx, 0, menuItem);
|
||||
}
|
||||
function showQueueGroupNodesMenuIfGroupIsSelected(existingOptions) {
|
||||
if (CONFIG_SERVICE.getConfigValue("features.menu_queue_selected_nodes") === false) {
|
||||
return;
|
||||
}
|
||||
const group = rgthree.lastCanvasMouseEvent &&
|
||||
(app.canvas.getCurrentGraph() || app.graph).getGroupOnPos(rgthree.lastCanvasMouseEvent.canvasX, rgthree.lastCanvasMouseEvent.canvasY);
|
||||
const outputNodes = (group && getOutputNodes(getGroupNodes(group))) || null;
|
||||
const menuItem = {
|
||||
content: `Queue Group Output Nodes (rgthree) `,
|
||||
className: "rgthree-contextmenu-item",
|
||||
callback: () => {
|
||||
outputNodes && rgthree.queueOutputNodes(outputNodes);
|
||||
},
|
||||
disabled: !(outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length),
|
||||
};
|
||||
let idx = existingOptions.findIndex((o) => { var _a; return (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith("Queue Selected "); }) + 1;
|
||||
idx = idx || existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Outputs") + 1;
|
||||
idx = idx || existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Align") + 1;
|
||||
idx = idx || 3;
|
||||
existingOptions.splice(idx, 0, menuItem);
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "rgthree.QueueNode",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||
nodeType.prototype.getExtraMenuOptions = function (canvas, options) {
|
||||
var _a;
|
||||
const extraOptions = (_a = getExtraMenuOptions === null || getExtraMenuOptions === void 0 ? void 0 : getExtraMenuOptions.call(this, canvas, options)) !== null && _a !== void 0 ? _a : [];
|
||||
showQueueNodesMenuIfOutputNodesAreSelected(options);
|
||||
showQueueGroupNodesMenuIfGroupIsSelected(options);
|
||||
return extraOptions;
|
||||
};
|
||||
},
|
||||
async setup() {
|
||||
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
|
||||
const options = getCanvasMenuOptions.apply(this, [...args]);
|
||||
showQueueNodesMenuIfOutputNodesAreSelected(options);
|
||||
showQueueGroupNodesMenuIfGroupIsSelected(options);
|
||||
return options;
|
||||
};
|
||||
},
|
||||
});
|
||||
45
custom_nodes/rgthree-comfy/web/comfyui/muter.js
Normal file
45
custom_nodes/rgthree-comfy/web/comfyui/muter.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { BaseNodeModeChanger } from "./base_node_mode_changer.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
const MODE_MUTE = 2;
|
||||
const MODE_ALWAYS = 0;
|
||||
class MuterNode extends BaseNodeModeChanger {
|
||||
constructor(title = MuterNode.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.FAST_MUTER;
|
||||
this.modeOn = MODE_ALWAYS;
|
||||
this.modeOff = MODE_MUTE;
|
||||
this.onConstructed();
|
||||
}
|
||||
async handleAction(action) {
|
||||
if (action === "Mute all") {
|
||||
for (const widget of this.widgets) {
|
||||
this.forceWidgetOff(widget, true);
|
||||
}
|
||||
}
|
||||
else if (action === "Enable all") {
|
||||
for (const widget of this.widgets) {
|
||||
this.forceWidgetOn(widget, true);
|
||||
}
|
||||
}
|
||||
else if (action === "Toggle all") {
|
||||
for (const widget of this.widgets) {
|
||||
this.forceWidgetToggle(widget, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MuterNode.exposedActions = ["Mute all", "Enable all", "Toggle all"];
|
||||
MuterNode.type = NodeTypesString.FAST_MUTER;
|
||||
MuterNode.title = NodeTypesString.FAST_MUTER;
|
||||
app.registerExtension({
|
||||
name: "rgthree.Muter",
|
||||
registerCustomNodes() {
|
||||
MuterNode.setUp();
|
||||
},
|
||||
loadedGraphNode(node) {
|
||||
if (node.type == MuterNode.title) {
|
||||
node._tempWidth = node.size[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
113
custom_nodes/rgthree-comfy/web/comfyui/node_collector.js
Normal file
113
custom_nodes/rgthree-comfy/web/comfyui/node_collector.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { addConnectionLayoutSupport } from "./utils.js";
|
||||
import { wait } from "../../rgthree/common/shared_utils.js";
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
import { BaseCollectorNode } from "./base_node_collector.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
class CollectorNode extends BaseCollectorNode {
|
||||
constructor(title = CollectorNode.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.NODE_COLLECTOR;
|
||||
this.onConstructed();
|
||||
}
|
||||
onConstructed() {
|
||||
this.addOutput("Output", "*");
|
||||
return super.onConstructed();
|
||||
}
|
||||
}
|
||||
CollectorNode.type = NodeTypesString.NODE_COLLECTOR;
|
||||
CollectorNode.title = NodeTypesString.NODE_COLLECTOR;
|
||||
class CombinerNode extends CollectorNode {
|
||||
constructor(title = CombinerNode.title) {
|
||||
super(title);
|
||||
const note = ComfyWidgets["STRING"](this, "last_seed", ["STRING", { multiline: true }], app).widget;
|
||||
note.inputEl.value =
|
||||
'The Node Combiner has been renamed to Node Collector. You can right-click and select "Update to Node Collector" to attempt to automatically update.';
|
||||
note.inputEl.readOnly = true;
|
||||
note.inputEl.style.backgroundColor = "#332222";
|
||||
note.inputEl.style.fontWeight = "bold";
|
||||
note.inputEl.style.fontStyle = "italic";
|
||||
note.inputEl.style.opacity = "0.8";
|
||||
this.getExtraMenuOptions = (canvas, options) => {
|
||||
options.splice(options.length - 1, 0, {
|
||||
content: "‼️ Update to Node Collector",
|
||||
callback: (_value, _options, _event, _parentMenu, _node) => {
|
||||
updateCombinerToCollector(this);
|
||||
},
|
||||
});
|
||||
return [];
|
||||
};
|
||||
}
|
||||
configure(info) {
|
||||
super.configure(info);
|
||||
if (this.title != CombinerNode.title && !this.title.startsWith("‼️")) {
|
||||
this.title = "‼️ " + this.title;
|
||||
}
|
||||
}
|
||||
}
|
||||
CombinerNode.legacyType = "Node Combiner (rgthree)";
|
||||
CombinerNode.title = "‼️ Node Combiner [DEPRECATED]";
|
||||
async function updateCombinerToCollector(node) {
|
||||
if (node.type === CombinerNode.legacyType) {
|
||||
const newNode = new CollectorNode();
|
||||
if (node.title != CombinerNode.title) {
|
||||
newNode.title = node.title.replace("‼️ ", "");
|
||||
}
|
||||
newNode.pos = [...node.pos];
|
||||
newNode.size = [...node.size];
|
||||
newNode.properties = { ...node.properties };
|
||||
const links = [];
|
||||
const graph = (node.graph || app.graph);
|
||||
for (const [index, output] of node.outputs.entries()) {
|
||||
for (const linkId of output.links || []) {
|
||||
const link = graph.links[linkId];
|
||||
if (!link)
|
||||
continue;
|
||||
const targetNode = graph.getNodeById(link.target_id);
|
||||
links.push({ node: newNode, slot: index, targetNode, targetSlot: link.target_slot });
|
||||
}
|
||||
}
|
||||
for (const [index, input] of node.inputs.entries()) {
|
||||
const linkId = input.link;
|
||||
if (linkId) {
|
||||
const link = graph.links[linkId];
|
||||
const originNode = graph.getNodeById(link.origin_id);
|
||||
links.push({
|
||||
node: originNode,
|
||||
slot: link.origin_slot,
|
||||
targetNode: newNode,
|
||||
targetSlot: index,
|
||||
});
|
||||
}
|
||||
}
|
||||
graph.add(newNode);
|
||||
await wait();
|
||||
for (const link of links) {
|
||||
link.node.connect(link.slot, link.targetNode, link.targetSlot);
|
||||
}
|
||||
await wait();
|
||||
graph.remove(node);
|
||||
}
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "rgthree.NodeCollector",
|
||||
registerCustomNodes() {
|
||||
addConnectionLayoutSupport(CollectorNode, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
LiteGraph.registerNodeType(CollectorNode.title, CollectorNode);
|
||||
CollectorNode.category = CollectorNode._category;
|
||||
},
|
||||
});
|
||||
app.registerExtension({
|
||||
name: "rgthree.NodeCombiner",
|
||||
registerCustomNodes() {
|
||||
addConnectionLayoutSupport(CombinerNode, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
LiteGraph.registerNodeType(CombinerNode.legacyType, CombinerNode);
|
||||
CombinerNode.category = CombinerNode._category;
|
||||
},
|
||||
});
|
||||
205
custom_nodes/rgthree-comfy/web/comfyui/node_mode_relay.js
Normal file
205
custom_nodes/rgthree-comfy/web/comfyui/node_mode_relay.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { PassThroughFollowing, addConnectionLayoutSupport, changeModeOfNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodesAndFilterPassThroughs, } from "./utils.js";
|
||||
import { wait } from "../../rgthree/common/shared_utils.js";
|
||||
import { BaseCollectorNode } from "./base_node_collector.js";
|
||||
import { NodeTypesString, stripRgthree } from "./constants.js";
|
||||
import { fitString } from "./utils_canvas.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
const MODE_ALWAYS = 0;
|
||||
const MODE_MUTE = 2;
|
||||
const MODE_BYPASS = 4;
|
||||
const MODE_REPEATS = [MODE_MUTE, MODE_BYPASS];
|
||||
const MODE_NOTHING = -99;
|
||||
const MODE_TO_OPTION = new Map([
|
||||
[MODE_ALWAYS, "ACTIVE"],
|
||||
[MODE_MUTE, "MUTE"],
|
||||
[MODE_BYPASS, "BYPASS"],
|
||||
[MODE_NOTHING, "NOTHING"],
|
||||
]);
|
||||
const OPTION_TO_MODE = new Map([
|
||||
["ACTIVE", MODE_ALWAYS],
|
||||
["MUTE", MODE_MUTE],
|
||||
["BYPASS", MODE_BYPASS],
|
||||
["NOTHING", MODE_NOTHING],
|
||||
]);
|
||||
const MODE_TO_PROPERTY = new Map([
|
||||
[MODE_MUTE, "on_muted_inputs"],
|
||||
[MODE_BYPASS, "on_bypassed_inputs"],
|
||||
[MODE_ALWAYS, "on_any_active_inputs"],
|
||||
]);
|
||||
const logger = rgthree.newLogSession("[NodeModeRelay]");
|
||||
class NodeModeRelay extends BaseCollectorNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
this.inputsPassThroughFollowing = PassThroughFollowing.ALL;
|
||||
this.comfyClass = NodeTypesString.NODE_MODE_RELAY;
|
||||
this.properties["on_muted_inputs"] = "MUTE";
|
||||
this.properties["on_bypassed_inputs"] = "BYPASS";
|
||||
this.properties["on_any_active_inputs"] = "ACTIVE";
|
||||
this.onConstructed();
|
||||
}
|
||||
onConstructed() {
|
||||
this.addOutput("REPEATER", "_NODE_REPEATER_", {
|
||||
color_on: "#Fc0",
|
||||
color_off: "#a80",
|
||||
shape: LiteGraph.ARROW_SHAPE,
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.stabilize();
|
||||
}, 500);
|
||||
return super.onConstructed();
|
||||
}
|
||||
onModeChange(from, to) {
|
||||
var _a;
|
||||
super.onModeChange(from, to);
|
||||
if (this.inputs.length <= 1 && !this.isInputConnected(0) && this.isAnyOutputConnected()) {
|
||||
const [n, v] = logger.infoParts(`Mode change without any inputs; relaying our mode.`);
|
||||
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
|
||||
this.dispatchModeToRepeater(to);
|
||||
}
|
||||
}
|
||||
onDrawForeground(ctx, canvas) {
|
||||
var _a;
|
||||
if ((_a = this.flags) === null || _a === void 0 ? void 0 : _a.collapsed) {
|
||||
return;
|
||||
}
|
||||
if (this.properties["on_muted_inputs"] !== "MUTE" ||
|
||||
this.properties["on_bypassed_inputs"] !== "BYPASS" ||
|
||||
this.properties["on_any_active_inputs"] != "ACTIVE") {
|
||||
let margin = 15;
|
||||
ctx.textAlign = "left";
|
||||
let label = `*(MUTE > ${this.properties["on_muted_inputs"]}, `;
|
||||
label += `BYPASS > ${this.properties["on_bypassed_inputs"]}, `;
|
||||
label += `ACTIVE > ${this.properties["on_any_active_inputs"]})`;
|
||||
ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
|
||||
const oldFont = ctx.font;
|
||||
ctx.font = "italic " + (LiteGraph.NODE_SUBTEXT_SIZE - 2) + "px Arial";
|
||||
ctx.fillText(fitString(ctx, label, this.size[0] - 20), 15, this.size[1] - 6);
|
||||
ctx.font = oldFont;
|
||||
}
|
||||
}
|
||||
computeSize(out) {
|
||||
let size = super.computeSize(out);
|
||||
if (this.properties["on_muted_inputs"] !== "MUTE" ||
|
||||
this.properties["on_bypassed_inputs"] !== "BYPASS" ||
|
||||
this.properties["on_any_active_inputs"] != "ACTIVE") {
|
||||
size[1] += 17;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
|
||||
var _a, _b;
|
||||
let canConnect = (_a = super.onConnectOutput) === null || _a === void 0 ? void 0 : _a.call(this, outputIndex, inputType, inputSlot, inputNode, inputIndex);
|
||||
let nextNode = (_b = getConnectedOutputNodesAndFilterPassThroughs(this, inputNode)[0]) !== null && _b !== void 0 ? _b : inputNode;
|
||||
return canConnect && nextNode.type === NodeTypesString.NODE_MODE_REPEATER;
|
||||
}
|
||||
onConnectionsChange(type, slotIndex, isConnected, link_info, ioSlot) {
|
||||
super.onConnectionsChange(type, slotIndex, isConnected, link_info, ioSlot);
|
||||
setTimeout(() => {
|
||||
this.stabilize();
|
||||
}, 500);
|
||||
}
|
||||
stabilize() {
|
||||
if (!this.graph || !this.isAnyOutputConnected() || !this.isInputConnected(0)) {
|
||||
return;
|
||||
}
|
||||
const inputNodes = getConnectedInputNodesAndFilterPassThroughs(this, this, -1, this.inputsPassThroughFollowing);
|
||||
let mode = undefined;
|
||||
for (const inputNode of inputNodes) {
|
||||
if (mode === undefined) {
|
||||
mode = inputNode.mode;
|
||||
}
|
||||
else if (mode === inputNode.mode && MODE_REPEATS.includes(mode)) {
|
||||
continue;
|
||||
}
|
||||
else if (inputNode.mode === MODE_ALWAYS || mode === MODE_ALWAYS) {
|
||||
mode = MODE_ALWAYS;
|
||||
}
|
||||
else {
|
||||
mode = undefined;
|
||||
}
|
||||
}
|
||||
this.dispatchModeToRepeater(mode);
|
||||
setTimeout(() => {
|
||||
this.stabilize();
|
||||
}, 500);
|
||||
}
|
||||
dispatchModeToRepeater(mode) {
|
||||
var _a, _b;
|
||||
if (mode != null) {
|
||||
const propertyVal = (_a = this.properties) === null || _a === void 0 ? void 0 : _a[MODE_TO_PROPERTY.get(mode) || ""];
|
||||
const newMode = OPTION_TO_MODE.get(propertyVal);
|
||||
mode = (newMode !== null ? newMode : mode);
|
||||
if (mode !== null && mode !== MODE_NOTHING) {
|
||||
if ((_b = this.outputs) === null || _b === void 0 ? void 0 : _b.length) {
|
||||
const outputNodes = getConnectedOutputNodesAndFilterPassThroughs(this);
|
||||
for (const outputNode of outputNodes) {
|
||||
changeModeOfNodes(outputNode, mode);
|
||||
wait(16).then(() => {
|
||||
outputNode.setDirtyCanvas(true, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getHelp() {
|
||||
return `
|
||||
<p>
|
||||
This node will relay its input nodes' modes (Mute, Bypass, or Active) to a connected
|
||||
${stripRgthree(NodeTypesString.NODE_MODE_REPEATER)} (which would then repeat that mode
|
||||
change to all of its inputs).
|
||||
</p>
|
||||
<ul>
|
||||
<li><p>
|
||||
When all connected input nodes are muted, the relay will set a connected repeater to
|
||||
mute (by default).
|
||||
</p></li>
|
||||
<li><p>
|
||||
When all connected input nodes are bypassed, the relay will set a connected repeater to
|
||||
bypass (by default).
|
||||
</p></li>
|
||||
<li><p>
|
||||
When any connected input nodes are active, the relay will set a connected repeater to
|
||||
active (by default).
|
||||
</p></li>
|
||||
<li><p>
|
||||
If no inputs are connected, the relay will set a connected repeater to its mode <i>when
|
||||
its own mode is changed</i>. <b>Note</b>, if any inputs are connected, then the above
|
||||
will occur and the Relay's mode does not matter.
|
||||
</p></li>
|
||||
</ul>
|
||||
<p>
|
||||
Note, you can change which signals get sent on the above in the <code>Properties</code>.
|
||||
For instance, you could configure an inverse relay which will send a MUTE when any of its
|
||||
inputs are active (instead of sending an ACTIVE signal), and send an ACTIVE signal when all
|
||||
of its inputs are muted (instead of sending a MUTE signal), etc.
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
NodeModeRelay.type = NodeTypesString.NODE_MODE_RELAY;
|
||||
NodeModeRelay.title = NodeTypesString.NODE_MODE_RELAY;
|
||||
NodeModeRelay["@on_muted_inputs"] = {
|
||||
type: "combo",
|
||||
values: ["MUTE", "ACTIVE", "BYPASS", "NOTHING"],
|
||||
};
|
||||
NodeModeRelay["@on_bypassed_inputs"] = {
|
||||
type: "combo",
|
||||
values: ["BYPASS", "ACTIVE", "MUTE", "NOTHING"],
|
||||
};
|
||||
NodeModeRelay["@on_any_active_inputs"] = {
|
||||
type: "combo",
|
||||
values: ["BYPASS", "ACTIVE", "MUTE", "NOTHING"],
|
||||
};
|
||||
app.registerExtension({
|
||||
name: "rgthree.NodeModeRepeaterHelper",
|
||||
registerCustomNodes() {
|
||||
addConnectionLayoutSupport(NodeModeRelay, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
LiteGraph.registerNodeType(NodeModeRelay.type, NodeModeRelay);
|
||||
NodeModeRelay.category = NodeModeRelay._category;
|
||||
},
|
||||
});
|
||||
148
custom_nodes/rgthree-comfy/web/comfyui/node_mode_repeater.js
Normal file
148
custom_nodes/rgthree-comfy/web/comfyui/node_mode_repeater.js
Normal file
@@ -0,0 +1,148 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { BaseCollectorNode } from "./base_node_collector.js";
|
||||
import { NodeTypesString, stripRgthree } from "./constants.js";
|
||||
import { PassThroughFollowing, addConnectionLayoutSupport, changeModeOfNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodesAndFilterPassThroughs, getGroupNodes, } from "./utils.js";
|
||||
class NodeModeRepeater extends BaseCollectorNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
this.inputsPassThroughFollowing = PassThroughFollowing.ALL;
|
||||
this.comfyClass = NodeTypesString.NODE_MODE_REPEATER;
|
||||
this.hasRelayInput = false;
|
||||
this.hasTogglerOutput = false;
|
||||
this.onConstructed();
|
||||
}
|
||||
onConstructed() {
|
||||
this.addOutput("OPT_CONNECTION", "*", {
|
||||
color_on: "#Fc0",
|
||||
color_off: "#a80",
|
||||
});
|
||||
return super.onConstructed();
|
||||
}
|
||||
onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
|
||||
let canConnect = !this.hasRelayInput;
|
||||
canConnect =
|
||||
canConnect && super.onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex);
|
||||
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 || ""));
|
||||
}
|
||||
onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
|
||||
var _a;
|
||||
let canConnect = (_a = super.onConnectInput) === null || _a === void 0 ? void 0 : _a.call(this, inputIndex, outputType, outputSlot, outputNode, outputIndex);
|
||||
let nextNode = getConnectedOutputNodesAndFilterPassThroughs(this, outputNode)[0] || outputNode;
|
||||
const isNextNodeRelay = nextNode.type === NodeTypesString.NODE_MODE_RELAY;
|
||||
return canConnect && (!isNextNodeRelay || !this.hasTogglerOutput);
|
||||
}
|
||||
onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot) {
|
||||
super.onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot);
|
||||
let hasTogglerOutput = false;
|
||||
let hasRelayInput = false;
|
||||
const outputNodes = getConnectedOutputNodesAndFilterPassThroughs(this);
|
||||
for (const outputNode of outputNodes) {
|
||||
if ((outputNode === null || outputNode === void 0 ? void 0 : outputNode.type) === NodeTypesString.FAST_MUTER ||
|
||||
(outputNode === null || outputNode === void 0 ? void 0 : outputNode.type) === NodeTypesString.FAST_BYPASSER) {
|
||||
hasTogglerOutput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const inputNodes = getConnectedInputNodesAndFilterPassThroughs(this);
|
||||
for (const [index, inputNode] of inputNodes.entries()) {
|
||||
if ((inputNode === null || inputNode === void 0 ? void 0 : inputNode.type) === NodeTypesString.NODE_MODE_RELAY) {
|
||||
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 {
|
||||
changeModeOfNodes(inputNode, this.mode);
|
||||
}
|
||||
}
|
||||
this.hasTogglerOutput = hasTogglerOutput;
|
||||
this.hasRelayInput = hasRelayInput;
|
||||
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",
|
||||
});
|
||||
}
|
||||
}
|
||||
onModeChange(from, to) {
|
||||
var _a, _b;
|
||||
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) {
|
||||
changeModeOfNodes(node, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((_b = (_a = this.graph) === null || _a === void 0 ? void 0 : _a._groups) === null || _b === void 0 ? void 0 : _b.length) {
|
||||
for (const group of this.graph._groups) {
|
||||
group.recomputeInsideNodes();
|
||||
const groupNodes = getGroupNodes(group);
|
||||
if (groupNodes === null || groupNodes === void 0 ? void 0 : groupNodes.includes(this)) {
|
||||
for (const node of groupNodes) {
|
||||
if (node !== this) {
|
||||
changeModeOfNodes(node, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getHelp() {
|
||||
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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
NodeModeRepeater.type = NodeTypesString.NODE_MODE_REPEATER;
|
||||
NodeModeRepeater.title = NodeTypesString.NODE_MODE_REPEATER;
|
||||
app.registerExtension({
|
||||
name: "rgthree.NodeModeRepeater",
|
||||
registerCustomNodes() {
|
||||
addConnectionLayoutSupport(NodeModeRepeater, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
LiteGraph.registerNodeType(NodeModeRepeater.type, NodeModeRepeater);
|
||||
NodeModeRepeater.category = NodeModeRepeater._category;
|
||||
},
|
||||
});
|
||||
149
custom_nodes/rgthree-comfy/web/comfyui/power_conductor.js
Normal file
149
custom_nodes/rgthree-comfy/web/comfyui/power_conductor.js
Normal file
@@ -0,0 +1,149 @@
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
||||
if (kind === "m") throw new TypeError("Private method is not writable");
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
||||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
||||
};
|
||||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
||||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
||||
};
|
||||
var _ComfyNodeWrapper_id, _ComfyWidgetWrapper_widget;
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { Exposed, execute, PyTuple } from "../../rgthree/common/py_parser.js";
|
||||
import { RgthreeBaseVirtualNode } from "./base_node.js";
|
||||
import { RgthreeBetterButtonWidget } from "./utils_widgets.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
import { changeModeOfNodes, getNodeById } from "./utils.js";
|
||||
const BUILT_INS = {
|
||||
node: {
|
||||
fn: (query) => {
|
||||
if (typeof query === "number" || /^\d+(\.\d+)?/.exec(query)) {
|
||||
return new ComfyNodeWrapper(Number(query));
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
};
|
||||
class RgthreePowerConductor extends RgthreeBaseVirtualNode {
|
||||
constructor(title = RgthreePowerConductor.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.POWER_CONDUCTOR;
|
||||
this.serialize_widgets = true;
|
||||
this.codeWidget = ComfyWidgets.STRING(this, "", ["STRING", { multiline: true }], app).widget;
|
||||
this.addCustomWidget(this.codeWidget);
|
||||
(this.buttonWidget = new RgthreeBetterButtonWidget("Run", (...args) => {
|
||||
this.execute();
|
||||
})),
|
||||
this.addCustomWidget(this.buttonWidget);
|
||||
this.onConstructed();
|
||||
}
|
||||
execute() {
|
||||
execute(this.codeWidget.value, {}, BUILT_INS);
|
||||
}
|
||||
}
|
||||
RgthreePowerConductor.title = NodeTypesString.POWER_CONDUCTOR;
|
||||
RgthreePowerConductor.type = NodeTypesString.POWER_CONDUCTOR;
|
||||
const NODE_CLASS = RgthreePowerConductor;
|
||||
class ComfyNodeWrapper {
|
||||
constructor(id) {
|
||||
_ComfyNodeWrapper_id.set(this, void 0);
|
||||
__classPrivateFieldSet(this, _ComfyNodeWrapper_id, id, "f");
|
||||
}
|
||||
getNode() {
|
||||
return getNodeById(__classPrivateFieldGet(this, _ComfyNodeWrapper_id, "f"));
|
||||
}
|
||||
get id() {
|
||||
return this.getNode().id;
|
||||
}
|
||||
get title() {
|
||||
return this.getNode().title;
|
||||
}
|
||||
set title(value) {
|
||||
this.getNode().title = value;
|
||||
}
|
||||
get widgets() {
|
||||
var _a;
|
||||
return new PyTuple((_a = this.getNode().widgets) === null || _a === void 0 ? void 0 : _a.map((w) => new ComfyWidgetWrapper(w)));
|
||||
}
|
||||
get mode() {
|
||||
return this.getNode().mode;
|
||||
}
|
||||
mute() {
|
||||
changeModeOfNodes(this.getNode(), 2);
|
||||
}
|
||||
bypass() {
|
||||
changeModeOfNodes(this.getNode(), 4);
|
||||
}
|
||||
enable() {
|
||||
changeModeOfNodes(this.getNode(), 0);
|
||||
}
|
||||
}
|
||||
_ComfyNodeWrapper_id = new WeakMap();
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyNodeWrapper.prototype, "id", null);
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyNodeWrapper.prototype, "title", null);
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyNodeWrapper.prototype, "widgets", null);
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyNodeWrapper.prototype, "mode", null);
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyNodeWrapper.prototype, "mute", null);
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyNodeWrapper.prototype, "bypass", null);
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyNodeWrapper.prototype, "enable", null);
|
||||
class ComfyWidgetWrapper {
|
||||
constructor(widget) {
|
||||
_ComfyWidgetWrapper_widget.set(this, void 0);
|
||||
__classPrivateFieldSet(this, _ComfyWidgetWrapper_widget, widget, "f");
|
||||
}
|
||||
get value() {
|
||||
return __classPrivateFieldGet(this, _ComfyWidgetWrapper_widget, "f").value;
|
||||
}
|
||||
get label() {
|
||||
return __classPrivateFieldGet(this, _ComfyWidgetWrapper_widget, "f").label;
|
||||
}
|
||||
toggle(value) {
|
||||
if (typeof __classPrivateFieldGet(this, _ComfyWidgetWrapper_widget, "f")["toggle"] === "function") {
|
||||
__classPrivateFieldGet(this, _ComfyWidgetWrapper_widget, "f")["toggle"](value);
|
||||
}
|
||||
else {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ComfyWidgetWrapper_widget = new WeakMap();
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyWidgetWrapper.prototype, "value", null);
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyWidgetWrapper.prototype, "label", null);
|
||||
__decorate([
|
||||
Exposed
|
||||
], ComfyWidgetWrapper.prototype, "toggle", null);
|
||||
app.registerExtension({
|
||||
name: "rgthree.PowerConductor",
|
||||
registerCustomNodes() {
|
||||
if (CONFIG_SERVICE.getConfigValue("unreleased.power_conductor.enabled")) {
|
||||
NODE_CLASS.setUp();
|
||||
}
|
||||
},
|
||||
});
|
||||
592
custom_nodes/rgthree-comfy/web/comfyui/power_lora_loader.js
Normal file
592
custom_nodes/rgthree-comfy/web/comfyui/power_lora_loader.js
Normal file
@@ -0,0 +1,592 @@
|
||||
var _a;
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { addConnectionLayoutSupport } from "./utils.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { drawInfoIcon, drawNumberWidgetPart, drawRoundedRectangle, drawTogglePart, fitString, isLowQuality, } from "./utils_canvas.js";
|
||||
import { RgthreeBaseWidget, RgthreeBetterButtonWidget, RgthreeDividerWidget, } from "./utils_widgets.js";
|
||||
import { rgthreeApi } from "../../rgthree/common/rgthree_api.js";
|
||||
import { showLoraChooser } from "./utils_menu.js";
|
||||
import { moveArrayItem, removeArrayItem } from "../../rgthree/common/shared_utils.js";
|
||||
import { RgthreeLoraInfoDialog } from "./dialog_info.js";
|
||||
import { LORA_INFO_SERVICE } from "../../rgthree/common/model_info_service.js";
|
||||
const PROP_LABEL_SHOW_STRENGTHS = "Show Strengths";
|
||||
const PROP_LABEL_SHOW_STRENGTHS_STATIC = `@${PROP_LABEL_SHOW_STRENGTHS}`;
|
||||
const PROP_VALUE_SHOW_STRENGTHS_SINGLE = "Single Strength";
|
||||
const PROP_VALUE_SHOW_STRENGTHS_SEPARATE = "Separate Model & Clip";
|
||||
class RgthreePowerLoraLoader extends RgthreeBaseServerNode {
|
||||
constructor(title = NODE_CLASS.title) {
|
||||
super(title);
|
||||
this.serialize_widgets = true;
|
||||
this.logger = rgthree.newLogSession(`[Power Lora Stack]`);
|
||||
this.loraWidgetsCounter = 0;
|
||||
this.widgetButtonSpacer = null;
|
||||
this.properties[PROP_LABEL_SHOW_STRENGTHS] = PROP_VALUE_SHOW_STRENGTHS_SINGLE;
|
||||
rgthreeApi.getLoras();
|
||||
if (rgthree.loadingApiJson) {
|
||||
const fullApiJson = rgthree.loadingApiJson;
|
||||
setTimeout(() => {
|
||||
this.configureFromApiJson(fullApiJson);
|
||||
}, 16);
|
||||
}
|
||||
}
|
||||
configureFromApiJson(fullApiJson) {
|
||||
var _b, _c;
|
||||
if (this.id == null) {
|
||||
const [n, v] = this.logger.errorParts("Cannot load from API JSON without node id.");
|
||||
(_b = console[n]) === null || _b === void 0 ? void 0 : _b.call(console, ...v);
|
||||
return;
|
||||
}
|
||||
const nodeData = fullApiJson[this.id] || fullApiJson[String(this.id)] || fullApiJson[Number(this.id)];
|
||||
if (nodeData == null) {
|
||||
const [n, v] = this.logger.errorParts(`No node found in API JSON for node id ${this.id}.`);
|
||||
(_c = console[n]) === null || _c === void 0 ? void 0 : _c.call(console, ...v);
|
||||
return;
|
||||
}
|
||||
this.configure({
|
||||
widgets_values: Object.values(nodeData.inputs).filter((input) => typeof (input === null || input === void 0 ? void 0 : input["lora"]) === "string"),
|
||||
});
|
||||
}
|
||||
configure(info) {
|
||||
var _b;
|
||||
while ((_b = this.widgets) === null || _b === void 0 ? void 0 : _b.length)
|
||||
this.removeWidget(0);
|
||||
this.widgetButtonSpacer = null;
|
||||
if (info.id != null) {
|
||||
super.configure(info);
|
||||
}
|
||||
this._tempWidth = this.size[0];
|
||||
this._tempHeight = this.size[1];
|
||||
for (const widgetValue of info.widgets_values || []) {
|
||||
if ((widgetValue === null || widgetValue === void 0 ? void 0 : widgetValue.lora) !== undefined) {
|
||||
const widget = this.addNewLoraWidget();
|
||||
widget.value = { ...widgetValue };
|
||||
}
|
||||
}
|
||||
this.addNonLoraWidgets();
|
||||
this.size[0] = this._tempWidth;
|
||||
this.size[1] = Math.max(this._tempHeight, this.computeSize()[1]);
|
||||
}
|
||||
onNodeCreated() {
|
||||
var _b;
|
||||
(_b = super.onNodeCreated) === null || _b === void 0 ? void 0 : _b.call(this);
|
||||
this.addNonLoraWidgets();
|
||||
const computed = this.computeSize();
|
||||
this.size = this.size || [0, 0];
|
||||
this.size[0] = Math.max(this.size[0], computed[0]);
|
||||
this.size[1] = Math.max(this.size[1], computed[1]);
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
addNewLoraWidget(lora) {
|
||||
this.loraWidgetsCounter++;
|
||||
const widget = this.addCustomWidget(new PowerLoraLoaderWidget("lora_" + this.loraWidgetsCounter));
|
||||
if (lora)
|
||||
widget.setLora(lora);
|
||||
if (this.widgetButtonSpacer) {
|
||||
moveArrayItem(this.widgets, widget, this.widgets.indexOf(this.widgetButtonSpacer));
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
addNonLoraWidgets() {
|
||||
moveArrayItem(this.widgets, this.addCustomWidget(new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 })), 0);
|
||||
moveArrayItem(this.widgets, this.addCustomWidget(new PowerLoraLoaderHeaderWidget()), 1);
|
||||
this.widgetButtonSpacer = this.addCustomWidget(new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new RgthreeBetterButtonWidget("➕ Add Lora", (event, pos, node) => {
|
||||
rgthreeApi.getLoras().then((lorasDetails) => {
|
||||
const loras = lorasDetails.map((l) => l.file);
|
||||
showLoraChooser(event, (value) => {
|
||||
var _b;
|
||||
if (typeof value === "string") {
|
||||
if (value.includes("Power Lora Chooser")) {
|
||||
}
|
||||
else if (value !== "NONE") {
|
||||
this.addNewLoraWidget(value);
|
||||
const computed = this.computeSize();
|
||||
const tempHeight = (_b = this._tempHeight) !== null && _b !== void 0 ? _b : 15;
|
||||
this.size[1] = Math.max(tempHeight, computed[1]);
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
}, null, [...loras]);
|
||||
});
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
getSlotInPosition(canvasX, canvasY) {
|
||||
var _b;
|
||||
const slot = super.getSlotInPosition(canvasX, canvasY);
|
||||
if (!slot) {
|
||||
let lastWidget = null;
|
||||
for (const widget of this.widgets) {
|
||||
if (!widget.last_y)
|
||||
return;
|
||||
if (canvasY > this.pos[1] + widget.last_y) {
|
||||
lastWidget = widget;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ((_b = lastWidget === null || lastWidget === void 0 ? void 0 : lastWidget.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_")) {
|
||||
return { widget: lastWidget, output: { type: "LORA WIDGET" } };
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
getSlotMenuOptions(slot) {
|
||||
var _b, _c, _d, _e, _f, _g;
|
||||
if ((_c = (_b = slot === null || slot === void 0 ? void 0 : slot.widget) === null || _b === void 0 ? void 0 : _b.name) === null || _c === void 0 ? void 0 : _c.startsWith("lora_")) {
|
||||
const widget = slot.widget;
|
||||
const index = this.widgets.indexOf(widget);
|
||||
const canMoveUp = !!((_e = (_d = this.widgets[index - 1]) === null || _d === void 0 ? void 0 : _d.name) === null || _e === void 0 ? void 0 : _e.startsWith("lora_"));
|
||||
const canMoveDown = !!((_g = (_f = this.widgets[index + 1]) === null || _f === void 0 ? void 0 : _f.name) === null || _g === void 0 ? void 0 : _g.startsWith("lora_"));
|
||||
const menuItems = [
|
||||
{
|
||||
content: `ℹ️ Show Info`,
|
||||
callback: () => {
|
||||
widget.showLoraInfoDialog();
|
||||
},
|
||||
},
|
||||
null,
|
||||
{
|
||||
content: `${widget.value.on ? "⚫" : "🟢"} Toggle ${widget.value.on ? "Off" : "On"}`,
|
||||
callback: () => {
|
||||
widget.value.on = !widget.value.on;
|
||||
},
|
||||
},
|
||||
{
|
||||
content: `⬆️ Move Up`,
|
||||
disabled: !canMoveUp,
|
||||
callback: () => {
|
||||
moveArrayItem(this.widgets, widget, index - 1);
|
||||
},
|
||||
},
|
||||
{
|
||||
content: `⬇️ Move Down`,
|
||||
disabled: !canMoveDown,
|
||||
callback: () => {
|
||||
moveArrayItem(this.widgets, widget, index + 1);
|
||||
},
|
||||
},
|
||||
{
|
||||
content: `🗑️ Remove`,
|
||||
callback: () => {
|
||||
removeArrayItem(this.widgets, widget);
|
||||
},
|
||||
},
|
||||
];
|
||||
new LiteGraph.ContextMenu(menuItems, {
|
||||
title: "LORA WIDGET",
|
||||
event: rgthree.lastCanvasMouseEvent,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
return this.defaultGetSlotMenuOptions(slot);
|
||||
}
|
||||
refreshComboInNode(defs) {
|
||||
rgthreeApi.getLoras(true);
|
||||
}
|
||||
hasLoraWidgets() {
|
||||
var _b;
|
||||
return !!((_b = this.widgets) === null || _b === void 0 ? void 0 : _b.find((w) => { var _b; return (_b = w.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_"); }));
|
||||
}
|
||||
allLorasState() {
|
||||
var _b, _c, _d;
|
||||
let allOn = true;
|
||||
let allOff = true;
|
||||
for (const widget of this.widgets) {
|
||||
if ((_b = widget.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_")) {
|
||||
const on = (_c = widget.value) === null || _c === void 0 ? void 0 : _c.on;
|
||||
allOn = allOn && on === true;
|
||||
allOff = allOff && on === false;
|
||||
if (!allOn && !allOff) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return allOn && ((_d = this.widgets) === null || _d === void 0 ? void 0 : _d.length) ? true : false;
|
||||
}
|
||||
toggleAllLoras() {
|
||||
var _b, _c;
|
||||
const allOn = this.allLorasState();
|
||||
const toggledTo = !allOn ? true : false;
|
||||
for (const widget of this.widgets) {
|
||||
if (((_b = widget.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_")) && ((_c = widget.value) === null || _c === void 0 ? void 0 : _c.on) != null) {
|
||||
widget.value.on = toggledTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, NODE_CLASS);
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, ctxClass) {
|
||||
addConnectionLayoutSupport(NODE_CLASS, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
setTimeout(() => {
|
||||
NODE_CLASS.category = comfyClass.category;
|
||||
});
|
||||
}
|
||||
getHelp() {
|
||||
return `
|
||||
<p>
|
||||
The ${this.type.replace("(rgthree)", "")} is a powerful node that condenses 100s of pixels
|
||||
of functionality in a single, dynamic node that allows you to add loras, change strengths,
|
||||
and quickly toggle on/off all without taking up half your screen.
|
||||
</p>
|
||||
<ul>
|
||||
<li><p>
|
||||
Add as many Lora's as you would like by clicking the "+ Add Lora" button.
|
||||
There's no real limit!
|
||||
</p></li>
|
||||
<li><p>
|
||||
Right-click on a Lora widget for special options to move the lora up or down
|
||||
(no image affect, only presentational), toggle it on/off, or delete the row all together.
|
||||
</p></li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Properties.</strong> You can change the following properties (by right-clicking
|
||||
on the node, and select "Properties" or "Properties Panel" from the menu):
|
||||
</p>
|
||||
<ul>
|
||||
<li><p>
|
||||
<code>${PROP_LABEL_SHOW_STRENGTHS}</code> - Change between showing a single, simple
|
||||
strength (which will be used for both model and clip), or a more advanced view with
|
||||
both model and clip strengths being modifiable.
|
||||
</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>`;
|
||||
}
|
||||
}
|
||||
_a = PROP_LABEL_SHOW_STRENGTHS_STATIC;
|
||||
RgthreePowerLoraLoader.title = NodeTypesString.POWER_LORA_LOADER;
|
||||
RgthreePowerLoraLoader.type = NodeTypesString.POWER_LORA_LOADER;
|
||||
RgthreePowerLoraLoader.comfyClass = NodeTypesString.POWER_LORA_LOADER;
|
||||
RgthreePowerLoraLoader[_a] = {
|
||||
type: "combo",
|
||||
values: [PROP_VALUE_SHOW_STRENGTHS_SINGLE, PROP_VALUE_SHOW_STRENGTHS_SEPARATE],
|
||||
};
|
||||
class PowerLoraLoaderHeaderWidget extends RgthreeBaseWidget {
|
||||
constructor(name = "PowerLoraLoaderHeaderWidget") {
|
||||
super(name);
|
||||
this.value = { type: "PowerLoraLoaderHeaderWidget" };
|
||||
this.type = "custom";
|
||||
this.hitAreas = {
|
||||
toggle: { bounds: [0, 0], onDown: this.onToggleDown },
|
||||
};
|
||||
this.showModelAndClip = null;
|
||||
}
|
||||
draw(ctx, node, w, posY, height) {
|
||||
if (!node.hasLoraWidgets()) {
|
||||
return;
|
||||
}
|
||||
this.showModelAndClip =
|
||||
node.properties[PROP_LABEL_SHOW_STRENGTHS] === PROP_VALUE_SHOW_STRENGTHS_SEPARATE;
|
||||
const margin = 10;
|
||||
const innerMargin = margin * 0.33;
|
||||
const lowQuality = isLowQuality();
|
||||
const allLoraState = node.allLorasState();
|
||||
posY += 2;
|
||||
const midY = posY + height * 0.5;
|
||||
let posX = 10;
|
||||
ctx.save();
|
||||
this.hitAreas.toggle.bounds = drawTogglePart(ctx, { posX, posY, height, value: allLoraState });
|
||||
if (!lowQuality) {
|
||||
posX += this.hitAreas.toggle.bounds[1] + innerMargin;
|
||||
ctx.globalAlpha = app.canvas.editor_alpha * 0.55;
|
||||
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText("Toggle All", posX, midY);
|
||||
let rposX = node.size[0] - margin - innerMargin - innerMargin;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(this.showModelAndClip ? "Clip" : "Strength", rposX - drawNumberWidgetPart.WIDTH_TOTAL / 2, midY);
|
||||
if (this.showModelAndClip) {
|
||||
rposX = rposX - drawNumberWidgetPart.WIDTH_TOTAL - innerMargin * 2;
|
||||
ctx.fillText("Model", rposX - drawNumberWidgetPart.WIDTH_TOTAL / 2, midY);
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
onToggleDown(event, pos, node) {
|
||||
node.toggleAllLoras();
|
||||
this.cancelMouseDown();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const DEFAULT_LORA_WIDGET_DATA = {
|
||||
on: true,
|
||||
lora: null,
|
||||
strength: 1,
|
||||
strengthTwo: null,
|
||||
};
|
||||
class PowerLoraLoaderWidget extends RgthreeBaseWidget {
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
this.haveMouseMovedStrength = false;
|
||||
this.loraInfoPromise = null;
|
||||
this.loraInfo = null;
|
||||
this.showModelAndClip = null;
|
||||
this.hitAreas = {
|
||||
toggle: { bounds: [0, 0], onDown: this.onToggleDown },
|
||||
lora: { bounds: [0, 0], onClick: this.onLoraClick },
|
||||
strengthDec: { bounds: [0, 0], onClick: this.onStrengthDecDown },
|
||||
strengthVal: { bounds: [0, 0], onClick: this.onStrengthValUp },
|
||||
strengthInc: { bounds: [0, 0], onClick: this.onStrengthIncDown },
|
||||
strengthAny: { bounds: [0, 0], onMove: this.onStrengthAnyMove },
|
||||
strengthTwoDec: { bounds: [0, 0], onClick: this.onStrengthTwoDecDown },
|
||||
strengthTwoVal: { bounds: [0, 0], onClick: this.onStrengthTwoValUp },
|
||||
strengthTwoInc: { bounds: [0, 0], onClick: this.onStrengthTwoIncDown },
|
||||
strengthTwoAny: { bounds: [0, 0], onMove: this.onStrengthTwoAnyMove },
|
||||
};
|
||||
this._value = {
|
||||
on: true,
|
||||
lora: null,
|
||||
strength: 1,
|
||||
strengthTwo: null,
|
||||
};
|
||||
}
|
||||
set value(v) {
|
||||
this._value = v;
|
||||
if (typeof this._value !== "object") {
|
||||
this._value = { ...DEFAULT_LORA_WIDGET_DATA };
|
||||
if (this.showModelAndClip) {
|
||||
this._value.strengthTwo = this._value.strength;
|
||||
}
|
||||
}
|
||||
this.getLoraInfo();
|
||||
}
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
setLora(lora) {
|
||||
this._value.lora = lora;
|
||||
this.getLoraInfo();
|
||||
}
|
||||
draw(ctx, node, w, posY, height) {
|
||||
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
||||
let currentShowModelAndClip = node.properties[PROP_LABEL_SHOW_STRENGTHS] === PROP_VALUE_SHOW_STRENGTHS_SEPARATE;
|
||||
if (this.showModelAndClip !== currentShowModelAndClip) {
|
||||
let oldShowModelAndClip = this.showModelAndClip;
|
||||
this.showModelAndClip = currentShowModelAndClip;
|
||||
if (this.showModelAndClip) {
|
||||
if (oldShowModelAndClip != null) {
|
||||
this.value.strengthTwo = (_b = this.value.strength) !== null && _b !== void 0 ? _b : 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.value.strengthTwo = null;
|
||||
this.hitAreas.strengthTwoDec.bounds = [0, -1];
|
||||
this.hitAreas.strengthTwoVal.bounds = [0, -1];
|
||||
this.hitAreas.strengthTwoInc.bounds = [0, -1];
|
||||
this.hitAreas.strengthTwoAny.bounds = [0, -1];
|
||||
}
|
||||
}
|
||||
ctx.save();
|
||||
const margin = 10;
|
||||
const innerMargin = margin * 0.33;
|
||||
const lowQuality = isLowQuality();
|
||||
const midY = posY + height * 0.5;
|
||||
let posX = margin;
|
||||
drawRoundedRectangle(ctx, { pos: [posX, posY], size: [node.size[0] - margin * 2, height] });
|
||||
this.hitAreas.toggle.bounds = drawTogglePart(ctx, { posX, posY, height, value: this.value.on });
|
||||
posX += this.hitAreas.toggle.bounds[1] + innerMargin;
|
||||
if (lowQuality) {
|
||||
ctx.restore();
|
||||
return;
|
||||
}
|
||||
if (!this.value.on) {
|
||||
ctx.globalAlpha = app.canvas.editor_alpha * 0.4;
|
||||
}
|
||||
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
|
||||
let rposX = node.size[0] - margin - innerMargin - innerMargin;
|
||||
const strengthValue = this.showModelAndClip
|
||||
? ((_c = this.value.strengthTwo) !== null && _c !== void 0 ? _c : 1)
|
||||
: ((_d = this.value.strength) !== null && _d !== void 0 ? _d : 1);
|
||||
let textColor = undefined;
|
||||
if (((_e = this.loraInfo) === null || _e === void 0 ? void 0 : _e.strengthMax) != null && strengthValue > ((_f = this.loraInfo) === null || _f === void 0 ? void 0 : _f.strengthMax)) {
|
||||
textColor = "#c66";
|
||||
}
|
||||
else if (((_g = this.loraInfo) === null || _g === void 0 ? void 0 : _g.strengthMin) != null && strengthValue < ((_h = this.loraInfo) === null || _h === void 0 ? void 0 : _h.strengthMin)) {
|
||||
textColor = "#c66";
|
||||
}
|
||||
const [leftArrow, text, rightArrow] = drawNumberWidgetPart(ctx, {
|
||||
posX: node.size[0] - margin - innerMargin - innerMargin,
|
||||
posY,
|
||||
height,
|
||||
value: strengthValue,
|
||||
direction: -1,
|
||||
textColor,
|
||||
});
|
||||
this.hitAreas.strengthDec.bounds = leftArrow;
|
||||
this.hitAreas.strengthVal.bounds = text;
|
||||
this.hitAreas.strengthInc.bounds = rightArrow;
|
||||
this.hitAreas.strengthAny.bounds = [leftArrow[0], rightArrow[0] + rightArrow[1] - leftArrow[0]];
|
||||
rposX = leftArrow[0] - innerMargin;
|
||||
if (this.showModelAndClip) {
|
||||
rposX -= innerMargin;
|
||||
this.hitAreas.strengthTwoDec.bounds = this.hitAreas.strengthDec.bounds;
|
||||
this.hitAreas.strengthTwoVal.bounds = this.hitAreas.strengthVal.bounds;
|
||||
this.hitAreas.strengthTwoInc.bounds = this.hitAreas.strengthInc.bounds;
|
||||
this.hitAreas.strengthTwoAny.bounds = this.hitAreas.strengthAny.bounds;
|
||||
let textColor = undefined;
|
||||
if (((_j = this.loraInfo) === null || _j === void 0 ? void 0 : _j.strengthMax) != null && this.value.strength > ((_k = this.loraInfo) === null || _k === void 0 ? void 0 : _k.strengthMax)) {
|
||||
textColor = "#c66";
|
||||
}
|
||||
else if (((_l = this.loraInfo) === null || _l === void 0 ? void 0 : _l.strengthMin) != null &&
|
||||
this.value.strength < ((_m = this.loraInfo) === null || _m === void 0 ? void 0 : _m.strengthMin)) {
|
||||
textColor = "#c66";
|
||||
}
|
||||
const [leftArrow, text, rightArrow] = drawNumberWidgetPart(ctx, {
|
||||
posX: rposX,
|
||||
posY,
|
||||
height,
|
||||
value: (_o = this.value.strength) !== null && _o !== void 0 ? _o : 1,
|
||||
direction: -1,
|
||||
textColor,
|
||||
});
|
||||
this.hitAreas.strengthDec.bounds = leftArrow;
|
||||
this.hitAreas.strengthVal.bounds = text;
|
||||
this.hitAreas.strengthInc.bounds = rightArrow;
|
||||
this.hitAreas.strengthAny.bounds = [
|
||||
leftArrow[0],
|
||||
rightArrow[0] + rightArrow[1] - leftArrow[0],
|
||||
];
|
||||
rposX = leftArrow[0] - innerMargin;
|
||||
}
|
||||
const infoIconSize = height * 0.66;
|
||||
const infoWidth = infoIconSize + innerMargin + innerMargin;
|
||||
if (this.hitAreas["info"]) {
|
||||
rposX -= innerMargin;
|
||||
drawInfoIcon(ctx, rposX - infoIconSize, posY + (height - infoIconSize) / 2, infoIconSize);
|
||||
this.hitAreas.info.bounds = [rposX - infoIconSize, infoWidth];
|
||||
rposX = rposX - infoIconSize - innerMargin;
|
||||
}
|
||||
const loraWidth = rposX - posX;
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
const loraLabel = String(((_p = this.value) === null || _p === void 0 ? void 0 : _p.lora) || "None");
|
||||
ctx.fillText(fitString(ctx, loraLabel, loraWidth), posX, midY);
|
||||
this.hitAreas.lora.bounds = [posX, loraWidth];
|
||||
posX += loraWidth + innerMargin;
|
||||
ctx.globalAlpha = app.canvas.editor_alpha;
|
||||
ctx.restore();
|
||||
}
|
||||
serializeValue(node, index) {
|
||||
var _b;
|
||||
const v = { ...this.value };
|
||||
if (!this.showModelAndClip) {
|
||||
delete v.strengthTwo;
|
||||
}
|
||||
else {
|
||||
this.value.strengthTwo = (_b = this.value.strengthTwo) !== null && _b !== void 0 ? _b : 1;
|
||||
v.strengthTwo = this.value.strengthTwo;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
onToggleDown(event, pos, node) {
|
||||
this.value.on = !this.value.on;
|
||||
this.cancelMouseDown();
|
||||
return true;
|
||||
}
|
||||
onInfoDown(event, pos, node) {
|
||||
this.showLoraInfoDialog();
|
||||
}
|
||||
onLoraClick(event, pos, node) {
|
||||
showLoraChooser(event, (value) => {
|
||||
if (typeof value === "string") {
|
||||
this.value.lora = value;
|
||||
this.loraInfo = null;
|
||||
this.getLoraInfo();
|
||||
}
|
||||
node.setDirtyCanvas(true, true);
|
||||
});
|
||||
this.cancelMouseDown();
|
||||
}
|
||||
onStrengthDecDown(event, pos, node) {
|
||||
this.stepStrength(-1, false);
|
||||
}
|
||||
onStrengthIncDown(event, pos, node) {
|
||||
this.stepStrength(1, false);
|
||||
}
|
||||
onStrengthTwoDecDown(event, pos, node) {
|
||||
this.stepStrength(-1, true);
|
||||
}
|
||||
onStrengthTwoIncDown(event, pos, node) {
|
||||
this.stepStrength(1, true);
|
||||
}
|
||||
onStrengthAnyMove(event, pos, node) {
|
||||
this.doOnStrengthAnyMove(event, false);
|
||||
}
|
||||
onStrengthTwoAnyMove(event, pos, node) {
|
||||
this.doOnStrengthAnyMove(event, true);
|
||||
}
|
||||
doOnStrengthAnyMove(event, isTwo = false) {
|
||||
var _b;
|
||||
if (event.deltaX) {
|
||||
let prop = isTwo ? "strengthTwo" : "strength";
|
||||
this.haveMouseMovedStrength = true;
|
||||
this.value[prop] = ((_b = this.value[prop]) !== null && _b !== void 0 ? _b : 1) + event.deltaX * 0.05;
|
||||
}
|
||||
}
|
||||
onStrengthValUp(event, pos, node) {
|
||||
this.doOnStrengthValUp(event, false);
|
||||
}
|
||||
onStrengthTwoValUp(event, pos, node) {
|
||||
this.doOnStrengthValUp(event, true);
|
||||
}
|
||||
doOnStrengthValUp(event, isTwo = false) {
|
||||
if (this.haveMouseMovedStrength)
|
||||
return;
|
||||
let prop = isTwo ? "strengthTwo" : "strength";
|
||||
const canvas = app.canvas;
|
||||
canvas.prompt("Value", this.value[prop], (v) => (this.value[prop] = Number(v)), event);
|
||||
}
|
||||
onMouseUp(event, pos, node) {
|
||||
super.onMouseUp(event, pos, node);
|
||||
this.haveMouseMovedStrength = false;
|
||||
}
|
||||
showLoraInfoDialog() {
|
||||
if (!this.value.lora || this.value.lora === "None") {
|
||||
return;
|
||||
}
|
||||
const infoDialog = new RgthreeLoraInfoDialog(this.value.lora).show();
|
||||
infoDialog.addEventListener("close", ((e) => {
|
||||
if (e.detail.dirty) {
|
||||
this.getLoraInfo(true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
stepStrength(direction, isTwo = false) {
|
||||
var _b;
|
||||
let step = 0.05;
|
||||
let prop = isTwo ? "strengthTwo" : "strength";
|
||||
let strength = ((_b = this.value[prop]) !== null && _b !== void 0 ? _b : 1) + step * direction;
|
||||
this.value[prop] = Math.round(strength * 100) / 100;
|
||||
}
|
||||
getLoraInfo(force = false) {
|
||||
if (!this.loraInfoPromise || force == true) {
|
||||
let promise;
|
||||
if (this.value.lora && this.value.lora != "None") {
|
||||
promise = LORA_INFO_SERVICE.getInfo(this.value.lora, force, true);
|
||||
}
|
||||
else {
|
||||
promise = Promise.resolve(null);
|
||||
}
|
||||
this.loraInfoPromise = promise.then((v) => (this.loraInfo = v));
|
||||
}
|
||||
return this.loraInfoPromise;
|
||||
}
|
||||
}
|
||||
const NODE_CLASS = RgthreePowerLoraLoader;
|
||||
app.registerExtension({
|
||||
name: "rgthree.PowerLoraLoader",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name === NODE_CLASS.type) {
|
||||
NODE_CLASS.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
175
custom_nodes/rgthree-comfy/web/comfyui/power_primitive.js
Normal file
175
custom_nodes/rgthree-comfy/web/comfyui/power_primitive.js
Normal file
@@ -0,0 +1,175 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
import { moveArrayItem } from "../../rgthree/common/shared_utils.js";
|
||||
const PROPERTY_HIDE_TYPE_SELECTOR = "hideTypeSelector";
|
||||
const PRIMITIVES = {
|
||||
STRING: "STRING",
|
||||
INT: "INT",
|
||||
FLOAT: "FLOAT",
|
||||
BOOLEAN: "BOOLEAN",
|
||||
};
|
||||
class RgthreePowerPrimitive extends RgthreeBaseServerNode {
|
||||
constructor(title = NODE_CLASS.title) {
|
||||
super(title);
|
||||
this.typeState = '';
|
||||
this.properties[PROPERTY_HIDE_TYPE_SELECTOR] = false;
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, NODE_CLASS);
|
||||
}
|
||||
onNodeCreated() {
|
||||
var _a;
|
||||
(_a = super.onNodeCreated) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
this.addInitialWidgets();
|
||||
}
|
||||
configure(info) {
|
||||
super.configure(info);
|
||||
if (this.outputTypeWidget.value === 'BOOL') {
|
||||
this.outputTypeWidget.value = 'BOOLEAN';
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.setTypedData();
|
||||
});
|
||||
}
|
||||
getExtraMenuOptions(canvas, options) {
|
||||
const that = this;
|
||||
super.getExtraMenuOptions(canvas, options);
|
||||
const isHidden = !!this.properties[PROPERTY_HIDE_TYPE_SELECTOR];
|
||||
const menuItems = [
|
||||
{
|
||||
content: `${isHidden ? "Show" : "Hide"} Type Selector Widget`,
|
||||
callback: (...args) => {
|
||||
this.setProperty(PROPERTY_HIDE_TYPE_SELECTOR, !this.properties[PROPERTY_HIDE_TYPE_SELECTOR]);
|
||||
},
|
||||
},
|
||||
{
|
||||
content: `Set type`,
|
||||
submenu: {
|
||||
options: Object.keys(PRIMITIVES),
|
||||
callback(value, ...args) {
|
||||
that.outputTypeWidget.value = value;
|
||||
that.setTypedData();
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
options.splice(0, 0, ...menuItems, null);
|
||||
return [];
|
||||
}
|
||||
addInitialWidgets() {
|
||||
if (!this.outputTypeWidget) {
|
||||
this.outputTypeWidget = this.addWidget("combo", "type", "STRING", (...args) => {
|
||||
this.setTypedData();
|
||||
}, {
|
||||
values: Object.keys(PRIMITIVES),
|
||||
});
|
||||
this.outputTypeWidget.hidden = this.properties[PROPERTY_HIDE_TYPE_SELECTOR];
|
||||
}
|
||||
this.setTypedData();
|
||||
}
|
||||
setTypedData() {
|
||||
var _a, _b, _c, _d, _e;
|
||||
const name = "value";
|
||||
const type = this.outputTypeWidget.value;
|
||||
const linked = !!((_b = (_a = this.inputs) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.link);
|
||||
const newTypeState = `${type}|${linked}`;
|
||||
if (this.typeState == newTypeState)
|
||||
return;
|
||||
this.typeState = newTypeState;
|
||||
let value = (_d = (_c = this.valueWidget) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : null;
|
||||
let newWidget = null;
|
||||
if (linked) {
|
||||
newWidget = ComfyWidgets["STRING"](this, name, ["STRING"], app).widget;
|
||||
newWidget.value = "";
|
||||
}
|
||||
else if (type == "STRING") {
|
||||
newWidget = ComfyWidgets["STRING"](this, name, ["STRING", { multiline: true }], app).widget;
|
||||
newWidget.value = value ? "" : String(value);
|
||||
}
|
||||
else if (type === "INT" || type === "FLOAT") {
|
||||
const isFloat = type === "FLOAT";
|
||||
newWidget = this.addWidget("number", name, value !== null && value !== void 0 ? value : 1, undefined, {
|
||||
precision: isFloat ? 1 : 0,
|
||||
step2: isFloat ? 0.1 : 0,
|
||||
});
|
||||
value = Number(value);
|
||||
value = value == null || isNaN(value) ? 0 : value;
|
||||
newWidget.value = value;
|
||||
}
|
||||
else if (type === "BOOLEAN") {
|
||||
newWidget = this.addWidget("toggle", name, !!(value !== null && value !== void 0 ? value : true), undefined, {
|
||||
on: "true",
|
||||
off: "false",
|
||||
});
|
||||
if (typeof value === "string") {
|
||||
value = !["false", "null", "None", "", "0"].includes(value.toLowerCase());
|
||||
}
|
||||
newWidget.value = !!value;
|
||||
}
|
||||
if (newWidget == null) {
|
||||
throw new Error(`Unsupported type "${type}".`);
|
||||
}
|
||||
if (this.valueWidget) {
|
||||
this.replaceWidget(this.valueWidget, newWidget);
|
||||
}
|
||||
else {
|
||||
if (!this.widgets.includes(newWidget)) {
|
||||
this.addCustomWidget(newWidget);
|
||||
}
|
||||
moveArrayItem(this.widgets, newWidget, 1);
|
||||
}
|
||||
this.valueWidget = newWidget;
|
||||
if (!((_e = this.inputs) === null || _e === void 0 ? void 0 : _e.length)) {
|
||||
this.addInput("value", "*", { widget: this.valueWidget });
|
||||
}
|
||||
else {
|
||||
this.inputs[0].widget = this.valueWidget;
|
||||
}
|
||||
const output = this.outputs[0];
|
||||
const outputLabel = output.label === "*" || output.label === output.type ? null : output.label;
|
||||
output.type = type;
|
||||
output.label = outputLabel || output.type;
|
||||
}
|
||||
onConnectionsChange(type, index, isConnected, link_info, inputOrOutput) {
|
||||
var _a;
|
||||
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.apply(this, [...arguments]);
|
||||
if (this.inputs.includes(inputOrOutput)) {
|
||||
this.setTypedData();
|
||||
}
|
||||
}
|
||||
onPropertyChanged(name, value, prev_value) {
|
||||
if (name === PROPERTY_HIDE_TYPE_SELECTOR) {
|
||||
if (!this.outputTypeWidget) {
|
||||
return true;
|
||||
}
|
||||
this.outputTypeWidget.hidden = this.properties[PROPERTY_HIDE_TYPE_SELECTOR];
|
||||
if (this.outputTypeWidget.hidden) {
|
||||
this.outputTypeWidget.computeLayoutSize = () => ({
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
maxHeight: 0,
|
||||
maxWidth: 0,
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.outputTypeWidget.computeLayoutSize = undefined;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
RgthreePowerPrimitive.title = NodeTypesString.POWER_PRIMITIVE;
|
||||
RgthreePowerPrimitive.type = NodeTypesString.POWER_PRIMITIVE;
|
||||
RgthreePowerPrimitive.comfyClass = NodeTypesString.POWER_PRIMITIVE;
|
||||
RgthreePowerPrimitive["@hideTypeSelector"] = { type: "boolean" };
|
||||
const NODE_CLASS = RgthreePowerPrimitive;
|
||||
app.registerExtension({
|
||||
name: "rgthree.PowerPrimitive",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name === NODE_CLASS.type) {
|
||||
NODE_CLASS.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
42
custom_nodes/rgthree-comfy/web/comfyui/power_prompt.js
Normal file
42
custom_nodes/rgthree-comfy/web/comfyui/power_prompt.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { addConnectionLayoutSupport } from "./utils.js";
|
||||
import { PowerPrompt } from "./base_power_prompt.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
let nodeData = null;
|
||||
app.registerExtension({
|
||||
name: "rgthree.PowerPrompt",
|
||||
async beforeRegisterNodeDef(nodeType, passedNodeData) {
|
||||
if (passedNodeData.name.includes("Power Prompt") && passedNodeData.name.includes("rgthree")) {
|
||||
nodeData = passedNodeData;
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
|
||||
this.powerPrompt = new PowerPrompt(this, passedNodeData);
|
||||
};
|
||||
addConnectionLayoutSupport(nodeType, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
}
|
||||
},
|
||||
async loadedGraphNode(node) {
|
||||
if (node.type === NodeTypesString.POWER_PROMPT) {
|
||||
setTimeout(() => {
|
||||
if (node.outputs[0].type === "STRING") {
|
||||
if (node.outputs[0].links) {
|
||||
node.outputs[3].links = node.outputs[3].links || [];
|
||||
for (const link of node.outputs[0].links) {
|
||||
node.outputs[3].links.push(link);
|
||||
(node.graph || app.graph).links[link].origin_slot = 3;
|
||||
}
|
||||
node.outputs[0].links = null;
|
||||
}
|
||||
node.outputs[0].type = nodeData.output[0];
|
||||
node.outputs[0].name = nodeData.output_name[0] || node.outputs[0].type;
|
||||
node.outputs[0].color_on = undefined;
|
||||
node.outputs[0].color_off = undefined;
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
},
|
||||
});
|
||||
314
custom_nodes/rgthree-comfy/web/comfyui/power_puter.js
Normal file
314
custom_nodes/rgthree-comfy/web/comfyui/power_puter.js
Normal file
@@ -0,0 +1,314 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
|
||||
import { debounce } from "../../rgthree/common/shared_utils.js";
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
import { RgthreeBaseWidget } from "./utils_widgets.js";
|
||||
import { drawPlusIcon, drawRoundedRectangle, drawWidgetButton, isLowQuality, measureText, } from "./utils_canvas.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
const ALPHABET = "abcdefghijklmnopqrstuv".split("");
|
||||
const OUTPUT_TYPES = ["STRING", "INT", "FLOAT", "BOOLEAN", "*"];
|
||||
class RgthreePowerPuter extends RgthreeBaseServerNode {
|
||||
constructor(title = NODE_CLASS.title) {
|
||||
super(title);
|
||||
this.stabilizeBound = this.stabilize.bind(this);
|
||||
this.addAnyInput(2);
|
||||
this.addInitialWidgets();
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, NODE_CLASS);
|
||||
}
|
||||
onConnectionsChange(...args) {
|
||||
var _a;
|
||||
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.apply(this, [...arguments]);
|
||||
this.scheduleStabilize();
|
||||
}
|
||||
scheduleStabilize(ms = 64) {
|
||||
return debounce(this.stabilizeBound, ms);
|
||||
}
|
||||
stabilize() {
|
||||
removeUnusedInputsFromEnd(this, 1);
|
||||
this.addAnyInput();
|
||||
this.setOutputs();
|
||||
}
|
||||
addInitialWidgets() {
|
||||
if (!this.outputTypeWidget) {
|
||||
this.outputTypeWidget = this.addCustomWidget(new OutputsWidget("outputs", this));
|
||||
this.expressionWidget = ComfyWidgets["STRING"](this, "code", ["STRING", { multiline: true }], app).widget;
|
||||
}
|
||||
}
|
||||
addAnyInput(num = 1) {
|
||||
for (let i = 0; i < num; i++) {
|
||||
this.addInput(ALPHABET[this.inputs.length], "*");
|
||||
}
|
||||
}
|
||||
setOutputs() {
|
||||
const desiredOutputs = this.outputTypeWidget.value.outputs;
|
||||
for (let i = 0; i < Math.max(this.outputs.length, desiredOutputs.length); i++) {
|
||||
const desired = desiredOutputs[i];
|
||||
let output = this.outputs[i];
|
||||
if (!desired && output) {
|
||||
this.disconnectOutput(i);
|
||||
this.removeOutput(i);
|
||||
continue;
|
||||
}
|
||||
output = output || this.addOutput("", "");
|
||||
const outputLabel = output.label === "*" || output.label === output.type ? null : output.label;
|
||||
output.type = String(desired);
|
||||
output.label = outputLabel || output.type;
|
||||
}
|
||||
}
|
||||
getHelp() {
|
||||
return `
|
||||
<p>
|
||||
The ${this.type.replace("(rgthree)", "")} is a powerful and versatile node that opens the
|
||||
door for a wide range of utility by offering mult-line code parsing for output. This node
|
||||
can be used for simple string concatenation, or math operations; to an image dimension or a
|
||||
node's widgets with advanced list comprehension.
|
||||
If you want to output something in your workflow, this is the node to do it.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><p>
|
||||
Evaluate almost any kind of input and more, and choose your output from INT, FLOAT,
|
||||
STRING, or BOOLEAN.
|
||||
</p></li>
|
||||
<li><p>
|
||||
Connect some nodes and do simply math operations like <code>a + b</code> or
|
||||
<code>ceil(1 / 2)</code>.
|
||||
</p></li>
|
||||
<li><p>
|
||||
Or do more advanced things, like input an image, and get the width like
|
||||
<code>a.shape[2]</code>.
|
||||
</p></li>
|
||||
<li><p>
|
||||
Even more powerful, you can target nodes in the prompt that's sent to the backend. For
|
||||
instance; if you have a Power Lora Loader node at id #5, and want to get a comma-delimited
|
||||
list of the enabled loras, you could enter
|
||||
<code>', '.join([v.lora for v in node(5).inputs.values() if 'lora' in v and v.on])</code>.
|
||||
</p></li>
|
||||
<li><p>
|
||||
See more at the <a target="_blank"
|
||||
href="https://github.com/rgthree/rgthree-comfy/wiki/Node:-Power-Puter">rgthree-comfy
|
||||
wiki</a>.
|
||||
</p></li>
|
||||
</ul>`;
|
||||
}
|
||||
}
|
||||
RgthreePowerPuter.title = NodeTypesString.POWER_PUTER;
|
||||
RgthreePowerPuter.type = NodeTypesString.POWER_PUTER;
|
||||
RgthreePowerPuter.comfyClass = NodeTypesString.POWER_PUTER;
|
||||
const NODE_CLASS = RgthreePowerPuter;
|
||||
const OUTPUTS_WIDGET_CHIP_HEIGHT = LiteGraph.NODE_WIDGET_HEIGHT - 4;
|
||||
const OUTPUTS_WIDGET_CHIP_SPACE = 4;
|
||||
const OUTPUTS_WIDGET_CHIP_ARROW_WIDTH = 5.5;
|
||||
const OUTPUTS_WIDGET_CHIP_ARROW_HEIGHT = 4;
|
||||
class OutputsWidget extends RgthreeBaseWidget {
|
||||
constructor(name, node) {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
this._value = { outputs: ["STRING"] };
|
||||
this.rows = 1;
|
||||
this.neededHeight = LiteGraph.NODE_WIDGET_HEIGHT + 8;
|
||||
this.hitAreas = {
|
||||
add: { bounds: [0, 0], onClick: this.onAddChipDown },
|
||||
output0: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 0 } },
|
||||
output1: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 1 } },
|
||||
output2: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 2 } },
|
||||
output3: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 3 } },
|
||||
output4: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 4 } },
|
||||
output5: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 5 } },
|
||||
output6: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 6 } },
|
||||
output7: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 7 } },
|
||||
output8: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 8 } },
|
||||
output9: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 9 } },
|
||||
};
|
||||
this.node = node;
|
||||
}
|
||||
set value(v) {
|
||||
let outputs = typeof v === "string" ? [v] : [...v.outputs];
|
||||
outputs = outputs.map((o) => (o === "BOOL" ? "BOOLEAN" : o));
|
||||
this._value.outputs = outputs;
|
||||
}
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
onAddChipDown(event, pos, node, bounds) {
|
||||
new LiteGraph.ContextMenu(OUTPUT_TYPES, {
|
||||
event: event,
|
||||
title: "Add an output",
|
||||
className: "rgthree-dark",
|
||||
callback: (value) => {
|
||||
if (isLowQuality())
|
||||
return;
|
||||
if (typeof value === "string" && OUTPUT_TYPES.includes(value)) {
|
||||
this._value.outputs.push(value);
|
||||
this.node.scheduleStabilize();
|
||||
}
|
||||
},
|
||||
});
|
||||
this.cancelMouseDown();
|
||||
return true;
|
||||
}
|
||||
onOutputChipDown(event, pos, node, bounds) {
|
||||
const options = [...OUTPUT_TYPES];
|
||||
if (this.value.outputs.length > 1) {
|
||||
options.push(null, "🗑️ Delete");
|
||||
}
|
||||
new LiteGraph.ContextMenu(options, {
|
||||
event: event,
|
||||
title: `Edit output #${bounds.data.index + 1}`,
|
||||
className: "rgthree-dark",
|
||||
callback: (value) => {
|
||||
var _a, _b;
|
||||
const index = bounds.data.index;
|
||||
if (typeof value !== "string" || value === this._value.outputs[index] || isLowQuality()) {
|
||||
return;
|
||||
}
|
||||
const output = this.node.outputs[index];
|
||||
if (value.toLocaleLowerCase().includes("delete")) {
|
||||
if ((_a = output.links) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
rgthree.showMessage({
|
||||
id: "puter-remove-linked-output",
|
||||
type: "warn",
|
||||
message: "[Power Puter] Removed and disconnected output from that was connected!",
|
||||
timeout: 3000,
|
||||
});
|
||||
this.node.disconnectOutput(index);
|
||||
}
|
||||
this.node.removeOutput(index);
|
||||
this._value.outputs.splice(index, 1);
|
||||
this.node.scheduleStabilize();
|
||||
return;
|
||||
}
|
||||
if (((_b = output.links) === null || _b === void 0 ? void 0 : _b.length) && value !== "*") {
|
||||
rgthree.showMessage({
|
||||
id: "puter-remove-linked-output",
|
||||
type: "warn",
|
||||
message: "[Power Puter] Changing output type of linked output! You should check for" +
|
||||
" compatibility.",
|
||||
timeout: 3000,
|
||||
});
|
||||
}
|
||||
this._value.outputs[index] = value;
|
||||
this.node.scheduleStabilize();
|
||||
},
|
||||
});
|
||||
this.cancelMouseDown();
|
||||
return true;
|
||||
}
|
||||
computeLayoutSize(node) {
|
||||
this.neededHeight =
|
||||
OUTPUTS_WIDGET_CHIP_SPACE +
|
||||
(OUTPUTS_WIDGET_CHIP_HEIGHT + OUTPUTS_WIDGET_CHIP_SPACE) * this.rows;
|
||||
return {
|
||||
minHeight: this.neededHeight,
|
||||
maxHeight: this.neededHeight,
|
||||
minWidth: 0,
|
||||
};
|
||||
}
|
||||
draw(ctx, node, w, posY, height) {
|
||||
var _a, _b;
|
||||
ctx.save();
|
||||
height = this.neededHeight;
|
||||
const margin = 10;
|
||||
const innerMargin = margin * 0.33;
|
||||
const width = node.size[0] - margin * 2;
|
||||
let borderRadius = LiteGraph.NODE_WIDGET_HEIGHT * 0.5;
|
||||
let midY = posY + height * 0.5;
|
||||
let posX = margin;
|
||||
let rposX = node.size[0] - margin;
|
||||
drawRoundedRectangle(ctx, { pos: [posX, posY], size: [width, height], borderRadius });
|
||||
posX += innerMargin * 2;
|
||||
rposX -= innerMargin * 2;
|
||||
if (isLowQuality()) {
|
||||
ctx.restore();
|
||||
return;
|
||||
}
|
||||
ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText("outputs", posX, midY);
|
||||
posX += measureText(ctx, "outputs") + innerMargin * 2;
|
||||
ctx.stroke(new Path2D(`M ${posX} ${posY} v ${height}`));
|
||||
posX += 1 + innerMargin * 2;
|
||||
const inititalPosX = posX;
|
||||
posY += OUTPUTS_WIDGET_CHIP_SPACE;
|
||||
height = OUTPUTS_WIDGET_CHIP_HEIGHT;
|
||||
borderRadius = height * 0.5;
|
||||
midY = posY + height / 2;
|
||||
ctx.textAlign = "center";
|
||||
ctx.lineJoin = ctx.lineCap = "round";
|
||||
ctx.fillStyle = ctx.strokeStyle = LiteGraph.WIDGET_TEXT_COLOR;
|
||||
let rows = 1;
|
||||
const values = (_b = (_a = this.value) === null || _a === void 0 ? void 0 : _a.outputs) !== null && _b !== void 0 ? _b : [];
|
||||
const fontSize = ctx.font.match(/(\d+)px/);
|
||||
if (fontSize === null || fontSize === void 0 ? void 0 : fontSize[1]) {
|
||||
ctx.font = ctx.font.replace(fontSize[1], `${Number(fontSize[1]) - 2}`);
|
||||
}
|
||||
let i = 0;
|
||||
for (i; i < values.length; i++) {
|
||||
const hitArea = this.hitAreas[`output${i}`];
|
||||
const isClicking = !!hitArea.wasMouseClickedAndIsOver;
|
||||
hitArea.data.index = i;
|
||||
const text = values[i];
|
||||
const textWidth = measureText(ctx, text) + innerMargin * 2;
|
||||
const width = textWidth + OUTPUTS_WIDGET_CHIP_ARROW_WIDTH + innerMargin * 5;
|
||||
if (posX + width >= rposX) {
|
||||
posX = inititalPosX;
|
||||
posY = posY + height + 4;
|
||||
midY = posY + height / 2;
|
||||
rows++;
|
||||
}
|
||||
drawWidgetButton(ctx, { pos: [posX, posY], size: [width, height], borderRadius }, null, isClicking);
|
||||
const startX = posX;
|
||||
posX += innerMargin * 2;
|
||||
const newMidY = midY + (isClicking ? 1 : 0);
|
||||
ctx.fillText(text, posX + textWidth / 2, newMidY);
|
||||
posX += textWidth + innerMargin;
|
||||
const arrow = new Path2D(`M${posX} ${newMidY - OUTPUTS_WIDGET_CHIP_ARROW_HEIGHT / 2}
|
||||
h${OUTPUTS_WIDGET_CHIP_ARROW_WIDTH}
|
||||
l-${OUTPUTS_WIDGET_CHIP_ARROW_WIDTH / 2} ${OUTPUTS_WIDGET_CHIP_ARROW_HEIGHT} z`);
|
||||
ctx.fill(arrow);
|
||||
ctx.stroke(arrow);
|
||||
posX += OUTPUTS_WIDGET_CHIP_ARROW_WIDTH + innerMargin * 2;
|
||||
hitArea.bounds = [startX, posY, width, height];
|
||||
posX += OUTPUTS_WIDGET_CHIP_SPACE;
|
||||
}
|
||||
for (i; i < 9; i++) {
|
||||
const hitArea = this.hitAreas[`output${i}`];
|
||||
if (hitArea.bounds[0] > 0) {
|
||||
hitArea.bounds = [0, 0, 0, 0];
|
||||
}
|
||||
}
|
||||
const addHitArea = this.hitAreas["add"];
|
||||
if (this.value.outputs.length < 10) {
|
||||
const isClicking = !!addHitArea.wasMouseClickedAndIsOver;
|
||||
const plusSize = 10;
|
||||
let plusWidth = innerMargin * 2 + plusSize + innerMargin * 2;
|
||||
if (posX + plusWidth >= rposX) {
|
||||
posX = inititalPosX;
|
||||
posY = posY + height + 4;
|
||||
midY = posY + height / 2;
|
||||
rows++;
|
||||
}
|
||||
drawWidgetButton(ctx, { size: [plusWidth, height], pos: [posX, posY], borderRadius }, null, isClicking);
|
||||
drawPlusIcon(ctx, posX + innerMargin * 2, midY + (isClicking ? 1 : 0), plusSize);
|
||||
addHitArea.bounds = [posX, posY, plusWidth, height];
|
||||
}
|
||||
else {
|
||||
addHitArea.bounds = [0, 0, 0, 0];
|
||||
}
|
||||
this.rows = rows;
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "rgthree.PowerPuter",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name === NODE_CLASS.type) {
|
||||
NODE_CLASS.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
102
custom_nodes/rgthree-comfy/web/comfyui/random_unmuter.js
Normal file
102
custom_nodes/rgthree-comfy/web/comfyui/random_unmuter.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { changeModeOfNodes, getConnectedInputNodesAndFilterPassThroughs } from "./utils.js";
|
||||
const MODE_MUTE = 2;
|
||||
const MODE_ALWAYS = 0;
|
||||
class RandomUnmuterNode extends BaseAnyInputConnectedNode {
|
||||
constructor(title = RandomUnmuterNode.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.RANDOM_UNMUTER;
|
||||
this.modeOn = MODE_ALWAYS;
|
||||
this.modeOff = MODE_MUTE;
|
||||
this.tempEnabledNode = null;
|
||||
this.processingQueue = false;
|
||||
this.onQueueBound = this.onQueue.bind(this);
|
||||
this.onQueueEndBound = this.onQueueEnd.bind(this);
|
||||
this.onGraphtoPromptBound = this.onGraphtoPrompt.bind(this);
|
||||
this.onGraphtoPromptEndBound = this.onGraphtoPromptEnd.bind(this);
|
||||
rgthree.addEventListener("queue", this.onQueueBound);
|
||||
rgthree.addEventListener("queue-end", this.onQueueEndBound);
|
||||
rgthree.addEventListener("graph-to-prompt", this.onGraphtoPromptBound);
|
||||
rgthree.addEventListener("graph-to-prompt-end", this.onGraphtoPromptEndBound);
|
||||
this.onConstructed();
|
||||
}
|
||||
onRemoved() {
|
||||
rgthree.removeEventListener("queue", this.onQueueBound);
|
||||
rgthree.removeEventListener("queue-end", this.onQueueEndBound);
|
||||
rgthree.removeEventListener("graph-to-prompt", this.onGraphtoPromptBound);
|
||||
rgthree.removeEventListener("graph-to-prompt-end", this.onGraphtoPromptEndBound);
|
||||
}
|
||||
onQueue(event) {
|
||||
this.processingQueue = true;
|
||||
}
|
||||
onQueueEnd(event) {
|
||||
this.processingQueue = false;
|
||||
}
|
||||
onGraphtoPrompt(event) {
|
||||
if (!this.processingQueue) {
|
||||
return;
|
||||
}
|
||||
this.tempEnabledNode = null;
|
||||
const linkedNodes = getConnectedInputNodesAndFilterPassThroughs(this);
|
||||
let allMuted = true;
|
||||
if (linkedNodes.length) {
|
||||
for (const node of linkedNodes) {
|
||||
if (node.mode !== this.modeOff) {
|
||||
allMuted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allMuted) {
|
||||
this.tempEnabledNode = linkedNodes[Math.floor(Math.random() * linkedNodes.length)] || null;
|
||||
if (this.tempEnabledNode) {
|
||||
changeModeOfNodes(this.tempEnabledNode, this.modeOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onGraphtoPromptEnd(event) {
|
||||
if (this.tempEnabledNode) {
|
||||
changeModeOfNodes(this.tempEnabledNode, this.modeOff);
|
||||
this.tempEnabledNode = null;
|
||||
}
|
||||
}
|
||||
handleLinkedNodesStabilization(linkedNodes) {
|
||||
return false;
|
||||
}
|
||||
getHelp() {
|
||||
return `
|
||||
<p>
|
||||
Use this node to unmute on of its inputs randomly when the graph is queued (and, immediately
|
||||
mute it back).
|
||||
</p>
|
||||
<ul>
|
||||
<li><p>
|
||||
NOTE: All input nodes MUST be muted to start; if not this node will not randomly unmute
|
||||
another. (This is powerful, as the generated image can be dragged in and the chosen input
|
||||
will already by unmuted and work w/o any further action.)
|
||||
</p></li>
|
||||
<li><p>
|
||||
TIP: Connect a Repeater's output to this nodes input and place that Repeater on a group
|
||||
without any other inputs, and it will mute/unmute the entire group.
|
||||
</p></li>
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
}
|
||||
RandomUnmuterNode.exposedActions = ["Mute all", "Enable all"];
|
||||
RandomUnmuterNode.type = NodeTypesString.RANDOM_UNMUTER;
|
||||
RandomUnmuterNode.title = RandomUnmuterNode.type;
|
||||
app.registerExtension({
|
||||
name: "rgthree.RandomUnmuter",
|
||||
registerCustomNodes() {
|
||||
RandomUnmuterNode.setUp();
|
||||
},
|
||||
loadedGraphNode(node) {
|
||||
if (node.type == RandomUnmuterNode.title) {
|
||||
node._tempWidth = node.size[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
981
custom_nodes/rgthree-comfy/web/comfyui/reroute.js
Normal file
981
custom_nodes/rgthree-comfy/web/comfyui/reroute.js
Normal file
@@ -0,0 +1,981 @@
|
||||
var _a, _b;
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { rgthreeConfig } from "../../rgthree/config.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { IoDirection, LAYOUT_CLOCKWISE, LAYOUT_LABEL_OPPOSITES, LAYOUT_LABEL_TO_DATA, addConnectionLayoutSupport, addMenuItem, getSlotLinks, isValidConnection, setConnectionsLayout, waitForCanvas, } from "./utils.js";
|
||||
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
|
||||
import { wait } from "../../rgthree/common/shared_utils.js";
|
||||
import { RgthreeBaseVirtualNode } from "./base_node.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
import { rgthreeApi } from "../../rgthree/common/rgthree_api.js";
|
||||
import { getWidgetConfig, mergeIfValid, setWidgetConfig } from "./utils_deprecated_comfyui.js";
|
||||
const CONFIG_REROUTE = ((_a = rgthreeConfig === null || rgthreeConfig === void 0 ? void 0 : rgthreeConfig["nodes"]) === null || _a === void 0 ? void 0 : _a["reroute"]) || {};
|
||||
const CONFIG_FAST_REROUTE = CONFIG_REROUTE["fast_reroute"];
|
||||
const CONFIG_FAST_REROUTE_ENABLED = (_b = CONFIG_FAST_REROUTE["enabled"]) !== null && _b !== void 0 ? _b : false;
|
||||
const CONFIG_KEY_CREATE_WHILE_LINKING = CONFIG_FAST_REROUTE["key_create_while_dragging_link"];
|
||||
const CONFIG_KEY_ROTATE = CONFIG_FAST_REROUTE["key_rotate"];
|
||||
const CONFIG_KEY_RESIZE = CONFIG_FAST_REROUTE["key_resize"];
|
||||
const CONFIG_KEY_MOVE = CONFIG_FAST_REROUTE["key_move"];
|
||||
const CONFIG_KEY_CXN_INPUT = CONFIG_FAST_REROUTE["key_connections_input"];
|
||||
const CONFIG_KEY_CXN_OUTPUT = CONFIG_FAST_REROUTE["key_connections_output"];
|
||||
let configWidth = Math.max(Math.round((Number(CONFIG_REROUTE["default_width"]) || 40) / 10) * 10, 10);
|
||||
let configHeight = Math.max(Math.round((Number(CONFIG_REROUTE["default_height"]) || 30) / 10) * 10, 10);
|
||||
while (configWidth * configHeight < 400) {
|
||||
configWidth += 10;
|
||||
configHeight += 10;
|
||||
}
|
||||
const configDefaultSize = [configWidth, configHeight];
|
||||
const configResizable = !!CONFIG_REROUTE["default_resizable"];
|
||||
let configLayout = CONFIG_REROUTE["default_layout"];
|
||||
if (!Array.isArray(configLayout)) {
|
||||
configLayout = ["Left", "Right"];
|
||||
}
|
||||
if (!LAYOUT_LABEL_TO_DATA[configLayout[0]]) {
|
||||
configLayout[0] = "Left";
|
||||
}
|
||||
if (!LAYOUT_LABEL_TO_DATA[configLayout[1]] || configLayout[0] == configLayout[1]) {
|
||||
configLayout[1] = LAYOUT_LABEL_OPPOSITES[configLayout[0]];
|
||||
}
|
||||
class RerouteService {
|
||||
constructor() {
|
||||
this.isFastLinking = false;
|
||||
this.handledNewRerouteKeypress = false;
|
||||
this.connectingData = null;
|
||||
this.fastReroutesHistory = [];
|
||||
this.handleLinkingKeydownBound = this.handleLinkingKeydown.bind(this);
|
||||
this.handleLinkingKeyupBound = this.handleLinkingKeyup.bind(this);
|
||||
if (CONFIG_FAST_REROUTE_ENABLED && (CONFIG_KEY_CREATE_WHILE_LINKING === null || CONFIG_KEY_CREATE_WHILE_LINKING === void 0 ? void 0 : CONFIG_KEY_CREATE_WHILE_LINKING.trim())) {
|
||||
this.onCanvasSetUpListenerForLinking();
|
||||
}
|
||||
}
|
||||
async onCanvasSetUpListenerForLinking() {
|
||||
const canvas = await waitForCanvas();
|
||||
const canvasProperty = true ? "connecting_links" : "connecting_node";
|
||||
canvas[`_${canvasProperty}`];
|
||||
const thisService = this;
|
||||
Object.defineProperty(canvas, canvasProperty, {
|
||||
get: function () {
|
||||
return this[`_${canvasProperty}`];
|
||||
},
|
||||
set: function (value) {
|
||||
var _a;
|
||||
const isValNull = !value || !(value === null || value === void 0 ? void 0 : value.length);
|
||||
const isPropNull = !this[`_${canvasProperty}`] || !((_a = this[`_${canvasProperty}`]) === null || _a === void 0 ? void 0 : _a.length);
|
||||
const isStartingLinking = !isValNull && isPropNull;
|
||||
const isStoppingLinking = !isPropNull && isValNull;
|
||||
this[`_${canvasProperty}`] = value;
|
||||
if (isStartingLinking) {
|
||||
thisService.startingLinking();
|
||||
}
|
||||
if (isStoppingLinking) {
|
||||
thisService.stoppingLinking();
|
||||
thisService.connectingData = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
startingLinking() {
|
||||
this.isFastLinking = true;
|
||||
KEY_EVENT_SERVICE.addEventListener("keydown", this.handleLinkingKeydownBound);
|
||||
KEY_EVENT_SERVICE.addEventListener("keyup", this.handleLinkingKeyupBound);
|
||||
}
|
||||
stoppingLinking() {
|
||||
this.isFastLinking = false;
|
||||
this.fastReroutesHistory = [];
|
||||
KEY_EVENT_SERVICE.removeEventListener("keydown", this.handleLinkingKeydownBound);
|
||||
KEY_EVENT_SERVICE.removeEventListener("keyup", this.handleLinkingKeyupBound);
|
||||
}
|
||||
handleLinkingKeydown(event) {
|
||||
if (!this.handledNewRerouteKeypress &&
|
||||
KEY_EVENT_SERVICE.areOnlyKeysDown(CONFIG_KEY_CREATE_WHILE_LINKING)) {
|
||||
this.handledNewRerouteKeypress = true;
|
||||
this.insertNewRerouteWhileLinking();
|
||||
}
|
||||
}
|
||||
handleLinkingKeyup(event) {
|
||||
if (this.handledNewRerouteKeypress &&
|
||||
!KEY_EVENT_SERVICE.areOnlyKeysDown(CONFIG_KEY_CREATE_WHILE_LINKING)) {
|
||||
this.handledNewRerouteKeypress = false;
|
||||
}
|
||||
}
|
||||
getConnectingData() {
|
||||
var _a, _b, _c, _d;
|
||||
const oldCanvas = app.canvas;
|
||||
if (oldCanvas.connecting_node &&
|
||||
oldCanvas.connecting_slot != null &&
|
||||
((_a = oldCanvas.connecting_pos) === null || _a === void 0 ? void 0 : _a.length)) {
|
||||
return {
|
||||
node: oldCanvas.connecting_node,
|
||||
input: oldCanvas.connecting_input,
|
||||
output: oldCanvas.connecting_output,
|
||||
slot: oldCanvas.connecting_slot,
|
||||
pos: [...oldCanvas.connecting_pos],
|
||||
};
|
||||
}
|
||||
const canvas = app.canvas;
|
||||
if ((_b = canvas.connecting_links) === null || _b === void 0 ? void 0 : _b.length) {
|
||||
const link = canvas.connecting_links[0];
|
||||
return {
|
||||
node: link.node,
|
||||
input: (_c = link.input) !== null && _c !== void 0 ? _c : undefined,
|
||||
output: (_d = link.output) !== null && _d !== void 0 ? _d : undefined,
|
||||
slot: link.slot,
|
||||
pos: [...link.pos],
|
||||
};
|
||||
}
|
||||
throw new Error("Error, handling linking keydown, but there's no link.");
|
||||
}
|
||||
setCanvasConnectingData(ctx) {
|
||||
var _a, _b;
|
||||
const oldCanvas = app.canvas;
|
||||
if (oldCanvas.connecting_node &&
|
||||
oldCanvas.connecting_slot != null &&
|
||||
((_a = oldCanvas.connecting_pos) === null || _a === void 0 ? void 0 : _a.length)) {
|
||||
oldCanvas.connecting_node = ctx.node;
|
||||
oldCanvas.connecting_input = ctx.input;
|
||||
oldCanvas.connecting_output = ctx.output;
|
||||
oldCanvas.connecting_slot = ctx.slot;
|
||||
oldCanvas.connecting_pos = ctx.pos;
|
||||
}
|
||||
const canvas = app.canvas;
|
||||
if ((_b = canvas.connecting_links) === null || _b === void 0 ? void 0 : _b.length) {
|
||||
const link = canvas.connecting_links[0];
|
||||
link.node = ctx.node;
|
||||
link.input = ctx.input;
|
||||
link.output = ctx.output;
|
||||
link.slot = ctx.slot;
|
||||
link.pos = ctx.pos;
|
||||
}
|
||||
}
|
||||
insertNewRerouteWhileLinking() {
|
||||
var _a;
|
||||
const canvas = app.canvas;
|
||||
this.connectingData = this.getConnectingData();
|
||||
if (!this.connectingData) {
|
||||
throw new Error("Error, handling linking keydown, but there's no link.");
|
||||
}
|
||||
const data = this.connectingData;
|
||||
const node = LiteGraph.createNode("Reroute (rgthree)");
|
||||
const entry = {
|
||||
node,
|
||||
previous: { ...this.connectingData },
|
||||
current: undefined,
|
||||
};
|
||||
this.fastReroutesHistory.push(entry);
|
||||
let connectingDir = (_a = (data.input || data.output)) === null || _a === void 0 ? void 0 : _a.dir;
|
||||
if (!connectingDir) {
|
||||
connectingDir = data.input ? LiteGraph.LEFT : LiteGraph.RIGHT;
|
||||
}
|
||||
let newPos = canvas.convertEventToCanvasOffset({
|
||||
clientX: Math.round(canvas.last_mouse_position[0] / 10) * 10,
|
||||
clientY: Math.round(canvas.last_mouse_position[1] / 10) * 10,
|
||||
});
|
||||
entry.node.pos = newPos;
|
||||
canvas.graph.add(entry.node);
|
||||
canvas.selectNode(entry.node);
|
||||
const distX = entry.node.pos[0] - data.pos[0];
|
||||
const distY = entry.node.pos[1] - data.pos[1];
|
||||
const layout = ["Left", "Right"];
|
||||
if (distX > 0 && Math.abs(distX) > Math.abs(distY)) {
|
||||
layout[0] = data.output ? "Left" : "Right";
|
||||
layout[1] = LAYOUT_LABEL_OPPOSITES[layout[0]];
|
||||
node.pos[0] -= node.size[0] + 10;
|
||||
node.pos[1] -= Math.round(node.size[1] / 2 / 10) * 10;
|
||||
}
|
||||
else if (distX < 0 && Math.abs(distX) > Math.abs(distY)) {
|
||||
layout[0] = data.output ? "Right" : "Left";
|
||||
layout[1] = LAYOUT_LABEL_OPPOSITES[layout[0]];
|
||||
node.pos[1] -= Math.round(node.size[1] / 2 / 10) * 10;
|
||||
}
|
||||
else if (distY < 0 && Math.abs(distY) > Math.abs(distX)) {
|
||||
layout[0] = data.output ? "Bottom" : "Top";
|
||||
layout[1] = LAYOUT_LABEL_OPPOSITES[layout[0]];
|
||||
node.pos[0] -= Math.round(node.size[0] / 2 / 10) * 10;
|
||||
}
|
||||
else if (distY > 0 && Math.abs(distY) > Math.abs(distX)) {
|
||||
layout[0] = data.output ? "Top" : "Bottom";
|
||||
layout[1] = LAYOUT_LABEL_OPPOSITES[layout[0]];
|
||||
node.pos[0] -= Math.round(node.size[0] / 2 / 10) * 10;
|
||||
node.pos[1] -= node.size[1] + 10;
|
||||
}
|
||||
setConnectionsLayout(entry.node, layout);
|
||||
if (data.output) {
|
||||
data.node.connect(data.slot, entry.node, 0);
|
||||
data.node = entry.node;
|
||||
data.output = entry.node.outputs[0];
|
||||
data.slot = 0;
|
||||
data.pos = entry.node.getConnectionPos(false, 0);
|
||||
data.direction =
|
||||
layout[0] === "Top" ? 2 : layout[0] === "Bottom" ? 1 : layout[0] === "Left" ? 4 : 3;
|
||||
}
|
||||
else {
|
||||
entry.node.connect(0, data.node, data.slot);
|
||||
data.node = entry.node;
|
||||
data.input = entry.node.inputs[0];
|
||||
data.slot = 0;
|
||||
data.pos = entry.node.getConnectionPos(true, 0);
|
||||
data.direction =
|
||||
layout[1] === "Top" ? 2 : layout[1] === "Bottom" ? 1 : layout[1] === "Left" ? 4 : 3;
|
||||
}
|
||||
this.setCanvasConnectingData(data);
|
||||
entry.current = { ...this.connectingData };
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
handleMoveOrResizeNodeMaybeWhileDragging(node) {
|
||||
const data = this.connectingData;
|
||||
if (this.isFastLinking && node === (data === null || data === void 0 ? void 0 : data.node)) {
|
||||
const entry = this.fastReroutesHistory[this.fastReroutesHistory.length - 1];
|
||||
if (entry) {
|
||||
data.pos = entry.node.getConnectionPos(!!data.input, 0);
|
||||
this.setCanvasConnectingData(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
handleRemovedNodeMaybeWhileDragging(node) {
|
||||
const currentEntry = this.fastReroutesHistory[this.fastReroutesHistory.length - 1];
|
||||
if ((currentEntry === null || currentEntry === void 0 ? void 0 : currentEntry.node) === node) {
|
||||
this.setCanvasConnectingData(currentEntry.previous);
|
||||
this.fastReroutesHistory.splice(this.fastReroutesHistory.length - 1, 1);
|
||||
if (currentEntry.previous.node) {
|
||||
app.canvas.selectNode(currentEntry.previous.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const SERVICE = new RerouteService();
|
||||
class RerouteNode extends RgthreeBaseVirtualNode {
|
||||
constructor(title = RerouteNode.title) {
|
||||
super(title);
|
||||
this.comfyClass = NodeTypesString.REROUTE;
|
||||
this.isVirtualNode = true;
|
||||
this.hideSlotLabels = true;
|
||||
this.schedulePromise = null;
|
||||
this.defaultConnectionsLayout = Array.from(configLayout);
|
||||
this.shortcuts = {
|
||||
rotate: { keys: CONFIG_KEY_ROTATE, state: false },
|
||||
connection_input: { keys: CONFIG_KEY_CXN_INPUT, state: false },
|
||||
connection_output: { keys: CONFIG_KEY_CXN_OUTPUT, state: false },
|
||||
resize: {
|
||||
keys: CONFIG_KEY_RESIZE,
|
||||
state: false,
|
||||
initialMousePos: [-1, -1],
|
||||
initialNodeSize: [-1, -1],
|
||||
initialNodePos: [-1, -1],
|
||||
resizeOnSide: [-1, -1],
|
||||
},
|
||||
move: {
|
||||
keys: CONFIG_KEY_MOVE,
|
||||
state: false,
|
||||
initialMousePos: [-1, -1],
|
||||
initialNodePos: [-1, -1],
|
||||
},
|
||||
};
|
||||
this.onConstructed();
|
||||
}
|
||||
onConstructed() {
|
||||
var _a;
|
||||
this.setResizable(!!((_a = this.properties["resizable"]) !== null && _a !== void 0 ? _a : configResizable));
|
||||
this.size = RerouteNode.size;
|
||||
this.addInput("", "*");
|
||||
this.addOutput("", "*");
|
||||
setTimeout(() => this.applyNodeSize(), 20);
|
||||
return super.onConstructed();
|
||||
}
|
||||
configure(info) {
|
||||
var _a, _b;
|
||||
if ((_a = info.inputs) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
info.inputs.length = 1;
|
||||
}
|
||||
super.configure(info);
|
||||
this.configuring = true;
|
||||
this.setResizable(!!((_b = this.properties["resizable"]) !== null && _b !== void 0 ? _b : configResizable));
|
||||
this.applyNodeSize();
|
||||
this.configuring = false;
|
||||
}
|
||||
setResizable(resizable) {
|
||||
this.properties["resizable"] = !!resizable;
|
||||
this.resizable = this.properties["resizable"];
|
||||
}
|
||||
clone() {
|
||||
const cloned = super.clone();
|
||||
cloned.inputs[0].type = "*";
|
||||
cloned.outputs[0].type = "*";
|
||||
return cloned;
|
||||
}
|
||||
onConnectionsChange(type, _slotIndex, connected, _link_info, _ioSlot) {
|
||||
if (connected && type === LiteGraph.OUTPUT) {
|
||||
const types = new Set(this.outputs[0].links.map((l) => { var _a; return (_a = app.graph.links[l]) === null || _a === void 0 ? void 0 : _a.type; }).filter((t) => t && t !== "*"));
|
||||
if (types.size > 1) {
|
||||
const linksToDisconnect = [];
|
||||
for (let i = 0; i < this.outputs[0].links.length - 1; i++) {
|
||||
const linkId = this.outputs[0].links[i];
|
||||
const link = app.graph.links[linkId];
|
||||
linksToDisconnect.push(link);
|
||||
}
|
||||
for (const link of linksToDisconnect) {
|
||||
const node = app.graph.getNodeById(link.target_id);
|
||||
node.disconnectInput(link.target_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.scheduleStabilize();
|
||||
}
|
||||
onDrawForeground(ctx, canvas) {
|
||||
var _a, _b, _c, _d;
|
||||
if ((_a = this.properties) === null || _a === void 0 ? void 0 : _a["showLabel"]) {
|
||||
const low_quality = ((_b = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _b === void 0 ? void 0 : _b.scale) && canvas.ds.scale < 0.6;
|
||||
if (low_quality || this.size[0] <= 10) {
|
||||
return;
|
||||
}
|
||||
const fontSize = Math.min(14, (this.size[1] * 0.65) | 0);
|
||||
ctx.save();
|
||||
ctx.fillStyle = "#888";
|
||||
ctx.font = `${fontSize}px Arial`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(String(this.title && this.title !== RerouteNode.title
|
||||
? this.title
|
||||
: ((_d = (_c = this.outputs) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.type) || ""), this.size[0] / 2, this.size[1] / 2, this.size[0] - 30);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
findInputSlot(name, returnObj = false) {
|
||||
return returnObj ? this.inputs[0] : 0;
|
||||
}
|
||||
findOutputSlot(name, returnObj) {
|
||||
return returnObj ? this.outputs[0] : 0;
|
||||
}
|
||||
disconnectOutput(slot, targetNode) {
|
||||
return super.disconnectOutput(slot, targetNode);
|
||||
}
|
||||
disconnectInput(slot) {
|
||||
var _a;
|
||||
if (rgthree.replacingReroute != null && ((_a = this.inputs[0]) === null || _a === void 0 ? void 0 : _a.link)) {
|
||||
const graph = app.graph;
|
||||
const link = graph.links[this.inputs[0].link];
|
||||
const node = (link === null || link === void 0 ? void 0 : link.origin_id) != null ? graph.getNodeById(link.origin_id) : null;
|
||||
if (rgthree.replacingReroute !== (node === null || node === void 0 ? void 0 : node.id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.disconnectInput(slot);
|
||||
}
|
||||
scheduleStabilize(ms = 64) {
|
||||
if (!this.schedulePromise) {
|
||||
this.schedulePromise = new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
this.schedulePromise = null;
|
||||
this.stabilize();
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
return this.schedulePromise;
|
||||
}
|
||||
stabilize() {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
||||
if (this.configuring) {
|
||||
return;
|
||||
}
|
||||
let currentNode = this;
|
||||
let updateNodes = [];
|
||||
let input = null;
|
||||
let inputType = null;
|
||||
let inputNode = null;
|
||||
let inputNodeOutputSlot = null;
|
||||
while (currentNode) {
|
||||
updateNodes.unshift(currentNode);
|
||||
const linkId = currentNode.inputs[0].link;
|
||||
if (linkId !== null) {
|
||||
const link = app.graph.links[linkId];
|
||||
const node = app.graph.getNodeById(link.origin_id);
|
||||
if (!node) {
|
||||
app.graph.removeLink(linkId);
|
||||
currentNode = null;
|
||||
break;
|
||||
}
|
||||
const type = node.constructor.type;
|
||||
if (type === null || type === void 0 ? void 0 : type.includes("Reroute")) {
|
||||
if (node === this) {
|
||||
currentNode.disconnectInput(link.target_slot);
|
||||
currentNode = null;
|
||||
}
|
||||
else {
|
||||
currentNode = node;
|
||||
}
|
||||
}
|
||||
else {
|
||||
inputNode = node;
|
||||
inputNodeOutputSlot = link.origin_slot;
|
||||
input = (_a = node.outputs[inputNodeOutputSlot]) !== null && _a !== void 0 ? _a : null;
|
||||
inputType = (_b = input === null || input === void 0 ? void 0 : input.type) !== null && _b !== void 0 ? _b : null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentNode = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const nodes = [this];
|
||||
let outputNode = null;
|
||||
let outputType = null;
|
||||
let outputWidgetConfig = null;
|
||||
let outputWidget = null;
|
||||
while (nodes.length) {
|
||||
currentNode = nodes.pop();
|
||||
const outputs = (currentNode.outputs ? currentNode.outputs[0].links : []) || [];
|
||||
if (outputs.length) {
|
||||
for (const linkId of outputs) {
|
||||
const link = app.graph.links[linkId];
|
||||
if (!link)
|
||||
continue;
|
||||
const node = app.graph.getNodeById(link.target_id);
|
||||
if (!node)
|
||||
continue;
|
||||
const type = node.constructor.type;
|
||||
if (type === null || type === void 0 ? void 0 : type.includes("Reroute")) {
|
||||
nodes.push(node);
|
||||
updateNodes.push(node);
|
||||
}
|
||||
else {
|
||||
const output = (_d = (_c = node.inputs) === null || _c === void 0 ? void 0 : _c[link.target_slot]) !== null && _d !== void 0 ? _d : null;
|
||||
const nodeOutType = output === null || output === void 0 ? void 0 : output.type;
|
||||
if (nodeOutType == null) {
|
||||
console.warn(`[rgthree] Reroute - Connected node ${node.id} does not have type information for ` +
|
||||
`slot ${link.target_slot}. Skipping connection enforcement, but something is odd ` +
|
||||
`with that node.`);
|
||||
}
|
||||
else if (inputType &&
|
||||
inputType !== "*" &&
|
||||
nodeOutType !== "*" &&
|
||||
!isValidConnection(input, output)) {
|
||||
console.warn(`[rgthree] Reroute - Disconnecting connected node's input (${node.id}.${link.target_slot}) (${node.type}) because its type (${String(nodeOutType)}) does not match the reroute type (${String(inputType)})`);
|
||||
node.disconnectInput(link.target_slot);
|
||||
}
|
||||
else {
|
||||
outputType = nodeOutType;
|
||||
outputNode = node;
|
||||
outputWidgetConfig = null;
|
||||
outputWidget = null;
|
||||
if (output === null || output === void 0 ? void 0 : output.widget) {
|
||||
rgthreeApi.print("PRIMITIVE_REROUTE");
|
||||
try {
|
||||
const config = getWidgetConfig(output);
|
||||
if (!outputWidgetConfig && config) {
|
||||
outputWidgetConfig = (_e = config[1]) !== null && _e !== void 0 ? _e : {};
|
||||
outputType = config[0];
|
||||
if (!outputWidget) {
|
||||
outputWidget = (_f = outputNode.widgets) === null || _f === void 0 ? void 0 : _f.find((w) => { var _a; return w.name === ((_a = output === null || output === void 0 ? void 0 : output.widget) === null || _a === void 0 ? void 0 : _a.name); });
|
||||
}
|
||||
const merged = mergeIfValid(output, [config[0], outputWidgetConfig]);
|
||||
if (merged === null || merged === void 0 ? void 0 : merged.customConfig) {
|
||||
outputWidgetConfig = merged.customConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error("[rgthree] Could not propagate widget infor for reroute; maybe ComfyUI updated?");
|
||||
outputWidgetConfig = null;
|
||||
outputWidget = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
}
|
||||
}
|
||||
const displayType = inputType || outputType || "*";
|
||||
const color = LGraphCanvas.link_type_colors[displayType];
|
||||
for (const node of updateNodes) {
|
||||
node.outputs[0].type = inputType || "*";
|
||||
node.__outputType = displayType;
|
||||
node.outputs[0].name = (input === null || input === void 0 ? void 0 : input.name) || "";
|
||||
node.size = node.computeSize();
|
||||
(_h = (_g = node).applyNodeSize) === null || _h === void 0 ? void 0 : _h.call(_g);
|
||||
for (const l of node.outputs[0].links || []) {
|
||||
const link = app.graph.links[l];
|
||||
if (link && color) {
|
||||
link.color = color;
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (outputWidgetConfig && outputWidget && outputType) {
|
||||
rgthreeApi.print("PRIMITIVE_REROUTE");
|
||||
node.inputs[0].widget = { name: "value" };
|
||||
setWidgetConfig(node.inputs[0], [outputType !== null && outputType !== void 0 ? outputType : displayType, outputWidgetConfig]);
|
||||
}
|
||||
else {
|
||||
setWidgetConfig(node.inputs[0], null);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error("[rgthree] Could not set widget config for reroute; maybe ComfyUI updated?");
|
||||
outputWidgetConfig = null;
|
||||
outputWidget = null;
|
||||
if ((_j = node.inputs[0]) === null || _j === void 0 ? void 0 : _j.widget) {
|
||||
delete node.inputs[0].widget;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inputNode && inputNodeOutputSlot != null) {
|
||||
const links = inputNode.outputs[inputNodeOutputSlot].links;
|
||||
for (const l of links || []) {
|
||||
const link = app.graph.links[l];
|
||||
if (link && color) {
|
||||
link.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
(_k = inputNode === null || inputNode === void 0 ? void 0 : inputNode.onConnectionsChainChange) === null || _k === void 0 ? void 0 : _k.call(inputNode);
|
||||
(_l = outputNode === null || outputNode === void 0 ? void 0 : outputNode.onConnectionsChainChange) === null || _l === void 0 ? void 0 : _l.call(outputNode);
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
setSize(size) {
|
||||
const oldSize = [...this.size];
|
||||
const newSize = [...size];
|
||||
super.setSize(newSize);
|
||||
this.properties["size"] = [...this.size];
|
||||
this.stabilizeLayout(oldSize, newSize);
|
||||
}
|
||||
stabilizeLayout(oldSize, newSize) {
|
||||
if (newSize[0] === 10 || newSize[1] === 10) {
|
||||
const props = this.properties;
|
||||
props["connections_layout"] = props["connections_layout"] || ["Left", "Right"];
|
||||
props["connections_dir"] = props["connections_dir"] || [-1, -1];
|
||||
const layout = props["connections_layout"];
|
||||
const dir = props["connections_dir"];
|
||||
if (oldSize[0] > 10 && newSize[0] === 10) {
|
||||
dir[0] = LiteGraph.DOWN;
|
||||
dir[1] = LiteGraph.UP;
|
||||
if (layout[0] === "Bottom") {
|
||||
layout[1] = "Top";
|
||||
}
|
||||
else if (layout[1] === "Top") {
|
||||
layout[0] = "Bottom";
|
||||
}
|
||||
else {
|
||||
layout[0] = "Top";
|
||||
layout[1] = "Bottom";
|
||||
dir[0] = LiteGraph.UP;
|
||||
dir[1] = LiteGraph.DOWN;
|
||||
}
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
else if (oldSize[1] > 10 && newSize[1] === 10) {
|
||||
dir[0] = LiteGraph.RIGHT;
|
||||
dir[1] = LiteGraph.LEFT;
|
||||
if (layout[0] === "Right") {
|
||||
layout[1] = "Left";
|
||||
}
|
||||
else if (layout[1] === "Left") {
|
||||
layout[0] = "Right";
|
||||
}
|
||||
else {
|
||||
layout[0] = "Left";
|
||||
layout[1] = "Right";
|
||||
dir[0] = LiteGraph.LEFT;
|
||||
dir[1] = LiteGraph.RIGHT;
|
||||
}
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
SERVICE.handleMoveOrResizeNodeMaybeWhileDragging(this);
|
||||
}
|
||||
applyNodeSize() {
|
||||
this.properties["size"] = this.properties["size"] || RerouteNode.size;
|
||||
this.properties["size"] = [
|
||||
Number(this.properties["size"][0]),
|
||||
Number(this.properties["size"][1]),
|
||||
];
|
||||
this.size = this.properties["size"];
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
rotate(degrees) {
|
||||
const w = this.size[0];
|
||||
const h = this.size[1];
|
||||
this.properties["connections_layout"] =
|
||||
this.properties["connections_layout"] || this.defaultConnectionsLayout;
|
||||
const connections_layout = this.properties["connections_layout"];
|
||||
const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[0]);
|
||||
const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[1]);
|
||||
if (degrees == 90 || degrees === -90) {
|
||||
if (degrees === -90) {
|
||||
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex - 1) % 4) + 4) % 4];
|
||||
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex - 1) % 4) + 4) % 4];
|
||||
}
|
||||
else {
|
||||
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 1) % 4) + 4) % 4];
|
||||
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 1) % 4) + 4) % 4];
|
||||
}
|
||||
}
|
||||
else if (degrees === 180) {
|
||||
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4];
|
||||
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4];
|
||||
}
|
||||
this.setSize([h, w]);
|
||||
}
|
||||
manuallyHandleMove(event) {
|
||||
const shortcut = this.shortcuts.move;
|
||||
if (shortcut.state) {
|
||||
const diffX = Math.round((event.clientX - shortcut.initialMousePos[0]) / 10) * 10;
|
||||
const diffY = Math.round((event.clientY - shortcut.initialMousePos[1]) / 10) * 10;
|
||||
this.pos[0] = shortcut.initialNodePos[0] + diffX;
|
||||
this.pos[1] = shortcut.initialNodePos[1] + diffY;
|
||||
this.setDirtyCanvas(true, true);
|
||||
SERVICE.handleMoveOrResizeNodeMaybeWhileDragging(this);
|
||||
}
|
||||
}
|
||||
manuallyHandleResize(event) {
|
||||
const shortcut = this.shortcuts.resize;
|
||||
if (shortcut.state) {
|
||||
let diffX = Math.round((event.clientX - shortcut.initialMousePos[0]) / 10) * 10;
|
||||
let diffY = Math.round((event.clientY - shortcut.initialMousePos[1]) / 10) * 10;
|
||||
diffX *= shortcut.resizeOnSide[0] === LiteGraph.LEFT ? -1 : 1;
|
||||
diffY *= shortcut.resizeOnSide[1] === LiteGraph.UP ? -1 : 1;
|
||||
const oldSize = [...this.size];
|
||||
this.setSize([
|
||||
Math.max(10, shortcut.initialNodeSize[0] + diffX),
|
||||
Math.max(10, shortcut.initialNodeSize[1] + diffY),
|
||||
]);
|
||||
if (shortcut.resizeOnSide[0] === LiteGraph.LEFT && oldSize[0] > 10) {
|
||||
this.pos[0] = shortcut.initialNodePos[0] - diffX;
|
||||
}
|
||||
if (shortcut.resizeOnSide[1] === LiteGraph.UP && oldSize[1] > 10) {
|
||||
this.pos[1] = shortcut.initialNodePos[1] - diffY;
|
||||
}
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
cycleConnection(ioDir) {
|
||||
var _a, _b;
|
||||
const props = this.properties;
|
||||
props["connections_layout"] = props["connections_layout"] || ["Left", "Right"];
|
||||
const connections_layout = this.properties["connections_layout"];
|
||||
const propIdx = ioDir == IoDirection.INPUT ? 0 : 1;
|
||||
const oppositeIdx = propIdx ? 0 : 1;
|
||||
let currentLayout = connections_layout[propIdx];
|
||||
let oppositeLayout = connections_layout[oppositeIdx];
|
||||
if (this.size[0] === 10 || this.size[1] === 10) {
|
||||
props["connections_dir"] = props["connections_dir"] || [-1, -1];
|
||||
const connections_dir = this.properties["connections_dir"];
|
||||
let currentDir = connections_dir[propIdx];
|
||||
const options = this.size[0] === 10
|
||||
? currentLayout === "Bottom"
|
||||
? [LiteGraph.DOWN, LiteGraph.RIGHT, LiteGraph.LEFT]
|
||||
: [LiteGraph.UP, LiteGraph.LEFT, LiteGraph.RIGHT]
|
||||
: currentLayout === "Right"
|
||||
? [LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.UP]
|
||||
: [LiteGraph.LEFT, LiteGraph.UP, LiteGraph.DOWN];
|
||||
let idx = options.indexOf(currentDir);
|
||||
let next = (_a = options[idx + 1]) !== null && _a !== void 0 ? _a : options[0];
|
||||
connections_dir[propIdx] = next;
|
||||
return;
|
||||
}
|
||||
let next = currentLayout;
|
||||
do {
|
||||
let idx = LAYOUT_CLOCKWISE.indexOf(next);
|
||||
next = (_b = LAYOUT_CLOCKWISE[idx + 1]) !== null && _b !== void 0 ? _b : LAYOUT_CLOCKWISE[0];
|
||||
} while (next === oppositeLayout);
|
||||
connections_layout[propIdx] = next;
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
onMouseMove(event) {
|
||||
if (this.shortcuts.move.state) {
|
||||
const shortcut = this.shortcuts.move;
|
||||
if (shortcut.initialMousePos[0] === -1) {
|
||||
shortcut.initialMousePos[0] = event.clientX;
|
||||
shortcut.initialMousePos[1] = event.clientY;
|
||||
shortcut.initialNodePos[0] = this.pos[0];
|
||||
shortcut.initialNodePos[1] = this.pos[1];
|
||||
}
|
||||
this.manuallyHandleMove(event);
|
||||
}
|
||||
else if (this.shortcuts.resize.state) {
|
||||
const shortcut = this.shortcuts.resize;
|
||||
if (shortcut.initialMousePos[0] === -1) {
|
||||
shortcut.initialMousePos[0] = event.clientX;
|
||||
shortcut.initialMousePos[1] = event.clientY;
|
||||
shortcut.initialNodeSize[0] = this.size[0];
|
||||
shortcut.initialNodeSize[1] = this.size[1];
|
||||
shortcut.initialNodePos[0] = this.pos[0];
|
||||
shortcut.initialNodePos[1] = this.pos[1];
|
||||
const canvas = app.canvas;
|
||||
const offset = canvas.convertEventToCanvasOffset(event);
|
||||
shortcut.resizeOnSide[0] = this.pos[0] > offset[0] ? LiteGraph.LEFT : LiteGraph.RIGHT;
|
||||
shortcut.resizeOnSide[1] = this.pos[1] > offset[1] ? LiteGraph.UP : LiteGraph.DOWN;
|
||||
}
|
||||
this.manuallyHandleResize(event);
|
||||
}
|
||||
}
|
||||
onKeyDown(event) {
|
||||
super.onKeyDown(event);
|
||||
const canvas = app.canvas;
|
||||
if (CONFIG_FAST_REROUTE_ENABLED) {
|
||||
for (const [key, shortcut] of Object.entries(this.shortcuts)) {
|
||||
if (!shortcut.state) {
|
||||
const keys = KEY_EVENT_SERVICE.areOnlyKeysDown(shortcut.keys);
|
||||
if (keys) {
|
||||
shortcut.state = true;
|
||||
if (key === "rotate") {
|
||||
this.rotate(90);
|
||||
}
|
||||
else if (key.includes("connection")) {
|
||||
this.cycleConnection(key.includes("input") ? IoDirection.INPUT : IoDirection.OUTPUT);
|
||||
}
|
||||
if (shortcut.initialMousePos) {
|
||||
canvas.node_capturing_input = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onKeyUp(event) {
|
||||
super.onKeyUp(event);
|
||||
const canvas = app.canvas;
|
||||
if (CONFIG_FAST_REROUTE_ENABLED) {
|
||||
for (const [key, shortcut] of Object.entries(this.shortcuts)) {
|
||||
if (shortcut.state) {
|
||||
const keys = KEY_EVENT_SERVICE.areOnlyKeysDown(shortcut.keys);
|
||||
if (!keys) {
|
||||
shortcut.state = false;
|
||||
if (shortcut.initialMousePos) {
|
||||
shortcut.initialMousePos = [-1, -1];
|
||||
if ((canvas.node_capturing_input = this)) {
|
||||
canvas.node_capturing_input = null;
|
||||
}
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onDeselected() {
|
||||
var _a;
|
||||
(_a = super.onDeselected) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
const canvas = app.canvas;
|
||||
for (const [key, shortcut] of Object.entries(this.shortcuts)) {
|
||||
shortcut.state = false;
|
||||
if (shortcut.initialMousePos) {
|
||||
shortcut.initialMousePos = [-1, -1];
|
||||
if ((canvas.node_capturing_input = this)) {
|
||||
canvas.node_capturing_input = null;
|
||||
}
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
onRemoved() {
|
||||
var _a;
|
||||
(_a = super.onRemoved) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
setTimeout(() => {
|
||||
SERVICE.handleRemovedNodeMaybeWhileDragging(this);
|
||||
}, 32);
|
||||
}
|
||||
getHelp() {
|
||||
return `
|
||||
<p>
|
||||
Finally, a comfortable, powerful reroute node with true multi-direction and powerful
|
||||
shortcuts to bring your workflow to the next level.
|
||||
</p>
|
||||
|
||||
${!CONFIG_FAST_REROUTE_ENABLED
|
||||
? `<p><i>Fast Shortcuts are currently disabled.</b>`
|
||||
: `
|
||||
<ul>
|
||||
<li><p>
|
||||
<code>${CONFIG_KEY_CREATE_WHILE_LINKING}</code> Create a new reroute node while dragging
|
||||
a link, connecting it to the link in the place and continuing the link.
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>${CONFIG_KEY_ROTATE}</code> Rotate the selected reroute node counter clockwise 90
|
||||
degrees.
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>${CONFIG_KEY_RESIZE}</code> Resize the selected reroute node from the nearest
|
||||
corner by holding down and moving your mouse.
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>${CONFIG_KEY_MOVE}</code> Move the selected reroute node by holding down and
|
||||
moving your mouse.
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>${CONFIG_KEY_CXN_INPUT}</code> Change the input layout/direction of the selected
|
||||
reroute node.
|
||||
</p></li>
|
||||
<li><p>
|
||||
<code>${CONFIG_KEY_CXN_OUTPUT}</code> Change the output layout/direction of the selected
|
||||
reroute node.
|
||||
</p></li>
|
||||
</ul>
|
||||
`}
|
||||
<p><small>
|
||||
To change, ${!CONFIG_FAST_REROUTE_ENABLED ? "enable" : "disable"} or configure sohrtcuts,
|
||||
make a copy of
|
||||
<code>/custom_nodes/rgthree-comfy/rgthree_config.json.default</code> to
|
||||
<code>/custom_nodes/rgthree-comfy/rgthree_config.json</code> and configure under
|
||||
<code>nodes > reroute > fast_reroute</code>.
|
||||
</small></p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
RerouteNode.title = NodeTypesString.REROUTE;
|
||||
RerouteNode.type = NodeTypesString.REROUTE;
|
||||
RerouteNode.title_mode = LiteGraph.NO_TITLE;
|
||||
RerouteNode.collapsable = false;
|
||||
RerouteNode.layout_slot_offset = 5;
|
||||
RerouteNode.size = configDefaultSize;
|
||||
addMenuItem(RerouteNode, app, {
|
||||
name: (node) => { var _a; return `${((_a = node.properties) === null || _a === void 0 ? void 0 : _a["showLabel"]) ? "Hide" : "Show"} Label/Title`; },
|
||||
property: "showLabel",
|
||||
callback: async (node, value) => {
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
},
|
||||
});
|
||||
addMenuItem(RerouteNode, app, {
|
||||
name: (node) => `${node.resizable ? "No" : "Allow"} Resizing`,
|
||||
callback: (node) => {
|
||||
node.setResizable(!node.resizable);
|
||||
node.size[0] = Math.max(40, node.size[0]);
|
||||
node.size[1] = Math.max(30, node.size[1]);
|
||||
node.applyNodeSize();
|
||||
},
|
||||
});
|
||||
addMenuItem(RerouteNode, app, {
|
||||
name: "Static Width",
|
||||
property: "size",
|
||||
subMenuOptions: (() => {
|
||||
const options = [];
|
||||
for (let w = 8; w > 0; w--) {
|
||||
options.push(`${w * 10}`);
|
||||
}
|
||||
return options;
|
||||
})(),
|
||||
prepareValue: (value, node) => [Number(value), node.size[1]],
|
||||
callback: (node) => {
|
||||
node.setResizable(false);
|
||||
node.applyNodeSize();
|
||||
},
|
||||
});
|
||||
addMenuItem(RerouteNode, app, {
|
||||
name: "Static Height",
|
||||
property: "size",
|
||||
subMenuOptions: (() => {
|
||||
const options = [];
|
||||
for (let w = 8; w > 0; w--) {
|
||||
options.push(`${w * 10}`);
|
||||
}
|
||||
return options;
|
||||
})(),
|
||||
prepareValue: (value, node) => [node.size[0], Number(value)],
|
||||
callback: (node) => {
|
||||
node.setResizable(false);
|
||||
node.applyNodeSize();
|
||||
},
|
||||
});
|
||||
addConnectionLayoutSupport(RerouteNode, app, [
|
||||
["Left", "Right"],
|
||||
["Left", "Top"],
|
||||
["Left", "Bottom"],
|
||||
["Right", "Left"],
|
||||
["Right", "Top"],
|
||||
["Right", "Bottom"],
|
||||
["Top", "Left"],
|
||||
["Top", "Right"],
|
||||
["Top", "Bottom"],
|
||||
["Bottom", "Left"],
|
||||
["Bottom", "Right"],
|
||||
["Bottom", "Top"],
|
||||
], (node) => {
|
||||
node.applyNodeSize();
|
||||
});
|
||||
addMenuItem(RerouteNode, app, {
|
||||
name: "Rotate",
|
||||
subMenuOptions: [
|
||||
"Rotate 90° Clockwise",
|
||||
"Rotate 90° Counter-Clockwise",
|
||||
"Rotate 180°",
|
||||
null,
|
||||
"Flip Horizontally",
|
||||
"Flip Vertically",
|
||||
],
|
||||
callback: (node_, value) => {
|
||||
const node = node_;
|
||||
if (value === null || value === void 0 ? void 0 : value.startsWith("Rotate 90° Clockwise")) {
|
||||
node.rotate(90);
|
||||
}
|
||||
else if (value === null || value === void 0 ? void 0 : value.startsWith("Rotate 90° Counter-Clockwise")) {
|
||||
node.rotate(-90);
|
||||
}
|
||||
else if (value === null || value === void 0 ? void 0 : value.startsWith("Rotate 180°")) {
|
||||
node.rotate(180);
|
||||
}
|
||||
else {
|
||||
const connections_layout = node.properties["connections_layout"];
|
||||
const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[0]);
|
||||
const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[1]);
|
||||
if (value === null || value === void 0 ? void 0 : value.startsWith("Flip Horizontally")) {
|
||||
if (["Left", "Right"].includes(connections_layout[0])) {
|
||||
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4];
|
||||
}
|
||||
if (["Left", "Right"].includes(connections_layout[1])) {
|
||||
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4];
|
||||
}
|
||||
}
|
||||
else if (value === null || value === void 0 ? void 0 : value.startsWith("Flip Vertically")) {
|
||||
if (["Top", "Bottom"].includes(connections_layout[0])) {
|
||||
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4];
|
||||
}
|
||||
if (["Top", "Bottom"].includes(connections_layout[1])) {
|
||||
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
addMenuItem(RerouteNode, app, {
|
||||
name: "Clone New Reroute...",
|
||||
subMenuOptions: ["Before", "After"],
|
||||
callback: async (node, value) => {
|
||||
const clone = node.clone();
|
||||
const pos = [...node.pos];
|
||||
if (value === "Before") {
|
||||
clone.pos = [pos[0] - 20, pos[1] - 20];
|
||||
app.graph.add(clone);
|
||||
await wait();
|
||||
const inputLinks = getSlotLinks(node.inputs[0]);
|
||||
for (const inputLink of inputLinks) {
|
||||
const link = inputLink.link;
|
||||
const linkedNode = app.graph.getNodeById(link.origin_id);
|
||||
if (linkedNode) {
|
||||
linkedNode.connect(0, clone, 0);
|
||||
}
|
||||
}
|
||||
clone.connect(0, node, 0);
|
||||
}
|
||||
else {
|
||||
clone.pos = [pos[0] + 20, pos[1] + 20];
|
||||
app.graph.add(clone);
|
||||
await wait();
|
||||
const outputLinks = getSlotLinks(node.outputs[0]);
|
||||
node.connect(0, clone, 0);
|
||||
for (const outputLink of outputLinks) {
|
||||
const link = outputLink.link;
|
||||
const linkedNode = app.graph.getNodeById(link.target_id);
|
||||
if (linkedNode) {
|
||||
clone.connect(0, linkedNode, link.target_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
app.registerExtension({
|
||||
name: "rgthree.Reroute",
|
||||
registerCustomNodes() {
|
||||
RerouteNode.setUp();
|
||||
},
|
||||
});
|
||||
696
custom_nodes/rgthree-comfy/web/comfyui/rgthree.css
Normal file
696
custom_nodes/rgthree-comfy/web/comfyui/rgthree.css
Normal file
@@ -0,0 +1,696 @@
|
||||
@charset "UTF-8";
|
||||
.rgthree-top-messages-container {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container > div {
|
||||
position: relative;
|
||||
height: fit-content;
|
||||
padding: 4px;
|
||||
margin-top: -100px; /* re-set by JS */
|
||||
opacity: 0;
|
||||
transition: all 0.33s ease-in-out;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container > div:last-child {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container > div:not(.-show) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container > div.-show {
|
||||
opacity: 1;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container > div.-show {
|
||||
opacity: 1;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container > div > div {
|
||||
position: relative;
|
||||
background: #353535;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: fit-content;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.88);
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container > div > div > span {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container > div > div > span svg {
|
||||
width: 20px;
|
||||
height: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container > div > div > span svg.icon-checkmark {
|
||||
fill: #2e9720;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container [type=warn]::before,
|
||||
.rgthree-top-messages-container [type=success]::before {
|
||||
content: "⚠️";
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
font-size: 18px;
|
||||
margin-right: 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container [type=success]::before {
|
||||
content: "🎉";
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container a {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
color: #fc0;
|
||||
margin-left: 4px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.rgthree-top-messages-container a:hover {
|
||||
color: #fc0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Fix node selector being crazy long b/c of array types. */
|
||||
.litegraph.litesearchbox input,
|
||||
.litegraph.litesearchbox select {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
/* There's no reason for this z-index to be so high. It layers on top of things it shouldn't,
|
||||
(like pythongssss' image gallery, the properties panel, etc.) */
|
||||
.comfy-multiline-input {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
.comfy-multiline-input:focus {
|
||||
z-index: 2 !important;
|
||||
}
|
||||
|
||||
.litegraph .dialog {
|
||||
z-index: 3 !important; /* This is set to 1, but goes under the multi-line inputs, so bump it. */
|
||||
}
|
||||
|
||||
:not(#fakeid) .rgthree-button-reset {
|
||||
position: relative;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:not(#fakeid) .rgthree-button {
|
||||
--padding-top: 7px;
|
||||
--padding-bottom: 9px;
|
||||
--padding-x: 16px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
border-radius: 0.33rem;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
margin: 0.25rem;
|
||||
box-shadow: 0px 0px 2px rgb(0, 0, 0);
|
||||
background: #212121;
|
||||
transition: all 0.1s ease-in-out;
|
||||
padding: var(--padding-top) var(--padding-x) var(--padding-bottom);
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button::before, :not(#fakeid) .rgthree-button::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
border-radius: 0.33rem;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.12), inset -1px -1px 0px rgba(0, 0, 0, 0.75);
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15));
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button::after {
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button:hover {
|
||||
background: #303030;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button:active {
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0);
|
||||
background: #121212;
|
||||
padding: calc(var(--padding-top) + 1px) calc(var(--padding-x) - 1px) calc(var(--padding-bottom) - 1px) calc(var(--padding-x) + 1px);
|
||||
}
|
||||
:not(#fakeid) .rgthree-button:active::before, :not(#fakeid) .rgthree-button:active::after {
|
||||
box-shadow: 1px 1px 0px rgba(255, 255, 255, 0.15), inset 1px 1px 0px rgba(0, 0, 0, 0.5), inset 1px 3px 5px rgba(0, 0, 0, 0.33);
|
||||
}
|
||||
:not(#fakeid) .rgthree-button.-blue {
|
||||
background: #346599 !important;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button.-blue:hover {
|
||||
background: #3b77b8 !important;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button.-blue:active {
|
||||
background: #1d5086 !important;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button.-green {
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15)), #14580b;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button.-green:hover {
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15)), #1a6d0f;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button.-green:active {
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.06)), #0f3f09;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button[disabled] {
|
||||
box-shadow: none;
|
||||
background: #666 !important;
|
||||
color: #aaa;
|
||||
pointer-events: none;
|
||||
}
|
||||
:not(#fakeid) .rgthree-button[disabled]::before, :not(#fakeid) .rgthree-button[disabled]::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group {
|
||||
font-size: 0;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button {
|
||||
margin: 0;
|
||||
flex: 1 1;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
border-radius: 0;
|
||||
background: var(--p-button-secondary-background);
|
||||
color: var(--p-button-secondary-color);
|
||||
}
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button.-primary {
|
||||
background: var(--p-button-primary-background);
|
||||
color: var(--p-button-primary-color);
|
||||
}
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button::before, :not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button::after {
|
||||
border-radius: 0;
|
||||
}
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button svg {
|
||||
fill: currentColor;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:first-of-type,
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:first-of-type::before,
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:first-of-type::after {
|
||||
border-top-left-radius: 0.33rem;
|
||||
border-bottom-left-radius: 0.33rem;
|
||||
}
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:last-of-type,
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:last-of-type::before,
|
||||
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:last-of-type::after {
|
||||
border-top-right-radius: 0.33rem;
|
||||
border-bottom-right-radius: 0.33rem;
|
||||
}
|
||||
|
||||
.rgthree-dialog {
|
||||
outline: 0;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: #414141;
|
||||
color: #fff;
|
||||
box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.05), inset -1px -1px 0px rgba(0, 0, 0, 0.5), 2px 2px 20px rgb(0, 0, 0);
|
||||
max-width: 800px;
|
||||
box-sizing: border-box;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-size: 1rem;
|
||||
padding: 0;
|
||||
max-height: calc(100% - 32px);
|
||||
}
|
||||
.rgthree-dialog *, .rgthree-dialog *::before, .rgthree-dialog *::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.rgthree-dialog-container > * {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
.rgthree-dialog-container > *:first-child {
|
||||
padding-top: 16px;
|
||||
}
|
||||
.rgthree-dialog-container > *:last-child {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-iconed::after {
|
||||
content: "";
|
||||
font-size: 276px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
opacity: 0.15;
|
||||
display: block;
|
||||
width: 237px;
|
||||
overflow: hidden;
|
||||
height: 186px;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-iconed.-help::after {
|
||||
content: "🛟";
|
||||
}
|
||||
|
||||
.rgthree-dialog.-iconed.-settings::after {
|
||||
content: "⚙️";
|
||||
}
|
||||
|
||||
@media (max-width: 832px) {
|
||||
.rgthree-dialog {
|
||||
max-width: calc(100% - 32px);
|
||||
}
|
||||
}
|
||||
.rgthree-dialog-container-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-title > svg:first-child {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-title h2 {
|
||||
font-size: 1.375rem;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-title h2 small {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: normal;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-content {
|
||||
overflow: auto;
|
||||
max-height: calc(100vh - 200px); /* Arbitrary height to copensate for margin, title, and footer.*/
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-content p {
|
||||
font-size: 0.8125rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-content ul li p {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-content ul li p + p {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-content ul li ul {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-content p code {
|
||||
display: inline-block;
|
||||
padding: 2px 4px;
|
||||
margin: 0px 2px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 3px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.rgthree-dialog-container-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
body.rgthree-dialog-open > *:not(.rgthree-dialog):not(.rgthree-top-messages-container) {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.rgthree-menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.08s ease-in-out;
|
||||
color: #dde;
|
||||
background-color: #111;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 0 10px black !important;
|
||||
}
|
||||
.rgthree-menu > li {
|
||||
position: relative;
|
||||
padding: 4px 6px;
|
||||
z-index: 9999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.rgthree-menu > li[role=button] {
|
||||
background-color: var(--comfy-menu-bg) !important;
|
||||
color: var(--input-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.rgthree-menu > li[role=button]:hover {
|
||||
filter: brightness(155%);
|
||||
}
|
||||
.rgthree-menu[state^=measuring] {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
}
|
||||
.rgthree-menu[state=open] {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.rgthree-top-menu {
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
background: var(--content-bg);
|
||||
color: var(--content-fg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.rgthree-top-menu * {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
.rgthree-top-menu > li:not(#fakeid) {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.rgthree-top-menu > li:not(#fakeid) > button {
|
||||
cursor: pointer;
|
||||
padding: 8px 12px 8px 8px;
|
||||
width: 100%;
|
||||
text-align: start;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
.rgthree-top-menu > li:not(#fakeid) > button:hover {
|
||||
background-color: var(--comfy-input-bg);
|
||||
}
|
||||
.rgthree-top-menu > li:not(#fakeid) > button svg {
|
||||
height: 16px;
|
||||
width: auto;
|
||||
margin-inline-end: 0.6em;
|
||||
}
|
||||
.rgthree-top-menu > li:not(#fakeid) > button svg.github-star {
|
||||
fill: rgb(227, 179, 65);
|
||||
}
|
||||
.rgthree-top-menu > li:not(#fakeid).rgthree-message {
|
||||
min-height: 32px;
|
||||
}
|
||||
.rgthree-top-menu > li:not(#fakeid).rgthree-message > span {
|
||||
padding: 8px 12px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
}
|
||||
.rgthree-top-menu.-modal::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.0666666667);
|
||||
}
|
||||
|
||||
body.rgthree-modal-menu-open > *:not(.rgthree-menu):not(.rgthree-top-messages-container) {
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings fieldset {
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
padding: 0 12px 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings fieldset > legend {
|
||||
margin-left: 8px;
|
||||
padding: 0 8px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .formrow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .formrow + .formrow {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > label {
|
||||
flex: 1 1 auto;
|
||||
user-select: none;
|
||||
padding: 8px 12px 12px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > label span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > label small {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 0.6875rem;
|
||||
opacity: 0.75;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow {
|
||||
font-size: 0.9rem;
|
||||
border-top: 1px dotted rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow label {
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow:first-child:not(.-checked) ~ .fieldrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow span {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > .fieldrow-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
flex: 0 0 auto;
|
||||
width: 50%;
|
||||
max-width: 230px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow.-type-boolean > .fieldrow-value {
|
||||
max-width: 64px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow.-type-number input {
|
||||
width: 48px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow input[type=checkbox] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow fieldset.rgthree-checklist-group {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow fieldset.rgthree-checklist-group > span.rgthree-checklist-item {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
padding-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow fieldset.rgthree-checklist-group > span.rgthree-checklist-item input[type=checkbox] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow fieldset.rgthree-checklist-group > span.rgthree-checklist-item label {
|
||||
padding-left: 4px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rgthree-comfyui-settings-row div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.rgthree-comfyui-settings-row div svg {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.litegraph.litecontextmenu .litemenu-title .rgthree-contextmenu-title-rgthree-comfy,
|
||||
.litegraph.litecontextmenu .litemenu-entry.rgthree-contextmenu-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.litegraph.litecontextmenu .litemenu-title .rgthree-contextmenu-title-rgthree-comfy svg,
|
||||
.litegraph.litecontextmenu .litemenu-entry.rgthree-contextmenu-item svg {
|
||||
fill: currentColor;
|
||||
width: auto;
|
||||
height: 16px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.litegraph.litecontextmenu .litemenu-entry.rgthree-contextmenu-item svg.github-star {
|
||||
fill: rgb(227, 179, 65);
|
||||
}
|
||||
|
||||
.litegraph.litecontextmenu .litemenu-title .rgthree-contextmenu-title-rgthree-comfy,
|
||||
.litegraph.litecontextmenu .litemenu-entry.rgthree-contextmenu-label {
|
||||
color: #dde;
|
||||
background-color: #212121 !important;
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
padding: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.litegraph.litecontextmenu .litemenu-title .rgthree-contextmenu-title-rgthree-comfy {
|
||||
font-size: 1.1em;
|
||||
color: #fff;
|
||||
background-color: #090909 !important;
|
||||
justify-content: center;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
rgthree-progress-bar {
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 14px;
|
||||
font-size: 10px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.25), 0px 1px 0px rgba(255, 255, 255, 0.125);
|
||||
}
|
||||
|
||||
* ~ rgthree-progress-bar,
|
||||
.comfyui-body-bottom rgthree-progress-bar {
|
||||
box-shadow: 0px -1px 0px rgb(0, 0, 0), inset 0px 1px 0px rgba(255, 255, 255, 0.15), inset 0px -1px 0px rgba(0, 0, 0, 0.25), 0px 1px 0px rgba(255, 255, 255, 0.125);
|
||||
}
|
||||
|
||||
body:not([style*=grid]):not([class*=grid]) rgthree-progress-bar {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
bottom: auto;
|
||||
}
|
||||
body:not([style*=grid]):not([class*=grid]) rgthree-progress-bar.rgthree-pos-bottom {
|
||||
top: auto;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.rgthree-debug-keydowns {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1050;
|
||||
top: 3px;
|
||||
right: 8px;
|
||||
font-size: 10px;
|
||||
color: #fff;
|
||||
font-family: sans-serif;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.rgthree-comfy-about-badge-logo {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: url(/rgthree/logo.svg?bg=transparent&fg=%2393c5fd);
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
747
custom_nodes/rgthree-comfy/web/comfyui/rgthree.js
Normal file
747
custom_nodes/rgthree-comfy/web/comfyui/rgthree.js
Normal file
@@ -0,0 +1,747 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
|
||||
import { SERVICE as BOOKMARKS_SERVICE } from "./services/bookmarks_services.js";
|
||||
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
|
||||
import { WorkflowLinkFixer } from "../../rgthree/common/link_fixer.js";
|
||||
import { injectCss, wait } from "../../rgthree/common/shared_utils.js";
|
||||
import { replaceNode, waitForCanvas, waitForGraph } from "./utils.js";
|
||||
import { NodeTypesString, addRgthree, getNodeTypeStrings } from "./constants.js";
|
||||
import { RgthreeProgressBar } from "../../rgthree/common/progress_bar.js";
|
||||
import { RgthreeConfigDialog } from "./config.js";
|
||||
import { iconGear, iconNode, iconReplace, iconStarFilled, logoRgthree, } from "../../rgthree/common/media/svgs.js";
|
||||
import { createElement, queryAll, query } from "../../rgthree/common/utils_dom.js";
|
||||
export var LogLevel;
|
||||
(function (LogLevel) {
|
||||
LogLevel[LogLevel["IMPORTANT"] = 1] = "IMPORTANT";
|
||||
LogLevel[LogLevel["ERROR"] = 2] = "ERROR";
|
||||
LogLevel[LogLevel["WARN"] = 3] = "WARN";
|
||||
LogLevel[LogLevel["INFO"] = 4] = "INFO";
|
||||
LogLevel[LogLevel["DEBUG"] = 5] = "DEBUG";
|
||||
LogLevel[LogLevel["DEV"] = 6] = "DEV";
|
||||
})(LogLevel || (LogLevel = {}));
|
||||
const LogLevelKeyToLogLevel = {
|
||||
IMPORTANT: LogLevel.IMPORTANT,
|
||||
ERROR: LogLevel.ERROR,
|
||||
WARN: LogLevel.WARN,
|
||||
INFO: LogLevel.INFO,
|
||||
DEBUG: LogLevel.DEBUG,
|
||||
DEV: LogLevel.DEV,
|
||||
};
|
||||
const LogLevelToMethod = {
|
||||
[LogLevel.IMPORTANT]: "log",
|
||||
[LogLevel.ERROR]: "error",
|
||||
[LogLevel.WARN]: "warn",
|
||||
[LogLevel.INFO]: "info",
|
||||
[LogLevel.DEBUG]: "log",
|
||||
[LogLevel.DEV]: "log",
|
||||
};
|
||||
const LogLevelToCSS = {
|
||||
[LogLevel.IMPORTANT]: "font-weight: bold; color: blue;",
|
||||
[LogLevel.ERROR]: "",
|
||||
[LogLevel.WARN]: "",
|
||||
[LogLevel.INFO]: "font-style: italic; color: blue;",
|
||||
[LogLevel.DEBUG]: "font-style: italic; color: #444;",
|
||||
[LogLevel.DEV]: "color: #004b68;",
|
||||
};
|
||||
let GLOBAL_LOG_LEVEL = LogLevel.ERROR;
|
||||
const apiURL = api.apiURL;
|
||||
api.apiURL = function (route) {
|
||||
if (route.includes("rgthree/")) {
|
||||
return (this.api_base + "/" + route).replace(/\/\//g, "/");
|
||||
}
|
||||
return apiURL.apply(this, arguments);
|
||||
};
|
||||
const INVOKE_EXTENSIONS_BLOCKLIST = [
|
||||
{
|
||||
name: "Comfy.WidgetInputs",
|
||||
reason: "Major conflict with rgthree-comfy nodes' inputs causing instability and " +
|
||||
"repeated link disconnections.",
|
||||
},
|
||||
{
|
||||
name: "efficiency.widgethider",
|
||||
reason: "Overrides value getter before widget getter is prepared. Can be lifted if/when " +
|
||||
"https://github.com/jags111/efficiency-nodes-comfyui/pull/203 is pulled.",
|
||||
},
|
||||
];
|
||||
class Logger {
|
||||
log(level, message, ...args) {
|
||||
var _a;
|
||||
const [n, v] = this.logParts(level, message, ...args);
|
||||
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
|
||||
}
|
||||
logParts(level, message, ...args) {
|
||||
if (level <= GLOBAL_LOG_LEVEL) {
|
||||
const css = LogLevelToCSS[level] || "";
|
||||
if (level === LogLevel.DEV) {
|
||||
message = `🔧 ${message}`;
|
||||
}
|
||||
return [LogLevelToMethod[level], [`%c${message}`, css, ...args]];
|
||||
}
|
||||
return ["none", []];
|
||||
}
|
||||
}
|
||||
class LogSession {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.logger = new Logger();
|
||||
this.logsCache = {};
|
||||
}
|
||||
logParts(level, message, ...args) {
|
||||
message = `${this.name || ""}${message ? " " + message : ""}`;
|
||||
return this.logger.logParts(level, message, ...args);
|
||||
}
|
||||
logPartsOnceForTime(level, time, message, ...args) {
|
||||
message = `${this.name || ""}${message ? " " + message : ""}`;
|
||||
const cacheKey = `${level}:${message}`;
|
||||
const cacheEntry = this.logsCache[cacheKey];
|
||||
const now = +new Date();
|
||||
if (cacheEntry && cacheEntry.lastShownTime + time > now) {
|
||||
return ["none", []];
|
||||
}
|
||||
const parts = this.logger.logParts(level, message, ...args);
|
||||
if (console[parts[0]]) {
|
||||
this.logsCache[cacheKey] = this.logsCache[cacheKey] || {};
|
||||
this.logsCache[cacheKey].lastShownTime = now;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
debugParts(message, ...args) {
|
||||
return this.logParts(LogLevel.DEBUG, message, ...args);
|
||||
}
|
||||
infoParts(message, ...args) {
|
||||
return this.logParts(LogLevel.INFO, message, ...args);
|
||||
}
|
||||
warnParts(message, ...args) {
|
||||
return this.logParts(LogLevel.WARN, message, ...args);
|
||||
}
|
||||
errorParts(message, ...args) {
|
||||
return this.logParts(LogLevel.ERROR, message, ...args);
|
||||
}
|
||||
newSession(name) {
|
||||
return new LogSession(`${this.name}${name}`);
|
||||
}
|
||||
}
|
||||
class Rgthree extends EventTarget {
|
||||
constructor() {
|
||||
var _a, _b, _c, _d;
|
||||
super();
|
||||
this.api = api;
|
||||
this.settingsDialog = null;
|
||||
this.progressBarEl = null;
|
||||
this.queueNodeIds = null;
|
||||
this.version = CONFIG_SERVICE.getConfigValue("version");
|
||||
this.logger = new LogSession("[rgthree]");
|
||||
this.monitorBadLinksAlerted = false;
|
||||
this.monitorLinkTimeout = null;
|
||||
this.processingQueue = false;
|
||||
this.loadingApiJson = null;
|
||||
this.replacingReroute = null;
|
||||
this.processingMouseDown = false;
|
||||
this.processingMouseUp = false;
|
||||
this.processingMouseMove = false;
|
||||
this.lastCanvasMouseEvent = null;
|
||||
this.canvasCurrentlyCopyingToClipboard = false;
|
||||
this.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false;
|
||||
this.canvasCurrentlyPastingFromClipboard = false;
|
||||
this.canvasCurrentlyPastingFromClipboardWithMultipleNodes = false;
|
||||
this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = null;
|
||||
this.isMac = !!(((_a = navigator.platform) === null || _a === void 0 ? void 0 : _a.toLocaleUpperCase().startsWith("MAC")) ||
|
||||
((_c = (_b = navigator.userAgentData) === null || _b === void 0 ? void 0 : _b.platform) === null || _c === void 0 ? void 0 : _c.toLocaleUpperCase().startsWith("MAC")));
|
||||
const logLevel = (_d = LogLevelKeyToLogLevel[CONFIG_SERVICE.getConfigValue("log_level")]) !== null && _d !== void 0 ? _d : GLOBAL_LOG_LEVEL;
|
||||
this.setLogLevel(logLevel);
|
||||
this.initializeGraphAndCanvasHooks();
|
||||
this.initializeComfyUIHooks();
|
||||
this.initializeContextMenu();
|
||||
this.rgthreeCssPromise = injectCss("extensions/rgthree-comfy/rgthree.css");
|
||||
this.initializeProgressBar();
|
||||
CONFIG_SERVICE.addEventListener("config-change", ((e) => {
|
||||
var _a, _b;
|
||||
if ((_b = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.key) === null || _b === void 0 ? void 0 : _b.includes("features.progress_bar")) {
|
||||
this.initializeProgressBar();
|
||||
}
|
||||
}));
|
||||
if (CONFIG_SERVICE.getConfigValue("debug.keys_down.enabled")) {
|
||||
const elDebugKeydowns = createElement("div.rgthree-debug-keydowns", {
|
||||
parent: document.body,
|
||||
});
|
||||
const updateDebugKeyDown = () => {
|
||||
elDebugKeydowns.innerText = Object.keys(KEY_EVENT_SERVICE.downKeys).join(" ");
|
||||
};
|
||||
KEY_EVENT_SERVICE.addEventListener("keydown", updateDebugKeyDown);
|
||||
KEY_EVENT_SERVICE.addEventListener("keyup", updateDebugKeyDown);
|
||||
}
|
||||
}
|
||||
async initializeProgressBar() {
|
||||
var _a;
|
||||
if (CONFIG_SERVICE.getConfigValue("features.progress_bar.enabled")) {
|
||||
await this.rgthreeCssPromise;
|
||||
if (!this.progressBarEl) {
|
||||
this.progressBarEl = RgthreeProgressBar.create();
|
||||
this.progressBarEl.setAttribute("title", "Progress Bar by rgthree. right-click for rgthree menu.");
|
||||
this.progressBarEl.addEventListener("contextmenu", async (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
this.progressBarEl.addEventListener("pointerdown", async (e) => {
|
||||
var _a;
|
||||
LiteGraph.closeAllContextMenus();
|
||||
if (e.button == 2) {
|
||||
const canvas = await waitForCanvas();
|
||||
new LiteGraph.ContextMenu(this.getRgthreeIContextMenuValues(), {
|
||||
title: `<div class="rgthree-contextmenu-item rgthree-contextmenu-title-rgthree-comfy">${logoRgthree} rgthree-comfy</div>`,
|
||||
left: e.clientX,
|
||||
top: 5,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (e.button == 0) {
|
||||
const nodeId = (_a = this.progressBarEl) === null || _a === void 0 ? void 0 : _a.currentNodeId;
|
||||
if (nodeId) {
|
||||
const [canvas, graph] = await Promise.all([waitForCanvas(), waitForGraph()]);
|
||||
const node = graph.getNodeById(Number(nodeId));
|
||||
if (node) {
|
||||
canvas.centerOnNode(node);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
const isUpdatedComfyBodyClasses = !!query(".comfyui-body-top");
|
||||
const position = CONFIG_SERVICE.getConfigValue("features.progress_bar.position");
|
||||
this.progressBarEl.classList.toggle("rgthree-pos-bottom", position === "bottom");
|
||||
if (isUpdatedComfyBodyClasses) {
|
||||
if (position === "bottom") {
|
||||
query(".comfyui-body-bottom").appendChild(this.progressBarEl);
|
||||
}
|
||||
else {
|
||||
query(".comfyui-body-top").appendChild(this.progressBarEl);
|
||||
}
|
||||
}
|
||||
else {
|
||||
document.body.appendChild(this.progressBarEl);
|
||||
}
|
||||
const height = CONFIG_SERVICE.getConfigValue("features.progress_bar.height") || 14;
|
||||
this.progressBarEl.style.height = `${height}px`;
|
||||
const fontSize = Math.max(10, Number(height) - 10);
|
||||
this.progressBarEl.style.fontSize = `${fontSize}px`;
|
||||
this.progressBarEl.style.fontWeight = fontSize <= 12 ? "bold" : "normal";
|
||||
}
|
||||
else {
|
||||
(_a = this.progressBarEl) === null || _a === void 0 ? void 0 : _a.remove();
|
||||
}
|
||||
}
|
||||
async initializeGraphAndCanvasHooks() {
|
||||
const rgthree = this;
|
||||
const graphSerialize = LGraph.prototype.serialize;
|
||||
LGraph.prototype.serialize = function () {
|
||||
const response = graphSerialize.apply(this, [...arguments]);
|
||||
rgthree.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = response;
|
||||
return response;
|
||||
};
|
||||
const processMouseDown = LGraphCanvas.prototype.processMouseDown;
|
||||
LGraphCanvas.prototype.processMouseDown = function (e) {
|
||||
rgthree.processingMouseDown = true;
|
||||
const returnVal = processMouseDown.apply(this, [...arguments]);
|
||||
rgthree.dispatchCustomEvent("on-process-mouse-down", { originalEvent: e });
|
||||
rgthree.processingMouseDown = false;
|
||||
return returnVal;
|
||||
};
|
||||
const adjustMouseEvent = LGraphCanvas.prototype.adjustMouseEvent;
|
||||
LGraphCanvas.prototype.adjustMouseEvent = function (e) {
|
||||
adjustMouseEvent.apply(this, [...arguments]);
|
||||
rgthree.lastCanvasMouseEvent = e;
|
||||
};
|
||||
const copyToClipboard = LGraphCanvas.prototype.copyToClipboard;
|
||||
LGraphCanvas.prototype.copyToClipboard = function (items) {
|
||||
rgthree.canvasCurrentlyCopyingToClipboard = true;
|
||||
rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes =
|
||||
Object.values(items || this.selected_nodes || []).length > 1;
|
||||
const value = copyToClipboard.apply(this, [...arguments]);
|
||||
rgthree.canvasCurrentlyCopyingToClipboard = false;
|
||||
rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false;
|
||||
return value;
|
||||
};
|
||||
const pasteFromClipboard = LGraphCanvas.prototype.pasteFromClipboard;
|
||||
LGraphCanvas.prototype.pasteFromClipboard = function (...args) {
|
||||
rgthree.canvasCurrentlyPastingFromClipboard = true;
|
||||
pasteFromClipboard.apply(this, [...arguments]);
|
||||
rgthree.canvasCurrentlyPastingFromClipboard = false;
|
||||
};
|
||||
const onGroupAdd = LGraphCanvas.onGroupAdd;
|
||||
LGraphCanvas.onGroupAdd = function (...args) {
|
||||
const graph = app.canvas.getCurrentGraph();
|
||||
onGroupAdd.apply(this, [...args]);
|
||||
LGraphCanvas.onShowPropertyEditor({}, null, null, null, graph._groups[graph._groups.length - 1]);
|
||||
};
|
||||
}
|
||||
async invokeExtensionsAsync(method, ...args) {
|
||||
var _a;
|
||||
const comfyapp = app;
|
||||
if (CONFIG_SERVICE.getConfigValue("features.invoke_extensions_async.node_created") === false) {
|
||||
const [m, a] = this.logParts(LogLevel.INFO, `Skipping invokeExtensionsAsync for applicable rgthree-comfy nodes`);
|
||||
(_a = console[m]) === null || _a === void 0 ? void 0 : _a.call(console, ...a);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return await Promise.all(comfyapp.extensions.map(async (ext) => {
|
||||
var _a, _b;
|
||||
if (ext === null || ext === void 0 ? void 0 : ext[method]) {
|
||||
try {
|
||||
const blocked = INVOKE_EXTENSIONS_BLOCKLIST.find((block) => ext.name.toLowerCase().startsWith(block.name.toLowerCase()));
|
||||
if (blocked) {
|
||||
const [n, v] = this.logger.logPartsOnceForTime(LogLevel.WARN, 5000, `Blocked extension '${ext.name}' method '${method}' for rgthree-nodes because: ${blocked.reason}`);
|
||||
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return await ext[method](...args, comfyapp);
|
||||
}
|
||||
catch (error) {
|
||||
const [n, v] = this.logParts(LogLevel.ERROR, `Error calling extension '${ext.name}' method '${method}' for rgthree-node.`, { error }, { extension: ext }, { args });
|
||||
(_b = console[n]) === null || _b === void 0 ? void 0 : _b.call(console, ...v);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
dispatchCustomEvent(event, detail) {
|
||||
if (detail != null) {
|
||||
return this.dispatchEvent(new CustomEvent(event, { detail }));
|
||||
}
|
||||
return this.dispatchEvent(new CustomEvent(event));
|
||||
}
|
||||
async initializeContextMenu() {
|
||||
const that = this;
|
||||
setTimeout(async () => {
|
||||
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
|
||||
let existingOptions = getCanvasMenuOptions.apply(this, [...args]);
|
||||
const options = [];
|
||||
options.push(null);
|
||||
options.push(null);
|
||||
options.push(null);
|
||||
options.push({
|
||||
content: logoRgthree + `rgthree-comfy`,
|
||||
className: "rgthree-contextmenu-item rgthree-contextmenu-main-item-rgthree-comfy",
|
||||
submenu: {
|
||||
options: that.getRgthreeIContextMenuValues(),
|
||||
},
|
||||
});
|
||||
options.push(null);
|
||||
options.push(null);
|
||||
let idx = null;
|
||||
idx = idx || existingOptions.findIndex((o) => { var _a, _b; return (_b = (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, "Queue Group"); }) + 1;
|
||||
idx =
|
||||
idx || existingOptions.findIndex((o) => { var _a, _b; return (_b = (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, "Queue Selected"); }) + 1;
|
||||
idx = idx || existingOptions.findIndex((o) => { var _a, _b; return (_b = (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, "Convert to Group"); });
|
||||
idx = idx || existingOptions.findIndex((o) => { var _a, _b; return (_b = (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, "Arrange ("); });
|
||||
idx = idx || existingOptions.findIndex((o) => !o) + 1;
|
||||
idx = idx || 3;
|
||||
existingOptions.splice(idx, 0, ...options);
|
||||
for (let i = existingOptions.length; i > 0; i--) {
|
||||
if (existingOptions[i] === null && existingOptions[i + 1] === null) {
|
||||
existingOptions.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return existingOptions;
|
||||
};
|
||||
}, 1016);
|
||||
}
|
||||
getRgthreeIContextMenuValues() {
|
||||
const [canvas, graph] = [app.canvas, app.canvas.getCurrentGraph()];
|
||||
const selectedNodes = Object.values(canvas.selected_nodes || {});
|
||||
let rerouteNodes = [];
|
||||
if (selectedNodes.length) {
|
||||
rerouteNodes = selectedNodes.filter((n) => n.type === "Reroute");
|
||||
}
|
||||
else {
|
||||
rerouteNodes = graph._nodes.filter((n) => n.type == "Reroute");
|
||||
}
|
||||
const rerouteLabel = selectedNodes.length ? "selected" : "all";
|
||||
const showBookmarks = CONFIG_SERVICE.getFeatureValue("menu_bookmarks.enabled");
|
||||
const bookmarkMenuItems = showBookmarks ? getBookmarks() : [];
|
||||
return [
|
||||
{
|
||||
content: "Nodes",
|
||||
disabled: true,
|
||||
className: "rgthree-contextmenu-item rgthree-contextmenu-label",
|
||||
},
|
||||
{
|
||||
content: iconNode + "All",
|
||||
className: "rgthree-contextmenu-item",
|
||||
has_submenu: true,
|
||||
submenu: {
|
||||
options: getNodeTypeStrings(),
|
||||
callback: (value, options, event) => {
|
||||
const node = LiteGraph.createNode(addRgthree(value));
|
||||
if (node) {
|
||||
node.pos = [
|
||||
rgthree.lastCanvasMouseEvent.canvasX,
|
||||
rgthree.lastCanvasMouseEvent.canvasY,
|
||||
];
|
||||
canvas.graph.add(node);
|
||||
canvas.selectNode(node);
|
||||
graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
},
|
||||
extra: { rgthree_doNotNest: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Actions",
|
||||
disabled: true,
|
||||
className: "rgthree-contextmenu-item rgthree-contextmenu-label",
|
||||
},
|
||||
{
|
||||
content: iconGear + "Settings (rgthree-comfy)",
|
||||
disabled: !!this.settingsDialog,
|
||||
className: "rgthree-contextmenu-item",
|
||||
callback: (...args) => {
|
||||
this.settingsDialog = new RgthreeConfigDialog().show();
|
||||
this.settingsDialog.addEventListener("close", (e) => {
|
||||
this.settingsDialog = null;
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
content: iconReplace + ` Convert ${rerouteLabel} Reroutes`,
|
||||
disabled: !rerouteNodes.length,
|
||||
className: "rgthree-contextmenu-item",
|
||||
callback: (...args) => {
|
||||
const msg = `Convert ${rerouteLabel} ComfyUI Reroutes to Reroute (rgthree) nodes? \n` +
|
||||
`(First save a copy of your workflow & check reroute connections afterwards)`;
|
||||
if (!window.confirm(msg)) {
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
for (const node of [...rerouteNodes]) {
|
||||
if (node.type == "Reroute") {
|
||||
this.replacingReroute = node.id;
|
||||
await replaceNode(node, NodeTypesString.REROUTE);
|
||||
this.replacingReroute = null;
|
||||
}
|
||||
}
|
||||
})();
|
||||
},
|
||||
},
|
||||
...bookmarkMenuItems,
|
||||
{
|
||||
content: "More...",
|
||||
disabled: true,
|
||||
className: "rgthree-contextmenu-item rgthree-contextmenu-label",
|
||||
},
|
||||
{
|
||||
content: iconStarFilled + "Star on Github",
|
||||
className: "rgthree-contextmenu-item rgthree-contextmenu-github",
|
||||
callback: (...args) => {
|
||||
window.open("https://github.com/rgthree/rgthree-comfy", "_blank");
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
async queueOutputNodes(nodes) {
|
||||
var _a;
|
||||
const nodeIds = nodes.map((n) => n.id);
|
||||
try {
|
||||
this.queueNodeIds = nodeIds;
|
||||
await app.queuePrompt(0);
|
||||
}
|
||||
catch (e) {
|
||||
const [n, v] = this.logParts(LogLevel.ERROR, `There was an error queuing nodes ${nodeIds}`, e);
|
||||
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
|
||||
}
|
||||
finally {
|
||||
this.queueNodeIds = null;
|
||||
}
|
||||
}
|
||||
recursiveAddNodes(nodeId, oldOutput, newOutput) {
|
||||
let currentId = nodeId;
|
||||
let currentNode = oldOutput[currentId];
|
||||
if (newOutput[currentId] == null) {
|
||||
newOutput[currentId] = currentNode;
|
||||
for (const inputValue of Object.values(currentNode.inputs || [])) {
|
||||
if (Array.isArray(inputValue)) {
|
||||
this.recursiveAddNodes(inputValue[0], oldOutput, newOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
return newOutput;
|
||||
}
|
||||
initializeComfyUIHooks() {
|
||||
const rgthree = this;
|
||||
const queuePrompt = app.queuePrompt;
|
||||
app.queuePrompt = async function (number, batchCount) {
|
||||
rgthree.processingQueue = true;
|
||||
rgthree.dispatchCustomEvent("queue");
|
||||
try {
|
||||
return await queuePrompt.apply(app, [...arguments]);
|
||||
}
|
||||
finally {
|
||||
rgthree.processingQueue = false;
|
||||
rgthree.dispatchCustomEvent("queue-end");
|
||||
}
|
||||
};
|
||||
const loadApiJson = app.loadApiJson;
|
||||
app.loadApiJson = async function (apiData, fileName) {
|
||||
rgthree.loadingApiJson = apiData;
|
||||
try {
|
||||
loadApiJson.apply(app, [...arguments]);
|
||||
}
|
||||
finally {
|
||||
rgthree.loadingApiJson = null;
|
||||
}
|
||||
};
|
||||
const graphToPrompt = app.graphToPrompt;
|
||||
app.graphToPrompt = async function () {
|
||||
rgthree.dispatchCustomEvent("graph-to-prompt");
|
||||
let promise = graphToPrompt.apply(app, [...arguments]);
|
||||
await promise;
|
||||
rgthree.dispatchCustomEvent("graph-to-prompt-end");
|
||||
return promise;
|
||||
};
|
||||
const apiQueuePrompt = api.queuePrompt;
|
||||
api.queuePrompt = async function (index, prompt, ...args) {
|
||||
var _a;
|
||||
if (((_a = rgthree.queueNodeIds) === null || _a === void 0 ? void 0 : _a.length) && prompt.output) {
|
||||
const oldOutput = prompt.output;
|
||||
let newOutput = {};
|
||||
for (const queueNodeId of rgthree.queueNodeIds) {
|
||||
rgthree.recursiveAddNodes(String(queueNodeId), oldOutput, newOutput);
|
||||
}
|
||||
prompt.output = newOutput;
|
||||
}
|
||||
rgthree.dispatchCustomEvent("comfy-api-queue-prompt-before", {
|
||||
workflow: prompt.workflow,
|
||||
output: prompt.output,
|
||||
});
|
||||
const response = apiQueuePrompt.apply(app, [index, prompt, ...args]);
|
||||
rgthree.dispatchCustomEvent("comfy-api-queue-prompt-end");
|
||||
return response;
|
||||
};
|
||||
const clean = app.clean;
|
||||
app.clean = function () {
|
||||
rgthree.clearAllMessages();
|
||||
clean && clean.apply(app, [...arguments]);
|
||||
};
|
||||
const loadGraphData = app.loadGraphData;
|
||||
app.loadGraphData = function (graph) {
|
||||
if (rgthree.monitorLinkTimeout) {
|
||||
clearTimeout(rgthree.monitorLinkTimeout);
|
||||
rgthree.monitorLinkTimeout = null;
|
||||
}
|
||||
rgthree.clearAllMessages();
|
||||
let graphCopy;
|
||||
try {
|
||||
graphCopy = JSON.parse(JSON.stringify(graph));
|
||||
}
|
||||
catch (e) {
|
||||
graphCopy = null;
|
||||
}
|
||||
setTimeout(() => {
|
||||
var _a, _b, _c;
|
||||
const wasLoadingAborted = (_b = (_a = document
|
||||
.querySelector(".comfy-modal-content")) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.includes("Loading aborted due");
|
||||
const graphToUse = wasLoadingAborted ? graphCopy || graph : app.graph;
|
||||
const fixer = WorkflowLinkFixer.create(graphToUse);
|
||||
const fixBadLinksResult = fixer.check();
|
||||
if (fixBadLinksResult.hasBadLinks) {
|
||||
const [n, v] = rgthree.logParts(LogLevel.WARN, `The workflow you've loaded has corrupt linking data. Open ${new URL(location.href).origin}/rgthree/link_fixer to try to fix.`);
|
||||
(_c = console[n]) === null || _c === void 0 ? void 0 : _c.call(console, ...v);
|
||||
if (CONFIG_SERVICE.getConfigValue("features.show_alerts_for_corrupt_workflows")) {
|
||||
rgthree.showMessage({
|
||||
id: "bad-links",
|
||||
type: "warn",
|
||||
message: "The workflow you've loaded has corrupt linking data that may be able to be fixed.",
|
||||
actions: [
|
||||
{
|
||||
label: "Open fixer",
|
||||
href: "/rgthree/link_fixer",
|
||||
},
|
||||
{
|
||||
label: "Fix in place",
|
||||
href: "/rgthree/link_fixer",
|
||||
callback: (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (confirm("This will attempt to fix in place. Please make sure to have a saved copy of your workflow.")) {
|
||||
try {
|
||||
const fixBadLinksResult = fixer.fix();
|
||||
if (!fixBadLinksResult.hasBadLinks) {
|
||||
rgthree.hideMessage("bad-links");
|
||||
alert("Success! It's possible some valid links may have been affected. Please check and verify your workflow.");
|
||||
wasLoadingAborted && app.loadGraphData(fixBadLinksResult.graph);
|
||||
if (CONFIG_SERVICE.getConfigValue("features.monitor_for_corrupt_links") ||
|
||||
CONFIG_SERVICE.getConfigValue("features.monitor_bad_links")) {
|
||||
rgthree.monitorLinkTimeout = setTimeout(() => {
|
||||
rgthree.monitorBadLinks();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
alert("Unsuccessful at fixing corrupt data. :(");
|
||||
rgthree.hideMessage("bad-links");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (CONFIG_SERVICE.getConfigValue("features.monitor_for_corrupt_links") ||
|
||||
CONFIG_SERVICE.getConfigValue("features.monitor_bad_links")) {
|
||||
rgthree.monitorLinkTimeout = setTimeout(() => {
|
||||
rgthree.monitorBadLinks();
|
||||
}, 5000);
|
||||
}
|
||||
}, 100);
|
||||
return loadGraphData && loadGraphData.apply(app, [...arguments]);
|
||||
};
|
||||
}
|
||||
getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node) {
|
||||
var _a, _b, _c;
|
||||
return ((_c = (_b = (_a = this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff) === null || _a === void 0 ? void 0 : _a.nodes) === null || _b === void 0 ? void 0 : _b.find((n) => n.id === node.id)) !== null && _c !== void 0 ? _c : null);
|
||||
}
|
||||
async showMessage(data) {
|
||||
let container = document.querySelector(".rgthree-top-messages-container");
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.classList.add("rgthree-top-messages-container");
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
const dialogs = queryAll("dialog[open]");
|
||||
if (dialogs.length) {
|
||||
let dialog = dialogs[dialogs.length - 1];
|
||||
dialog.appendChild(container);
|
||||
dialog.addEventListener("close", (e) => {
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
}
|
||||
await this.hideMessage(data.id);
|
||||
const messageContainer = document.createElement("div");
|
||||
messageContainer.setAttribute("type", data.type || "info");
|
||||
const message = document.createElement("span");
|
||||
message.innerHTML = data.message;
|
||||
messageContainer.appendChild(message);
|
||||
for (let a = 0; a < (data.actions || []).length; a++) {
|
||||
const action = data.actions[a];
|
||||
if (a > 0) {
|
||||
const sep = document.createElement("span");
|
||||
sep.innerHTML = " | ";
|
||||
messageContainer.appendChild(sep);
|
||||
}
|
||||
const actionEl = document.createElement("a");
|
||||
actionEl.innerText = action.label;
|
||||
if (action.href) {
|
||||
actionEl.target = "_blank";
|
||||
actionEl.href = action.href;
|
||||
}
|
||||
if (action.callback) {
|
||||
actionEl.onclick = (e) => {
|
||||
return action.callback(e);
|
||||
};
|
||||
}
|
||||
messageContainer.appendChild(actionEl);
|
||||
}
|
||||
const messageAnimContainer = document.createElement("div");
|
||||
messageAnimContainer.setAttribute("msg-id", data.id);
|
||||
messageAnimContainer.appendChild(messageContainer);
|
||||
container.appendChild(messageAnimContainer);
|
||||
await wait(64);
|
||||
messageAnimContainer.style.marginTop = `-${messageAnimContainer.offsetHeight}px`;
|
||||
await wait(64);
|
||||
messageAnimContainer.classList.add("-show");
|
||||
if (data.timeout) {
|
||||
await wait(data.timeout);
|
||||
this.hideMessage(data.id);
|
||||
}
|
||||
}
|
||||
async hideMessage(id) {
|
||||
const msg = document.querySelector(`.rgthree-top-messages-container > [msg-id="${id}"]`);
|
||||
if (msg === null || msg === void 0 ? void 0 : msg.classList.contains("-show")) {
|
||||
msg.classList.remove("-show");
|
||||
await wait(750);
|
||||
}
|
||||
msg && msg.remove();
|
||||
}
|
||||
async clearAllMessages() {
|
||||
let container = document.querySelector(".rgthree-top-messages-container");
|
||||
container && (container.innerHTML = "");
|
||||
}
|
||||
setLogLevel(level) {
|
||||
if (typeof level === "string") {
|
||||
level = LogLevelKeyToLogLevel[CONFIG_SERVICE.getConfigValue("log_level")];
|
||||
}
|
||||
if (level != null) {
|
||||
GLOBAL_LOG_LEVEL = level;
|
||||
}
|
||||
}
|
||||
logParts(level, message, ...args) {
|
||||
return this.logger.logParts(level, message, ...args);
|
||||
}
|
||||
newLogSession(name) {
|
||||
return this.logger.newSession(name);
|
||||
}
|
||||
isDebugMode() {
|
||||
if (window.location.href.includes("rgthree-debug=false")) {
|
||||
return false;
|
||||
}
|
||||
return GLOBAL_LOG_LEVEL >= LogLevel.DEBUG || window.location.href.includes("rgthree-debug");
|
||||
}
|
||||
isDevMode() {
|
||||
if (window.location.href.includes("rgthree-dev=false")) {
|
||||
return false;
|
||||
}
|
||||
return GLOBAL_LOG_LEVEL >= LogLevel.DEV || window.location.href.includes("rgthree-dev");
|
||||
}
|
||||
monitorBadLinks() {
|
||||
const badLinksFound = WorkflowLinkFixer.create(app.graph).check();
|
||||
if (badLinksFound.hasBadLinks && !this.monitorBadLinksAlerted) {
|
||||
this.monitorBadLinksAlerted = true;
|
||||
alert(`Problematic links just found in live data. Can you save your workflow and file a bug with ` +
|
||||
`the last few steps you took to trigger this at ` +
|
||||
`https://github.com/rgthree/rgthree-comfy/issues. Thank you!`);
|
||||
}
|
||||
else if (!badLinksFound.hasBadLinks) {
|
||||
this.monitorBadLinksAlerted = false;
|
||||
}
|
||||
this.monitorLinkTimeout = setTimeout(() => {
|
||||
this.monitorBadLinks();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
function getBookmarks() {
|
||||
const bookmarks = BOOKMARKS_SERVICE.getCurrentBookmarks();
|
||||
const bookmarkItems = bookmarks.map((n) => ({
|
||||
content: `[${n.shortcutKey}] ${n.title}`,
|
||||
className: "rgthree-contextmenu-item",
|
||||
callback: () => {
|
||||
n.canvasToBookmark();
|
||||
},
|
||||
}));
|
||||
return !bookmarkItems.length
|
||||
? []
|
||||
: [
|
||||
{
|
||||
content: "🔖 Bookmarks",
|
||||
disabled: true,
|
||||
className: "rgthree-contextmenu-item rgthree-contextmenu-label",
|
||||
},
|
||||
...bookmarkItems,
|
||||
];
|
||||
}
|
||||
export const rgthree = new Rgthree();
|
||||
window.rgthree = rgthree;
|
||||
app.registerExtension({
|
||||
name: "Comfy.RgthreeComfy",
|
||||
aboutPageBadges: [
|
||||
{
|
||||
label: `rgthree-comfy v${rgthree.version}`,
|
||||
url: "https://github.com/rgthree/rgthree-comfy",
|
||||
icon: "rgthree-comfy-about-badge-logo",
|
||||
},
|
||||
],
|
||||
});
|
||||
202
custom_nodes/rgthree-comfy/web/comfyui/seed.js
Normal file
202
custom_nodes/rgthree-comfy/web/comfyui/seed.js
Normal file
@@ -0,0 +1,202 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
import { RgthreeBaseServerNode } from "./base_node.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { addConnectionLayoutSupport } from "./utils.js";
|
||||
import { NodeTypesString } from "./constants.js";
|
||||
const LAST_SEED_BUTTON_LABEL = "♻️ (Use Last Queued Seed)";
|
||||
const SPECIAL_SEED_RANDOM = -1;
|
||||
const SPECIAL_SEED_INCREMENT = -2;
|
||||
const SPECIAL_SEED_DECREMENT = -3;
|
||||
const SPECIAL_SEEDS = [SPECIAL_SEED_RANDOM, SPECIAL_SEED_INCREMENT, SPECIAL_SEED_DECREMENT];
|
||||
class RgthreeSeed extends RgthreeBaseServerNode {
|
||||
constructor(title = RgthreeSeed.title) {
|
||||
super(title);
|
||||
this.serialize_widgets = true;
|
||||
this.logger = rgthree.newLogSession(`[Seed]`);
|
||||
this.lastSeed = undefined;
|
||||
this.serializedCtx = {};
|
||||
this.lastSeedValue = null;
|
||||
this.handleApiHijackingBound = this.handleApiHijacking.bind(this);
|
||||
this.properties["randomMax"] = 1125899906842624;
|
||||
this.properties["randomMin"] = 0;
|
||||
rgthree.addEventListener("comfy-api-queue-prompt-before", this.handleApiHijackingBound);
|
||||
}
|
||||
onPropertyChanged(prop, value, prevValue) {
|
||||
if (prop === 'randomMax') {
|
||||
this.properties["randomMax"] = Math.min(1125899906842624, Number(value));
|
||||
}
|
||||
else if (prop === 'randomMin') {
|
||||
this.properties["randomMin"] = Math.max(-1125899906842624, Number(value));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
onRemoved() {
|
||||
rgthree.addEventListener("comfy-api-queue-prompt-before", this.handleApiHijackingBound);
|
||||
}
|
||||
configure(info) {
|
||||
var _a;
|
||||
super.configure(info);
|
||||
if ((_a = this.properties) === null || _a === void 0 ? void 0 : _a["showLastSeed"]) {
|
||||
this.addLastSeedValue();
|
||||
}
|
||||
}
|
||||
async handleAction(action) {
|
||||
if (action === "Randomize Each Time") {
|
||||
this.seedWidget.value = SPECIAL_SEED_RANDOM;
|
||||
}
|
||||
else if (action === "Use Last Queued Seed") {
|
||||
this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value;
|
||||
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
|
||||
this.lastSeedButton.disabled = true;
|
||||
}
|
||||
}
|
||||
onNodeCreated() {
|
||||
var _a;
|
||||
(_a = super.onNodeCreated) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||
for (const [i, w] of this.widgets.entries()) {
|
||||
if (w.name === "seed") {
|
||||
this.seedWidget = w;
|
||||
this.seedWidget.value = SPECIAL_SEED_RANDOM;
|
||||
}
|
||||
else if (w.name === "control_after_generate") {
|
||||
this.widgets.splice(i, 1);
|
||||
}
|
||||
}
|
||||
this.addWidget("button", "🎲 Randomize Each Time", "", () => {
|
||||
this.seedWidget.value = SPECIAL_SEED_RANDOM;
|
||||
}, { serialize: false });
|
||||
this.addWidget("button", "🎲 New Fixed Random", "", () => {
|
||||
this.seedWidget.value = this.generateRandomSeed();
|
||||
}, { serialize: false });
|
||||
this.lastSeedButton = this.addWidget("button", LAST_SEED_BUTTON_LABEL, "", () => {
|
||||
this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value;
|
||||
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
|
||||
this.lastSeedButton.disabled = true;
|
||||
}, { width: 50, serialize: false });
|
||||
this.lastSeedButton.disabled = true;
|
||||
}
|
||||
generateRandomSeed() {
|
||||
let step = this.seedWidget.options.step || 1;
|
||||
const randomMin = Number(this.properties['randomMin'] || 0);
|
||||
const randomMax = Number(this.properties['randomMax'] || 1125899906842624);
|
||||
const randomRange = (randomMax - randomMin) / (step / 10);
|
||||
let seed = Math.floor(Math.random() * randomRange) * (step / 10) + randomMin;
|
||||
if (SPECIAL_SEEDS.includes(seed)) {
|
||||
seed = 0;
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
getExtraMenuOptions(canvas, options) {
|
||||
var _a;
|
||||
(_a = super.getExtraMenuOptions) === null || _a === void 0 ? void 0 : _a.apply(this, [...arguments]);
|
||||
options.splice(options.length - 1, 0, {
|
||||
content: "Show/Hide Last Seed Value",
|
||||
callback: (_value, _options, _event, _parentMenu, _node) => {
|
||||
this.properties["showLastSeed"] = !this.properties["showLastSeed"];
|
||||
if (this.properties["showLastSeed"]) {
|
||||
this.addLastSeedValue();
|
||||
}
|
||||
else {
|
||||
this.removeLastSeedValue();
|
||||
}
|
||||
},
|
||||
});
|
||||
return [];
|
||||
}
|
||||
addLastSeedValue() {
|
||||
if (this.lastSeedValue)
|
||||
return;
|
||||
this.lastSeedValue = ComfyWidgets["STRING"](this, "last_seed", ["STRING", { multiline: true }], app).widget;
|
||||
this.lastSeedValue.inputEl.readOnly = true;
|
||||
this.lastSeedValue.inputEl.style.fontSize = "0.75rem";
|
||||
this.lastSeedValue.inputEl.style.textAlign = "center";
|
||||
this.computeSize();
|
||||
}
|
||||
removeLastSeedValue() {
|
||||
if (!this.lastSeedValue)
|
||||
return;
|
||||
this.lastSeedValue.inputEl.remove();
|
||||
this.widgets.splice(this.widgets.indexOf(this.lastSeedValue), 1);
|
||||
this.lastSeedValue = null;
|
||||
this.computeSize();
|
||||
}
|
||||
handleApiHijacking(e) {
|
||||
var _a, _b, _c, _d;
|
||||
if (this.mode === LiteGraph.NEVER || this.mode === 4) {
|
||||
return;
|
||||
}
|
||||
const workflow = e.detail.workflow;
|
||||
const output = e.detail.output;
|
||||
let workflowNode = (_b = (_a = workflow === null || workflow === void 0 ? void 0 : workflow.nodes) === null || _a === void 0 ? void 0 : _a.find((n) => n.id === this.id)) !== null && _b !== void 0 ? _b : null;
|
||||
let outputInputs = (_c = output === null || output === void 0 ? void 0 : output[this.id]) === null || _c === void 0 ? void 0 : _c.inputs;
|
||||
if (!workflowNode ||
|
||||
!outputInputs ||
|
||||
outputInputs[this.seedWidget.name || "seed"] === undefined) {
|
||||
const [n, v] = this.logger.warnParts(`Node ${this.id} not found in prompt data sent to server. This may be fine if only ` +
|
||||
`queuing part of the workflow. If not, then this could be a bug.`);
|
||||
(_d = console[n]) === null || _d === void 0 ? void 0 : _d.call(console, ...v);
|
||||
return;
|
||||
}
|
||||
const seedToUse = this.getSeedToUse();
|
||||
const seedWidgetndex = this.widgets.indexOf(this.seedWidget);
|
||||
workflowNode.widgets_values[seedWidgetndex] = seedToUse;
|
||||
outputInputs[this.seedWidget.name || "seed"] = seedToUse;
|
||||
this.lastSeed = seedToUse;
|
||||
if (seedToUse != this.seedWidget.value) {
|
||||
this.lastSeedButton.name = `♻️ ${this.lastSeed}`;
|
||||
this.lastSeedButton.disabled = false;
|
||||
}
|
||||
else {
|
||||
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
|
||||
this.lastSeedButton.disabled = true;
|
||||
}
|
||||
if (this.lastSeedValue) {
|
||||
this.lastSeedValue.value = `Last Seed: ${this.lastSeed}`;
|
||||
}
|
||||
}
|
||||
getSeedToUse() {
|
||||
const inputSeed = Number(this.seedWidget.value);
|
||||
let seedToUse = null;
|
||||
if (SPECIAL_SEEDS.includes(inputSeed)) {
|
||||
if (typeof this.lastSeed === "number" && !SPECIAL_SEEDS.includes(this.lastSeed)) {
|
||||
if (inputSeed === SPECIAL_SEED_INCREMENT) {
|
||||
seedToUse = this.lastSeed + 1;
|
||||
}
|
||||
else if (inputSeed === SPECIAL_SEED_DECREMENT) {
|
||||
seedToUse = this.lastSeed - 1;
|
||||
}
|
||||
}
|
||||
if (seedToUse == null || SPECIAL_SEEDS.includes(seedToUse)) {
|
||||
seedToUse = this.generateRandomSeed();
|
||||
}
|
||||
}
|
||||
return seedToUse !== null && seedToUse !== void 0 ? seedToUse : inputSeed;
|
||||
}
|
||||
static setUp(comfyClass, nodeData) {
|
||||
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, RgthreeSeed);
|
||||
}
|
||||
static onRegisteredForOverride(comfyClass, ctxClass) {
|
||||
addConnectionLayoutSupport(RgthreeSeed, app, [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
]);
|
||||
setTimeout(() => {
|
||||
RgthreeSeed.category = comfyClass.category;
|
||||
});
|
||||
}
|
||||
}
|
||||
RgthreeSeed.title = NodeTypesString.SEED;
|
||||
RgthreeSeed.type = NodeTypesString.SEED;
|
||||
RgthreeSeed.comfyClass = NodeTypesString.SEED;
|
||||
RgthreeSeed.exposedActions = ["Randomize Each Time", "Use Last Queued Seed"];
|
||||
RgthreeSeed["@randomMax"] = { type: "number" };
|
||||
RgthreeSeed["@randomMin"] = { type: "number" };
|
||||
app.registerExtension({
|
||||
name: "rgthree.Seed",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name === RgthreeSeed.type) {
|
||||
RgthreeSeed.setUp(nodeType, nodeData);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { NodeTypesString } from "../constants.js";
|
||||
import { reduceNodesDepthFirst } from "../utils.js";
|
||||
const SHORTCUT_DEFAULTS = "1234567890abcdefghijklmnopqrstuvwxyz".split("");
|
||||
class BookmarksService {
|
||||
getCurrentBookmarks() {
|
||||
return reduceNodesDepthFirst(app.graph.nodes, (n, acc) => {
|
||||
if (n.type === NodeTypesString.BOOKMARK) {
|
||||
acc.push(n);
|
||||
}
|
||||
}, []).sort((a, b) => a.title.localeCompare(b.title));
|
||||
}
|
||||
getExistingShortcuts() {
|
||||
const bookmarkNodes = this.getCurrentBookmarks();
|
||||
const usedShortcuts = new Set(bookmarkNodes.map((n) => n.shortcutKey));
|
||||
return usedShortcuts;
|
||||
}
|
||||
getNextShortcut() {
|
||||
var _a;
|
||||
const existingShortcuts = this.getExistingShortcuts();
|
||||
return (_a = SHORTCUT_DEFAULTS.find((char) => !existingShortcuts.has(char))) !== null && _a !== void 0 ? _a : "1";
|
||||
}
|
||||
}
|
||||
export const SERVICE = new BookmarksService();
|
||||
@@ -0,0 +1,28 @@
|
||||
import { rgthreeConfig } from "../../../rgthree/config.js";
|
||||
import { getObjectValue, setObjectValue } from "../../../rgthree/common/shared_utils.js";
|
||||
import { rgthreeApi } from "../../../rgthree/common/rgthree_api.js";
|
||||
class ConfigService extends EventTarget {
|
||||
getConfigValue(key, def) {
|
||||
return getObjectValue(rgthreeConfig, key, def);
|
||||
}
|
||||
getFeatureValue(key, def) {
|
||||
key = "features." + key.replace(/^features\./, "");
|
||||
return getObjectValue(rgthreeConfig, key, def);
|
||||
}
|
||||
async setConfigValues(changed) {
|
||||
const body = new FormData();
|
||||
body.append("json", JSON.stringify(changed));
|
||||
const response = await rgthreeApi.fetchJson("/config", { method: "POST", body });
|
||||
if (response.status === "ok") {
|
||||
for (const [key, value] of Object.entries(changed)) {
|
||||
setObjectValue(rgthreeConfig, key, value);
|
||||
this.dispatchEvent(new CustomEvent("config-change", { detail: { key, value } }));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
export const SERVICE = new ConfigService();
|
||||
@@ -0,0 +1,51 @@
|
||||
import { getConnectedOutputNodesAndFilterPassThroughs } from "../utils.js";
|
||||
export let SERVICE;
|
||||
const OWNED_PREFIX = "+";
|
||||
const REGEX_PREFIX = /^[\+⚠️]\s*/;
|
||||
const REGEX_EMPTY_INPUT = /^\+\s*$/;
|
||||
export function stripContextInputPrefixes(name) {
|
||||
return name.replace(REGEX_PREFIX, "");
|
||||
}
|
||||
export function getContextOutputName(inputName) {
|
||||
if (inputName === "base_ctx")
|
||||
return "CONTEXT";
|
||||
return stripContextInputPrefixes(inputName).toUpperCase();
|
||||
}
|
||||
export var InputMutationOperation;
|
||||
(function (InputMutationOperation) {
|
||||
InputMutationOperation[InputMutationOperation["UNKNOWN"] = 0] = "UNKNOWN";
|
||||
InputMutationOperation[InputMutationOperation["ADDED"] = 1] = "ADDED";
|
||||
InputMutationOperation[InputMutationOperation["REMOVED"] = 2] = "REMOVED";
|
||||
InputMutationOperation[InputMutationOperation["RENAMED"] = 3] = "RENAMED";
|
||||
})(InputMutationOperation || (InputMutationOperation = {}));
|
||||
export class ContextService {
|
||||
constructor() {
|
||||
if (SERVICE) {
|
||||
throw new Error("ContextService was already instantiated.");
|
||||
}
|
||||
}
|
||||
onInputChanges(node, mutation) {
|
||||
const childCtxs = getConnectedOutputNodesAndFilterPassThroughs(node, node, 0);
|
||||
for (const childCtx of childCtxs) {
|
||||
childCtx.handleUpstreamMutation(mutation);
|
||||
}
|
||||
}
|
||||
getDynamicContextInputsData(node) {
|
||||
return node
|
||||
.getContextInputsList()
|
||||
.map((input, index) => ({
|
||||
name: stripContextInputPrefixes(input.name),
|
||||
type: String(input.type),
|
||||
index,
|
||||
}))
|
||||
.filter((i) => i.type !== "*");
|
||||
}
|
||||
getDynamicContextOutputsData(node) {
|
||||
return node.outputs.map((output, index) => ({
|
||||
name: stripContextInputPrefixes(output.name),
|
||||
type: String(output.type),
|
||||
index,
|
||||
}));
|
||||
}
|
||||
}
|
||||
SERVICE = new ContextService();
|
||||
@@ -0,0 +1,163 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { getGraphDependantNodeKey, getGroupNodes, reduceNodesDepthFirst } from "../utils.js";
|
||||
class FastGroupsService {
|
||||
constructor() {
|
||||
this.msThreshold = 400;
|
||||
this.msLastUnsorted = 0;
|
||||
this.msLastAlpha = 0;
|
||||
this.msLastPosition = 0;
|
||||
this.groupsUnsorted = [];
|
||||
this.groupsSortedAlpha = [];
|
||||
this.groupsSortedPosition = [];
|
||||
this.fastGroupNodes = [];
|
||||
this.runScheduledForMs = null;
|
||||
this.runScheduleTimeout = null;
|
||||
this.runScheduleAnimation = null;
|
||||
this.cachedNodeBoundings = null;
|
||||
}
|
||||
addFastGroupNode(node) {
|
||||
this.fastGroupNodes.push(node);
|
||||
this.scheduleRun(8);
|
||||
}
|
||||
removeFastGroupNode(node) {
|
||||
var _a;
|
||||
const index = this.fastGroupNodes.indexOf(node);
|
||||
if (index > -1) {
|
||||
this.fastGroupNodes.splice(index, 1);
|
||||
}
|
||||
if (!((_a = this.fastGroupNodes) === null || _a === void 0 ? void 0 : _a.length)) {
|
||||
this.clearScheduledRun();
|
||||
this.groupsUnsorted = [];
|
||||
this.groupsSortedAlpha = [];
|
||||
this.groupsSortedPosition = [];
|
||||
}
|
||||
}
|
||||
run() {
|
||||
if (!this.runScheduledForMs) {
|
||||
return;
|
||||
}
|
||||
for (const node of this.fastGroupNodes) {
|
||||
node.refreshWidgets();
|
||||
}
|
||||
this.clearScheduledRun();
|
||||
this.scheduleRun();
|
||||
}
|
||||
scheduleRun(ms = 500) {
|
||||
if (this.runScheduledForMs && ms < this.runScheduledForMs) {
|
||||
this.clearScheduledRun();
|
||||
}
|
||||
if (!this.runScheduledForMs && this.fastGroupNodes.length) {
|
||||
this.runScheduledForMs = ms;
|
||||
this.runScheduleTimeout = setTimeout(() => {
|
||||
this.runScheduleAnimation = requestAnimationFrame(() => this.run());
|
||||
}, ms);
|
||||
}
|
||||
}
|
||||
clearScheduledRun() {
|
||||
this.runScheduleTimeout && clearTimeout(this.runScheduleTimeout);
|
||||
this.runScheduleAnimation && cancelAnimationFrame(this.runScheduleAnimation);
|
||||
this.runScheduleTimeout = null;
|
||||
this.runScheduleAnimation = null;
|
||||
this.runScheduledForMs = null;
|
||||
}
|
||||
getBoundingsForAllNodes() {
|
||||
if (!this.cachedNodeBoundings) {
|
||||
this.cachedNodeBoundings = reduceNodesDepthFirst(app.graph._nodes, (node, acc) => {
|
||||
var _a, _b;
|
||||
let bounds = node.getBounding();
|
||||
if (bounds[0] === 0 && bounds[1] === 0 && bounds[2] === 0 && bounds[3] === 0) {
|
||||
const ctx = (_b = (_a = node.graph) === null || _a === void 0 ? void 0 : _a.primaryCanvas) === null || _b === void 0 ? void 0 : _b.canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
node.updateArea(ctx);
|
||||
bounds = node.getBounding();
|
||||
}
|
||||
}
|
||||
acc[getGraphDependantNodeKey(node)] = bounds;
|
||||
}, {});
|
||||
setTimeout(() => {
|
||||
this.cachedNodeBoundings = null;
|
||||
}, 50);
|
||||
}
|
||||
return this.cachedNodeBoundings;
|
||||
}
|
||||
recomputeInsideNodesForGroup(group) {
|
||||
if (app.canvas.isDragging)
|
||||
return;
|
||||
const cachedBoundings = this.getBoundingsForAllNodes();
|
||||
const nodes = group.graph.nodes;
|
||||
group._children.clear();
|
||||
group.nodes.length = 0;
|
||||
for (const node of nodes) {
|
||||
const nodeBounding = cachedBoundings[getGraphDependantNodeKey(node)];
|
||||
const nodeCenter = nodeBounding &&
|
||||
[nodeBounding[0] + nodeBounding[2] * 0.5, nodeBounding[1] + nodeBounding[3] * 0.5];
|
||||
if (nodeCenter) {
|
||||
const grouBounds = group._bounding;
|
||||
if (nodeCenter[0] >= grouBounds[0] &&
|
||||
nodeCenter[0] < grouBounds[0] + grouBounds[2] &&
|
||||
nodeCenter[1] >= grouBounds[1] &&
|
||||
nodeCenter[1] < grouBounds[1] + grouBounds[3]) {
|
||||
group._children.add(node);
|
||||
group.nodes.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getGroupsUnsorted(now) {
|
||||
var _a, _b, _c;
|
||||
const canvas = app.canvas;
|
||||
const graph = (_a = canvas.getCurrentGraph()) !== null && _a !== void 0 ? _a : app.graph;
|
||||
if (!canvas.selected_group_moving &&
|
||||
(!this.groupsUnsorted.length || now - this.msLastUnsorted > this.msThreshold)) {
|
||||
this.groupsUnsorted = [...graph._groups];
|
||||
const subgraphs = (_b = graph.subgraphs) === null || _b === void 0 ? void 0 : _b.values();
|
||||
if (subgraphs) {
|
||||
let s;
|
||||
while ((s = subgraphs.next().value))
|
||||
this.groupsUnsorted.push(...((_c = s.groups) !== null && _c !== void 0 ? _c : []));
|
||||
}
|
||||
for (const group of this.groupsUnsorted) {
|
||||
this.recomputeInsideNodesForGroup(group);
|
||||
group.rgthree_hasAnyActiveNode = getGroupNodes(group).some((n) => n.mode === LiteGraph.ALWAYS);
|
||||
}
|
||||
this.msLastUnsorted = now;
|
||||
}
|
||||
return this.groupsUnsorted;
|
||||
}
|
||||
getGroupsAlpha(now) {
|
||||
if (!this.groupsSortedAlpha.length || now - this.msLastAlpha > this.msThreshold) {
|
||||
this.groupsSortedAlpha = [...this.getGroupsUnsorted(now)].sort((a, b) => {
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
this.msLastAlpha = now;
|
||||
}
|
||||
return this.groupsSortedAlpha;
|
||||
}
|
||||
getGroupsPosition(now) {
|
||||
if (!this.groupsSortedPosition.length || now - this.msLastPosition > this.msThreshold) {
|
||||
this.groupsSortedPosition = [...this.getGroupsUnsorted(now)].sort((a, b) => {
|
||||
const aY = Math.floor(a._pos[1] / 30);
|
||||
const bY = Math.floor(b._pos[1] / 30);
|
||||
if (aY == bY) {
|
||||
const aX = Math.floor(a._pos[0] / 30);
|
||||
const bX = Math.floor(b._pos[0] / 30);
|
||||
return aX - bX;
|
||||
}
|
||||
return aY - bY;
|
||||
});
|
||||
this.msLastPosition = now;
|
||||
}
|
||||
return this.groupsSortedPosition;
|
||||
}
|
||||
getGroups(sort) {
|
||||
const now = +new Date();
|
||||
if (sort === "alphanumeric") {
|
||||
return this.getGroupsAlpha(now);
|
||||
}
|
||||
if (sort === "position") {
|
||||
return this.getGroupsPosition(now);
|
||||
}
|
||||
return this.getGroupsUnsorted(now);
|
||||
}
|
||||
}
|
||||
export const SERVICE = new FastGroupsService();
|
||||
@@ -0,0 +1,115 @@
|
||||
class KeyEventService extends EventTarget {
|
||||
constructor() {
|
||||
var _a, _b, _c;
|
||||
super();
|
||||
this.downKeys = {};
|
||||
this.shiftDownKeys = {};
|
||||
this.ctrlKey = false;
|
||||
this.altKey = false;
|
||||
this.metaKey = false;
|
||||
this.shiftKey = false;
|
||||
this.isMac = !!(((_a = navigator.platform) === null || _a === void 0 ? void 0 : _a.toLocaleUpperCase().startsWith("MAC")) ||
|
||||
((_c = (_b = navigator.userAgentData) === null || _b === void 0 ? void 0 : _b.platform) === null || _c === void 0 ? void 0 : _c.toLocaleUpperCase().startsWith("MAC")));
|
||||
this.initialize();
|
||||
}
|
||||
initialize() {
|
||||
const that = this;
|
||||
const processKey = LGraphCanvas.prototype.processKey;
|
||||
LGraphCanvas.prototype.processKey = function (e) {
|
||||
if (e.type === "keydown" || e.type === "keyup") {
|
||||
that.handleKeyDownOrUp(e);
|
||||
}
|
||||
return processKey.apply(this, [...arguments]);
|
||||
};
|
||||
window.addEventListener("keydown", (e) => {
|
||||
that.handleKeyDownOrUp(e);
|
||||
});
|
||||
window.addEventListener("keyup", (e) => {
|
||||
that.handleKeyDownOrUp(e);
|
||||
});
|
||||
document.addEventListener("visibilitychange", (e) => {
|
||||
this.clearKeydowns();
|
||||
});
|
||||
window.addEventListener("blur", (e) => {
|
||||
this.clearKeydowns();
|
||||
});
|
||||
}
|
||||
handleKeyDownOrUp(e) {
|
||||
const key = e.key.toLocaleUpperCase();
|
||||
if ((e.type === 'keydown' && this.downKeys[key] === true)
|
||||
|| (e.type === 'keyup' && this.downKeys[key] === undefined)) {
|
||||
return;
|
||||
}
|
||||
this.ctrlKey = !!e.ctrlKey;
|
||||
this.altKey = !!e.altKey;
|
||||
this.metaKey = !!e.metaKey;
|
||||
this.shiftKey = !!e.shiftKey;
|
||||
if (e.type === "keydown") {
|
||||
this.downKeys[key] = true;
|
||||
this.dispatchCustomEvent("keydown", { originalEvent: e });
|
||||
if (this.shiftKey && key !== 'SHIFT') {
|
||||
this.shiftDownKeys[key] = true;
|
||||
}
|
||||
}
|
||||
else if (e.type === "keyup") {
|
||||
if (key === "META" && this.isMac) {
|
||||
this.clearKeydowns();
|
||||
}
|
||||
else {
|
||||
delete this.downKeys[key];
|
||||
}
|
||||
if (key === 'SHIFT') {
|
||||
for (const key in this.shiftDownKeys) {
|
||||
delete this.downKeys[key];
|
||||
delete this.shiftDownKeys[key];
|
||||
}
|
||||
}
|
||||
this.dispatchCustomEvent("keyup", { originalEvent: e });
|
||||
}
|
||||
}
|
||||
clearKeydowns() {
|
||||
this.ctrlKey = false;
|
||||
this.altKey = false;
|
||||
this.metaKey = false;
|
||||
this.shiftKey = false;
|
||||
for (const key in this.downKeys)
|
||||
delete this.downKeys[key];
|
||||
}
|
||||
dispatchCustomEvent(event, detail) {
|
||||
if (detail != null) {
|
||||
return this.dispatchEvent(new CustomEvent(event, { detail }));
|
||||
}
|
||||
return this.dispatchEvent(new CustomEvent(event));
|
||||
}
|
||||
getKeysFromShortcut(shortcut) {
|
||||
let keys;
|
||||
if (typeof shortcut === "string") {
|
||||
shortcut = shortcut.replace(/\s/g, "");
|
||||
shortcut = shortcut.replace(/^\+/, "__PLUS__").replace(/\+\+/, "+__PLUS__");
|
||||
keys = shortcut.split("+").map((i) => i.replace("__PLUS__", "+"));
|
||||
}
|
||||
else {
|
||||
keys = [...shortcut];
|
||||
}
|
||||
return keys.map((k) => k.toLocaleUpperCase());
|
||||
}
|
||||
areAllKeysDown(keys) {
|
||||
keys = this.getKeysFromShortcut(keys);
|
||||
return keys.every((k) => {
|
||||
return this.downKeys[k];
|
||||
});
|
||||
}
|
||||
areOnlyKeysDown(keys, alsoAllowShift = false) {
|
||||
keys = this.getKeysFromShortcut(keys);
|
||||
const allKeysDown = this.areAllKeysDown(keys);
|
||||
const downKeysLength = Object.values(this.downKeys).length;
|
||||
if (allKeysDown && keys.length === downKeysLength) {
|
||||
return true;
|
||||
}
|
||||
if (alsoAllowShift && !keys.includes("SHIFT") && keys.length === downKeysLength - 1) {
|
||||
return allKeysDown && this.areAllKeysDown(["SHIFT"]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export const SERVICE = new KeyEventService();
|
||||
723
custom_nodes/rgthree-comfy/web/comfyui/utils.js
Normal file
723
custom_nodes/rgthree-comfy/web/comfyui/utils.js
Normal file
@@ -0,0 +1,723 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { getResolver, wait } from "../../rgthree/common/shared_utils.js";
|
||||
import { RgthreeHelpDialog } from "../../rgthree/common/dialog.js";
|
||||
const oldApiGetNodeDefs = api.getNodeDefs;
|
||||
api.getNodeDefs = async function () {
|
||||
const defs = await oldApiGetNodeDefs.call(api);
|
||||
this.dispatchEvent(new CustomEvent("fresh-node-defs", { detail: defs }));
|
||||
return defs;
|
||||
};
|
||||
export var IoDirection;
|
||||
(function (IoDirection) {
|
||||
IoDirection[IoDirection["INPUT"] = 0] = "INPUT";
|
||||
IoDirection[IoDirection["OUTPUT"] = 1] = "OUTPUT";
|
||||
})(IoDirection || (IoDirection = {}));
|
||||
const PADDING = 0;
|
||||
export const LAYOUT_LABEL_TO_DATA = {
|
||||
Left: [LiteGraph.LEFT, [0, 0.5], [PADDING, 0]],
|
||||
Right: [LiteGraph.RIGHT, [1, 0.5], [-PADDING, 0]],
|
||||
Top: [LiteGraph.UP, [0.5, 0], [0, PADDING]],
|
||||
Bottom: [LiteGraph.DOWN, [0.5, 1], [0, -PADDING]],
|
||||
};
|
||||
export const LAYOUT_LABEL_OPPOSITES = {
|
||||
Left: "Right",
|
||||
Right: "Left",
|
||||
Top: "Bottom",
|
||||
Bottom: "Top",
|
||||
};
|
||||
export const LAYOUT_CLOCKWISE = ["Top", "Right", "Bottom", "Left"];
|
||||
export function addMenuItem(node, _app, config, after = "Shape") {
|
||||
const oldGetExtraMenuOptions = node.prototype.getExtraMenuOptions;
|
||||
node.prototype.getExtraMenuOptions = function (canvas, menuOptions) {
|
||||
oldGetExtraMenuOptions && oldGetExtraMenuOptions.apply(this, [canvas, menuOptions]);
|
||||
addMenuItemOnExtraMenuOptions(this, config, menuOptions, after);
|
||||
};
|
||||
}
|
||||
let canvasResolver = null;
|
||||
export function waitForCanvas() {
|
||||
if (canvasResolver === null) {
|
||||
canvasResolver = getResolver();
|
||||
function _waitForCanvas() {
|
||||
if (!canvasResolver.completed) {
|
||||
if (app === null || app === void 0 ? void 0 : app.canvas) {
|
||||
canvasResolver.resolve(app.canvas);
|
||||
}
|
||||
else {
|
||||
requestAnimationFrame(_waitForCanvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
_waitForCanvas();
|
||||
}
|
||||
return canvasResolver.promise;
|
||||
}
|
||||
let graphResolver = null;
|
||||
export function waitForGraph() {
|
||||
if (graphResolver === null) {
|
||||
graphResolver = getResolver();
|
||||
function _wait() {
|
||||
if (!graphResolver.completed) {
|
||||
if (app === null || app === void 0 ? void 0 : app.graph) {
|
||||
graphResolver.resolve(app.graph);
|
||||
}
|
||||
else {
|
||||
requestAnimationFrame(_wait);
|
||||
}
|
||||
}
|
||||
}
|
||||
_wait();
|
||||
}
|
||||
return graphResolver.promise;
|
||||
}
|
||||
export function addMenuItemOnExtraMenuOptions(node, config, menuOptions, after = "Shape") {
|
||||
let idx = menuOptions
|
||||
.slice()
|
||||
.reverse()
|
||||
.findIndex((option) => option === null || option === void 0 ? void 0 : option.isRgthree);
|
||||
if (idx == -1) {
|
||||
idx = menuOptions.findIndex((option) => { var _a; return (_a = option === null || option === void 0 ? void 0 : option.content) === null || _a === void 0 ? void 0 : _a.includes(after); }) + 1;
|
||||
if (!idx) {
|
||||
idx = menuOptions.length - 1;
|
||||
}
|
||||
menuOptions.splice(idx, 0, null);
|
||||
idx++;
|
||||
}
|
||||
else {
|
||||
idx = menuOptions.length - idx;
|
||||
}
|
||||
const subMenuOptions = typeof config.subMenuOptions === "function"
|
||||
? config.subMenuOptions(node)
|
||||
: config.subMenuOptions;
|
||||
menuOptions.splice(idx, 0, {
|
||||
content: typeof config.name == "function" ? config.name(node) : config.name,
|
||||
has_submenu: !!(subMenuOptions === null || subMenuOptions === void 0 ? void 0 : subMenuOptions.length),
|
||||
isRgthree: true,
|
||||
callback: (value, _options, event, parentMenu, _node) => {
|
||||
if (!!(subMenuOptions === null || subMenuOptions === void 0 ? void 0 : subMenuOptions.length)) {
|
||||
new LiteGraph.ContextMenu(subMenuOptions.map((option) => (option ? { content: option } : null)), {
|
||||
event,
|
||||
parentMenu,
|
||||
callback: (subValue, _options, _event, _parentMenu, _node) => {
|
||||
if (config.property) {
|
||||
node.properties = node.properties || {};
|
||||
node.properties[config.property] = config.prepareValue
|
||||
? config.prepareValue(subValue.content || "", node)
|
||||
: subValue.content || "";
|
||||
}
|
||||
config.callback && config.callback(node, subValue === null || subValue === void 0 ? void 0 : subValue.content);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (config.property) {
|
||||
node.properties = node.properties || {};
|
||||
node.properties[config.property] = config.prepareValue
|
||||
? config.prepareValue(node.properties[config.property], node)
|
||||
: !node.properties[config.property];
|
||||
}
|
||||
config.callback && config.callback(node, value === null || value === void 0 ? void 0 : value.content);
|
||||
},
|
||||
});
|
||||
}
|
||||
export function addConnectionLayoutSupport(node, app, options = [
|
||||
["Left", "Right"],
|
||||
["Right", "Left"],
|
||||
], callback) {
|
||||
addMenuItem(node, app, {
|
||||
name: "Connections Layout",
|
||||
property: "connections_layout",
|
||||
subMenuOptions: options.map((option) => option[0] + (option[1] ? " -> " + option[1] : "")),
|
||||
prepareValue: (value, node) => {
|
||||
var _a;
|
||||
const values = String(value).split(" -> ");
|
||||
if (!values[1] && !((_a = node.outputs) === null || _a === void 0 ? void 0 : _a.length)) {
|
||||
values[1] = LAYOUT_LABEL_OPPOSITES[values[0]];
|
||||
}
|
||||
if (!LAYOUT_LABEL_TO_DATA[values[0]] || !LAYOUT_LABEL_TO_DATA[values[1]]) {
|
||||
throw new Error(`New Layout invalid: [${values[0]}, ${values[1]}]`);
|
||||
}
|
||||
return values;
|
||||
},
|
||||
callback: (node) => {
|
||||
var _a;
|
||||
callback && callback(node);
|
||||
(_a = node.graph) === null || _a === void 0 ? void 0 : _a.setDirtyCanvas(true, true);
|
||||
},
|
||||
});
|
||||
node.prototype.getConnectionPos = function (isInput, slotNumber, out) {
|
||||
return getConnectionPosForLayout(this, isInput, slotNumber, out);
|
||||
};
|
||||
node.prototype.getInputPos = function (slotNumber) {
|
||||
return getConnectionPosForLayout(this, true, slotNumber, [0, 0]);
|
||||
};
|
||||
node.prototype.getOutputPos = function (slotNumber) {
|
||||
return getConnectionPosForLayout(this, false, slotNumber, [0, 0]);
|
||||
};
|
||||
}
|
||||
export function setConnectionsLayout(node, newLayout) {
|
||||
var _a;
|
||||
newLayout = newLayout || node.defaultConnectionsLayout || ["Left", "Right"];
|
||||
if (!newLayout[1] && !((_a = node.outputs) === null || _a === void 0 ? void 0 : _a.length)) {
|
||||
newLayout[1] = LAYOUT_LABEL_OPPOSITES[newLayout[0]];
|
||||
}
|
||||
if (!LAYOUT_LABEL_TO_DATA[newLayout[0]] || !LAYOUT_LABEL_TO_DATA[newLayout[1]]) {
|
||||
throw new Error(`New Layout invalid: [${newLayout[0]}, ${newLayout[1]}]`);
|
||||
}
|
||||
node.properties = node.properties || {};
|
||||
node.properties["connections_layout"] = newLayout;
|
||||
}
|
||||
export function setConnectionsCollapse(node, collapseConnections = null) {
|
||||
node.properties = node.properties || {};
|
||||
collapseConnections =
|
||||
collapseConnections !== null ? collapseConnections : !node.properties["collapse_connections"];
|
||||
node.properties["collapse_connections"] = collapseConnections;
|
||||
}
|
||||
export function getConnectionPosForLayout(node, isInput, slotNumber, out) {
|
||||
var _a, _b, _c;
|
||||
out = out || new Float32Array(2);
|
||||
node.properties = node.properties || {};
|
||||
const layout = node.properties["connections_layout"] ||
|
||||
node.defaultConnectionsLayout || ["Left", "Right"];
|
||||
const collapseConnections = node.properties["collapse_connections"] || false;
|
||||
const offset = (_a = node.constructor.layout_slot_offset) !== null && _a !== void 0 ? _a : LiteGraph.NODE_SLOT_HEIGHT * 0.5;
|
||||
let side = isInput ? layout[0] : layout[1];
|
||||
const otherSide = isInput ? layout[1] : layout[0];
|
||||
let data = LAYOUT_LABEL_TO_DATA[side];
|
||||
const slotList = node[isInput ? "inputs" : "outputs"];
|
||||
const cxn = slotList[slotNumber];
|
||||
if (!cxn) {
|
||||
console.log("No connection found.. weird", isInput, slotNumber);
|
||||
return out;
|
||||
}
|
||||
if (cxn.disabled) {
|
||||
if (cxn.color_on !== "#666665") {
|
||||
cxn._color_on_org = cxn._color_on_org || cxn.color_on;
|
||||
cxn._color_off_org = cxn._color_off_org || cxn.color_off;
|
||||
}
|
||||
cxn.color_on = "#666665";
|
||||
cxn.color_off = "#666665";
|
||||
}
|
||||
else if (cxn.color_on === "#666665") {
|
||||
cxn.color_on = cxn._color_on_org || undefined;
|
||||
cxn.color_off = cxn._color_off_org || undefined;
|
||||
}
|
||||
const displaySlot = collapseConnections
|
||||
? 0
|
||||
: slotNumber -
|
||||
slotList.reduce((count, ioput, index) => {
|
||||
count += index < slotNumber && ioput.hidden ? 1 : 0;
|
||||
return count;
|
||||
}, 0);
|
||||
cxn.dir = data[0];
|
||||
const connections_dir = node.properties["connections_dir"];
|
||||
if ((node.size[0] == 10 || node.size[1] == 10) && connections_dir) {
|
||||
cxn.dir = connections_dir[isInput ? 0 : 1];
|
||||
}
|
||||
if (side === "Left") {
|
||||
if (node.flags.collapsed) {
|
||||
var w = node._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;
|
||||
out[0] = node.pos[0];
|
||||
out[1] = node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;
|
||||
}
|
||||
else {
|
||||
toggleConnectionLabel(cxn, !isInput || collapseConnections || !!node.hideSlotLabels);
|
||||
out[0] = node.pos[0] + offset;
|
||||
if ((_b = node.constructor) === null || _b === void 0 ? void 0 : _b.type.includes("Reroute")) {
|
||||
out[1] = node.pos[1] + node.size[1] * 0.5;
|
||||
}
|
||||
else {
|
||||
out[1] =
|
||||
node.pos[1] +
|
||||
(displaySlot + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +
|
||||
(node.constructor.slot_start_y || 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (side === "Right") {
|
||||
if (node.flags.collapsed) {
|
||||
var w = node._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;
|
||||
out[0] = node.pos[0] + w;
|
||||
out[1] = node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;
|
||||
}
|
||||
else {
|
||||
toggleConnectionLabel(cxn, isInput || collapseConnections || !!node.hideSlotLabels);
|
||||
out[0] = node.pos[0] + node.size[0] + 1 - offset;
|
||||
if ((_c = node.constructor) === null || _c === void 0 ? void 0 : _c.type.includes("Reroute")) {
|
||||
out[1] = node.pos[1] + node.size[1] * 0.5;
|
||||
}
|
||||
else {
|
||||
out[1] =
|
||||
node.pos[1] +
|
||||
(displaySlot + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +
|
||||
(node.constructor.slot_start_y || 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (side === "Top") {
|
||||
if (!cxn.has_old_label) {
|
||||
cxn.has_old_label = true;
|
||||
cxn.old_label = cxn.label;
|
||||
cxn.label = " ";
|
||||
}
|
||||
out[0] = node.pos[0] + node.size[0] * 0.5;
|
||||
out[1] = node.pos[1] + offset;
|
||||
}
|
||||
else if (side === "Bottom") {
|
||||
if (!cxn.has_old_label) {
|
||||
cxn.has_old_label = true;
|
||||
cxn.old_label = cxn.label;
|
||||
cxn.label = " ";
|
||||
}
|
||||
out[0] = node.pos[0] + node.size[0] * 0.5;
|
||||
out[1] = node.pos[1] + node.size[1] - offset;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function toggleConnectionLabel(cxn, hide = true) {
|
||||
if (hide) {
|
||||
if (!cxn.has_old_label) {
|
||||
cxn.has_old_label = true;
|
||||
cxn.old_label = cxn.label;
|
||||
}
|
||||
cxn.label = " ";
|
||||
}
|
||||
else if (!hide && cxn.has_old_label) {
|
||||
cxn.has_old_label = false;
|
||||
cxn.label = cxn.old_label;
|
||||
cxn.old_label = undefined;
|
||||
}
|
||||
return cxn;
|
||||
}
|
||||
export function addHelpMenuItem(node, content, menuOptions) {
|
||||
addMenuItemOnExtraMenuOptions(node, {
|
||||
name: "🛟 Node Help",
|
||||
callback: (node) => {
|
||||
if (node.showHelp) {
|
||||
node.showHelp();
|
||||
}
|
||||
else {
|
||||
new RgthreeHelpDialog(node, content).show();
|
||||
}
|
||||
},
|
||||
}, menuOptions, "Properties Panel");
|
||||
}
|
||||
export var PassThroughFollowing;
|
||||
(function (PassThroughFollowing) {
|
||||
PassThroughFollowing[PassThroughFollowing["ALL"] = 0] = "ALL";
|
||||
PassThroughFollowing[PassThroughFollowing["NONE"] = 1] = "NONE";
|
||||
PassThroughFollowing[PassThroughFollowing["REROUTE_ONLY"] = 2] = "REROUTE_ONLY";
|
||||
})(PassThroughFollowing || (PassThroughFollowing = {}));
|
||||
export function shouldPassThrough(node, passThroughFollowing = PassThroughFollowing.ALL) {
|
||||
var _a;
|
||||
const type = (_a = node === null || node === void 0 ? void 0 : node.constructor) === null || _a === void 0 ? void 0 : _a.type;
|
||||
if (!type || passThroughFollowing === PassThroughFollowing.NONE) {
|
||||
return false;
|
||||
}
|
||||
if (passThroughFollowing === PassThroughFollowing.REROUTE_ONLY) {
|
||||
return type.includes("Reroute");
|
||||
}
|
||||
return (type.includes("Reroute") || type.includes("Node Combiner") || type.includes("Node Collector"));
|
||||
}
|
||||
function filterOutPassthroughNodes(infos, passThroughFollowing = PassThroughFollowing.ALL) {
|
||||
return infos.filter((i) => !shouldPassThrough(i.node, passThroughFollowing));
|
||||
}
|
||||
export function getConnectedInputNodes(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
|
||||
return getConnectedNodesInfo(startNode, IoDirection.INPUT, currentNode, slot, passThroughFollowing).map((n) => n.node);
|
||||
}
|
||||
export function getConnectedInputInfosAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
|
||||
return filterOutPassthroughNodes(getConnectedNodesInfo(startNode, IoDirection.INPUT, currentNode, slot, passThroughFollowing), passThroughFollowing);
|
||||
}
|
||||
export function getConnectedInputNodesAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
|
||||
return getConnectedInputInfosAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing).map((n) => n.node);
|
||||
}
|
||||
export function getConnectedOutputNodes(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
|
||||
return getConnectedNodesInfo(startNode, IoDirection.OUTPUT, currentNode, slot, passThroughFollowing).map((n) => n.node);
|
||||
}
|
||||
export function getConnectedOutputNodesAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
|
||||
return filterOutPassthroughNodes(getConnectedNodesInfo(startNode, IoDirection.OUTPUT, currentNode, slot, passThroughFollowing), passThroughFollowing).map((n) => n.node);
|
||||
}
|
||||
export function getConnectedNodesInfo(startNode, dir = IoDirection.INPUT, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL, originTravelFromSlot) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h;
|
||||
currentNode = currentNode || startNode;
|
||||
let rootNodes = [];
|
||||
if (startNode === currentNode || shouldPassThrough(currentNode, passThroughFollowing)) {
|
||||
let linkIds;
|
||||
slot = slot != null && slot > -1 ? slot : undefined;
|
||||
if (dir == IoDirection.OUTPUT) {
|
||||
if (slot != null) {
|
||||
linkIds = [...(((_b = (_a = currentNode.outputs) === null || _a === void 0 ? void 0 : _a[slot]) === null || _b === void 0 ? void 0 : _b.links) || [])];
|
||||
}
|
||||
else {
|
||||
linkIds = ((_c = currentNode.outputs) === null || _c === void 0 ? void 0 : _c.flatMap((i) => i.links)) || [];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (slot != null) {
|
||||
linkIds = [(_e = (_d = currentNode.inputs) === null || _d === void 0 ? void 0 : _d[slot]) === null || _e === void 0 ? void 0 : _e.link];
|
||||
}
|
||||
else {
|
||||
linkIds = ((_f = currentNode.inputs) === null || _f === void 0 ? void 0 : _f.map((i) => i.link)) || [];
|
||||
}
|
||||
}
|
||||
const graph = (_g = currentNode.graph) !== null && _g !== void 0 ? _g : app.graph;
|
||||
for (const linkId of linkIds) {
|
||||
let link = null;
|
||||
if (typeof linkId == "number") {
|
||||
link = (_h = graph.links[linkId]) !== null && _h !== void 0 ? _h : null;
|
||||
}
|
||||
if (!link) {
|
||||
continue;
|
||||
}
|
||||
const travelFromSlot = dir == IoDirection.OUTPUT ? link.origin_slot : link.target_slot;
|
||||
const connectedId = dir == IoDirection.OUTPUT ? link.target_id : link.origin_id;
|
||||
const travelToSlot = dir == IoDirection.OUTPUT ? link.target_slot : link.origin_slot;
|
||||
originTravelFromSlot = originTravelFromSlot != null ? originTravelFromSlot : travelFromSlot;
|
||||
const originNode = graph.getNodeById(connectedId);
|
||||
if (!link) {
|
||||
console.error("No connected node found... weird");
|
||||
continue;
|
||||
}
|
||||
if (rootNodes.some((n) => n.node == originNode)) {
|
||||
console.log(`${startNode.title} (${startNode.id}) seems to have two links to ${originNode.title} (${originNode.id}). One may be stale: ${linkIds.join(", ")}`);
|
||||
}
|
||||
else {
|
||||
rootNodes.push({ node: originNode, travelFromSlot, travelToSlot, originTravelFromSlot });
|
||||
if (shouldPassThrough(originNode, passThroughFollowing)) {
|
||||
for (const foundNode of getConnectedNodesInfo(startNode, dir, originNode, undefined, undefined, originTravelFromSlot)) {
|
||||
if (!rootNodes.map((n) => n.node).includes(foundNode.node)) {
|
||||
rootNodes.push(foundNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rootNodes;
|
||||
}
|
||||
export function followConnectionUntilType(node, dir, slotNum, skipSelf = false) {
|
||||
const slots = dir === IoDirection.OUTPUT ? node.outputs : node.inputs;
|
||||
if (!slots || !slots.length) {
|
||||
return null;
|
||||
}
|
||||
let type = null;
|
||||
if (slotNum) {
|
||||
if (!slots[slotNum]) {
|
||||
return null;
|
||||
}
|
||||
type = getTypeFromSlot(slots[slotNum], dir, skipSelf);
|
||||
}
|
||||
else {
|
||||
for (const slot of slots) {
|
||||
type = getTypeFromSlot(slot, dir, skipSelf);
|
||||
if (type) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
function getTypeFromSlot(slot, dir, skipSelf = false) {
|
||||
let graph = app.canvas.getCurrentGraph();
|
||||
let type = slot === null || slot === void 0 ? void 0 : slot.type;
|
||||
if (!skipSelf && type != null && type != "*") {
|
||||
return { type: type, label: slot === null || slot === void 0 ? void 0 : slot.label, name: slot === null || slot === void 0 ? void 0 : slot.name };
|
||||
}
|
||||
const links = getSlotLinks(slot);
|
||||
for (const link of links) {
|
||||
const connectedId = dir == IoDirection.OUTPUT ? link.link.target_id : link.link.origin_id;
|
||||
const connectedSlotNum = dir == IoDirection.OUTPUT ? link.link.target_slot : link.link.origin_slot;
|
||||
const connectedNode = graph.getNodeById(connectedId);
|
||||
const connectedSlots = dir === IoDirection.OUTPUT ? connectedNode.inputs : connectedNode.outputs;
|
||||
let connectedSlot = connectedSlots[connectedSlotNum];
|
||||
if ((connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) != null && (connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) != "*") {
|
||||
return {
|
||||
type: connectedSlot.type,
|
||||
label: connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.label,
|
||||
name: connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.name,
|
||||
};
|
||||
}
|
||||
else if ((connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) == "*") {
|
||||
return followConnectionUntilType(connectedNode, dir);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
export async function replaceNode(existingNode, typeOrNewNode, inputNameMap) {
|
||||
const existingCtor = existingNode.constructor;
|
||||
const newNode = typeof typeOrNewNode === "string" ? LiteGraph.createNode(typeOrNewNode) : typeOrNewNode;
|
||||
if (existingNode.title != existingCtor.title) {
|
||||
newNode.title = existingNode.title;
|
||||
}
|
||||
newNode.pos = [...existingNode.pos];
|
||||
newNode.properties = { ...existingNode.properties };
|
||||
const oldComputeSize = [...existingNode.computeSize()];
|
||||
const oldSize = [
|
||||
existingNode.size[0] === oldComputeSize[0] ? null : existingNode.size[0],
|
||||
existingNode.size[1] === oldComputeSize[1] ? null : existingNode.size[1],
|
||||
];
|
||||
let setSizeIters = 0;
|
||||
const setSizeFn = () => {
|
||||
const newComputesize = newNode.computeSize();
|
||||
newNode.size[0] = Math.max(oldSize[0] || 0, newComputesize[0]);
|
||||
newNode.size[1] = Math.max(oldSize[1] || 0, newComputesize[1]);
|
||||
setSizeIters++;
|
||||
if (setSizeIters > 10) {
|
||||
requestAnimationFrame(setSizeFn);
|
||||
}
|
||||
};
|
||||
setSizeFn();
|
||||
const links = [];
|
||||
const graph = existingNode.graph || app.graph;
|
||||
for (const [index, output] of existingNode.outputs.entries()) {
|
||||
for (const linkId of output.links || []) {
|
||||
const link = graph.links[linkId];
|
||||
if (!link)
|
||||
continue;
|
||||
const targetNode = graph.getNodeById(link.target_id);
|
||||
links.push({ node: newNode, slot: output.name, targetNode, targetSlot: link.target_slot });
|
||||
}
|
||||
}
|
||||
for (const [index, input] of existingNode.inputs.entries()) {
|
||||
const linkId = input.link;
|
||||
if (linkId) {
|
||||
const link = graph.links[linkId];
|
||||
const originNode = graph.getNodeById(link.origin_id);
|
||||
links.push({
|
||||
node: originNode,
|
||||
slot: link.origin_slot,
|
||||
targetNode: newNode,
|
||||
targetSlot: (inputNameMap === null || inputNameMap === void 0 ? void 0 : inputNameMap.has(input.name))
|
||||
? inputNameMap.get(input.name)
|
||||
: input.name || index,
|
||||
});
|
||||
}
|
||||
}
|
||||
graph.add(newNode);
|
||||
await wait();
|
||||
for (const link of links) {
|
||||
link.node.connect(link.slot, link.targetNode, link.targetSlot);
|
||||
}
|
||||
await wait();
|
||||
graph.remove(existingNode);
|
||||
newNode.size = newNode.computeSize();
|
||||
newNode.setDirtyCanvas(true, true);
|
||||
return newNode;
|
||||
}
|
||||
export function getOriginNodeByLink(linkId) {
|
||||
let node = null;
|
||||
if (linkId != null) {
|
||||
const link = getLinkById(linkId);
|
||||
node = (link != null && getNodeById(link.origin_id)) || null;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
export function getLinkById(linkId) {
|
||||
var _a, _b, _c;
|
||||
if (linkId == null)
|
||||
return null;
|
||||
let link = (_a = app.graph.links[linkId]) !== null && _a !== void 0 ? _a : null;
|
||||
link = (_c = link !== null && link !== void 0 ? link : (_b = app.canvas.getCurrentGraph()) === null || _b === void 0 ? void 0 : _b.links[linkId]) !== null && _c !== void 0 ? _c : null;
|
||||
return link || findSomethingInAllSubgraphs((subgraph) => { var _a; return (_a = subgraph === null || subgraph === void 0 ? void 0 : subgraph.links[linkId]) !== null && _a !== void 0 ? _a : null; });
|
||||
}
|
||||
export function getNodeById(id) {
|
||||
var _a, _b;
|
||||
if (id == null)
|
||||
return null;
|
||||
let node = app.graph.getNodeById(id);
|
||||
node = (_b = node !== null && node !== void 0 ? node : (_a = app.canvas.getCurrentGraph()) === null || _a === void 0 ? void 0 : _a.getNodeById(id)) !== null && _b !== void 0 ? _b : null;
|
||||
return node || findSomethingInAllSubgraphs((subgraph) => { var _a; return (_a = subgraph === null || subgraph === void 0 ? void 0 : subgraph.getNodeById(id)) !== null && _a !== void 0 ? _a : null; });
|
||||
}
|
||||
export function findFromNodeForSubgraph(subgraphId) {
|
||||
var _a;
|
||||
const node = (_a = findSomethingInAllSubgraphs((subgraph) => subgraph.nodes
|
||||
.filter((node) => node.isSubgraphNode())
|
||||
.find((node) => node.subgraph.id === subgraphId))) !== null && _a !== void 0 ? _a : null;
|
||||
return node;
|
||||
}
|
||||
function findSomethingInAllSubgraphs(fn) {
|
||||
var _a, _b;
|
||||
const rootGraph = (_a = app.rootGraph) !== null && _a !== void 0 ? _a : app.graph.rootGraph;
|
||||
const subgraphs = [rootGraph, ...(_b = rootGraph.subgraphs) === null || _b === void 0 ? void 0 : _b.values()];
|
||||
for (const subgraph of subgraphs) {
|
||||
const thing = fn(subgraph);
|
||||
if (thing)
|
||||
return thing;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
export function applyMixins(original, constructors) {
|
||||
constructors.forEach((baseCtor) => {
|
||||
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
|
||||
Object.defineProperty(original.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null));
|
||||
});
|
||||
});
|
||||
}
|
||||
export function getSlotLinks(inputOrOutput) {
|
||||
var _a;
|
||||
const links = [];
|
||||
if (!inputOrOutput) {
|
||||
return links;
|
||||
}
|
||||
if ((_a = inputOrOutput.links) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
const output = inputOrOutput;
|
||||
for (const linkId of output.links || []) {
|
||||
const link = app.graph.links[linkId];
|
||||
if (link) {
|
||||
links.push({ id: linkId, link: link });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inputOrOutput.link) {
|
||||
const input = inputOrOutput;
|
||||
const link = app.graph.links[input.link];
|
||||
if (link) {
|
||||
links.push({ id: input.link, link: link });
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
export async function matchLocalSlotsToServer(node, direction, serverNodeData) {
|
||||
var _a, _b, _c;
|
||||
const serverSlotNames = direction == IoDirection.INPUT
|
||||
? Object.keys(((_a = serverNodeData.input) === null || _a === void 0 ? void 0 : _a.optional) || {})
|
||||
: serverNodeData.output_name;
|
||||
const serverSlotTypes = direction == IoDirection.INPUT
|
||||
? Object.values(((_b = serverNodeData.input) === null || _b === void 0 ? void 0 : _b.optional) || {}).map((i) => i[0])
|
||||
: serverNodeData.output;
|
||||
const slots = direction == IoDirection.INPUT ? node.inputs : node.outputs;
|
||||
let firstIndex = slots.findIndex((o, i) => i !== serverSlotNames.indexOf(o.name));
|
||||
if (firstIndex > -1) {
|
||||
const links = {};
|
||||
slots.map((slot) => {
|
||||
var _a;
|
||||
links[slot.name] = links[slot.name] || [];
|
||||
(_a = links[slot.name]) === null || _a === void 0 ? void 0 : _a.push(...getSlotLinks(slot));
|
||||
});
|
||||
for (const [index, serverSlotName] of serverSlotNames.entries()) {
|
||||
const currentNodeSlot = slots.map((s) => s.name).indexOf(serverSlotName);
|
||||
if (currentNodeSlot > -1) {
|
||||
if (currentNodeSlot != index) {
|
||||
const splicedItem = slots.splice(currentNodeSlot, 1)[0];
|
||||
slots.splice(index, 0, splicedItem);
|
||||
}
|
||||
}
|
||||
else if (currentNodeSlot === -1) {
|
||||
const splicedItem = {
|
||||
name: serverSlotName,
|
||||
type: serverSlotTypes[index],
|
||||
links: [],
|
||||
};
|
||||
slots.splice(index, 0, splicedItem);
|
||||
}
|
||||
}
|
||||
if (slots.length > serverSlotNames.length) {
|
||||
for (let i = slots.length - 1; i > serverSlotNames.length - 1; i--) {
|
||||
if (direction == IoDirection.INPUT) {
|
||||
node.disconnectInput(i);
|
||||
node.removeInput(i);
|
||||
}
|
||||
else {
|
||||
node.disconnectOutput(i);
|
||||
node.removeOutput(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [name, slotLinks] of Object.entries(links)) {
|
||||
let currentNodeSlot = slots.map((s) => s.name).indexOf(name);
|
||||
if (currentNodeSlot > -1) {
|
||||
for (const linkData of slotLinks) {
|
||||
if (direction == IoDirection.INPUT) {
|
||||
linkData.link.target_slot = currentNodeSlot;
|
||||
}
|
||||
else {
|
||||
linkData.link.origin_slot = currentNodeSlot;
|
||||
const nextNode = app.graph.getNodeById(linkData.link.target_id);
|
||||
if (nextNode && ((_c = nextNode.constructor) === null || _c === void 0 ? void 0 : _c.type.includes("Reroute"))) {
|
||||
nextNode.stabilize && nextNode.stabilize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export function isValidConnection(ioA, ioB) {
|
||||
if (!ioA || !ioB) {
|
||||
return false;
|
||||
}
|
||||
const typeA = String(ioA.type);
|
||||
const typeB = String(ioB.type);
|
||||
let isValid = LiteGraph.isValidConnection(typeA, typeB);
|
||||
if (!isValid) {
|
||||
let areCombos = (typeA.includes(",") && typeB === "COMBO") || (typeA === "COMBO" && typeB.includes(","));
|
||||
if (areCombos) {
|
||||
const nameA = ioA.name.toUpperCase().replace("_NAME", "").replace("CKPT", "MODEL");
|
||||
const nameB = ioB.name.toUpperCase().replace("_NAME", "").replace("CKPT", "MODEL");
|
||||
isValid = nameA.includes(nameB) || nameB.includes(nameA);
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
const oldIsValidConnection = LiteGraph.isValidConnection;
|
||||
LiteGraph.isValidConnection = function (typeA, typeB) {
|
||||
let isValid = oldIsValidConnection.call(LiteGraph, typeA, typeB);
|
||||
if (!isValid) {
|
||||
typeA = String(typeA);
|
||||
typeB = String(typeB);
|
||||
let areCombos = (typeA.includes(",") && typeB === "COMBO") || (typeA === "COMBO" && typeB.includes(","));
|
||||
isValid = areCombos;
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
export function getOutputNodes(nodes) {
|
||||
return ((nodes === null || nodes === void 0 ? void 0 : nodes.filter((n) => {
|
||||
var _a;
|
||||
return (n.mode != LiteGraph.NEVER && ((_a = n.constructor.nodeData) === null || _a === void 0 ? void 0 : _a.output_node));
|
||||
})) || []);
|
||||
}
|
||||
export function changeModeOfNodes(nodeOrNodes, mode) {
|
||||
reduceNodesDepthFirst(nodeOrNodes, (n) => {
|
||||
n.mode = mode;
|
||||
});
|
||||
}
|
||||
export function reduceNodesDepthFirst(nodeOrNodes, reduceFn, reduceTo) {
|
||||
var _a;
|
||||
const nodes = Array.isArray(nodeOrNodes) ? nodeOrNodes : [nodeOrNodes];
|
||||
const stack = nodes.map((node) => ({ node }));
|
||||
while (stack.length > 0) {
|
||||
const { node } = stack.pop();
|
||||
const result = reduceFn(node, reduceTo);
|
||||
if (result !== undefined && result !== reduceTo) {
|
||||
reduceTo = result;
|
||||
}
|
||||
if (((_a = node.isSubgraphNode) === null || _a === void 0 ? void 0 : _a.call(node)) && node.subgraph) {
|
||||
const children = node.subgraph.nodes;
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
stack.push({ node: children[i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
return reduceTo;
|
||||
}
|
||||
export function getGroupNodes(group) {
|
||||
return Array.from(group._children).filter((c) => c instanceof LGraphNode);
|
||||
}
|
||||
export function getGraphDependantNodeKey(node) {
|
||||
var _a;
|
||||
const graph = (_a = node.graph) !== null && _a !== void 0 ? _a : app.graph;
|
||||
return `${graph.id}:${node.id}`;
|
||||
}
|
||||
export function getFullColor(color, liteGraphKey = "color") {
|
||||
if (!color) {
|
||||
return "";
|
||||
}
|
||||
if (LGraphCanvas.node_colors[color]) {
|
||||
color = LGraphCanvas.node_colors[color][liteGraphKey];
|
||||
}
|
||||
color = color.replace("#", "").toLocaleLowerCase();
|
||||
if (color.length === 3) {
|
||||
color = color.replace(/(.)(.)(.)/, "$1$1$2$2$3$3");
|
||||
}
|
||||
return `#${color}`;
|
||||
}
|
||||
225
custom_nodes/rgthree-comfy/web/comfyui/utils_canvas.js
Normal file
225
custom_nodes/rgthree-comfy/web/comfyui/utils_canvas.js
Normal file
@@ -0,0 +1,225 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
function binarySearch(max, getValue, match) {
|
||||
let min = 0;
|
||||
while (min <= max) {
|
||||
let guess = Math.floor((min + max) / 2);
|
||||
const compareVal = getValue(guess);
|
||||
if (compareVal === match)
|
||||
return guess;
|
||||
if (compareVal < match)
|
||||
min = guess + 1;
|
||||
else
|
||||
max = guess - 1;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
export function fitString(ctx, str, maxWidth) {
|
||||
let width = ctx.measureText(str).width;
|
||||
const ellipsis = "…";
|
||||
const ellipsisWidth = measureText(ctx, ellipsis);
|
||||
if (width <= maxWidth || width <= ellipsisWidth) {
|
||||
return str;
|
||||
}
|
||||
const index = binarySearch(str.length, (guess) => measureText(ctx, str.substring(0, guess)), maxWidth - ellipsisWidth);
|
||||
return str.substring(0, index) + ellipsis;
|
||||
}
|
||||
export function measureText(ctx, str) {
|
||||
return ctx.measureText(str).width;
|
||||
}
|
||||
export function isLowQuality() {
|
||||
var _a;
|
||||
const canvas = app.canvas;
|
||||
return (((_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) || 1) <= 0.5;
|
||||
}
|
||||
export function drawNodeWidget(ctx, options) {
|
||||
const lowQuality = isLowQuality();
|
||||
const data = {
|
||||
width: options.size[0],
|
||||
height: options.size[1],
|
||||
posY: options.pos[1],
|
||||
lowQuality,
|
||||
margin: 15,
|
||||
colorOutline: LiteGraph.WIDGET_OUTLINE_COLOR,
|
||||
colorBackground: LiteGraph.WIDGET_BGCOLOR,
|
||||
colorText: LiteGraph.WIDGET_TEXT_COLOR,
|
||||
colorTextSecondary: LiteGraph.WIDGET_SECONDARY_TEXT_COLOR,
|
||||
};
|
||||
ctx.strokeStyle = options.colorStroke || data.colorOutline;
|
||||
ctx.fillStyle = options.colorBackground || data.colorBackground;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(data.margin, data.posY, data.width - data.margin * 2, data.height, lowQuality ? [0] : options.borderRadius ? [options.borderRadius] : [options.size[1] * 0.5]);
|
||||
ctx.fill();
|
||||
if (!lowQuality) {
|
||||
ctx.stroke();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
export function drawRoundedRectangle(ctx, options) {
|
||||
const lowQuality = isLowQuality();
|
||||
options = { ...options };
|
||||
ctx.save();
|
||||
ctx.strokeStyle = options.colorStroke || LiteGraph.WIDGET_OUTLINE_COLOR;
|
||||
ctx.fillStyle = options.colorBackground || LiteGraph.WIDGET_BGCOLOR;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(...options.pos, ...options.size, lowQuality ? [0] : options.borderRadius ? [options.borderRadius] : [options.size[1] * 0.5]);
|
||||
ctx.fill();
|
||||
!lowQuality && ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
export function drawNumberWidgetPart(ctx, options) {
|
||||
const arrowWidth = 9;
|
||||
const arrowHeight = 10;
|
||||
const innerMargin = 3;
|
||||
const numberWidth = 32;
|
||||
const xBoundsArrowLess = [0, 0];
|
||||
const xBoundsNumber = [0, 0];
|
||||
const xBoundsArrowMore = [0, 0];
|
||||
ctx.save();
|
||||
let posX = options.posX;
|
||||
const { posY, height, value, textColor } = options;
|
||||
const midY = posY + height / 2;
|
||||
if (options.direction === -1) {
|
||||
posX = posX - arrowWidth - innerMargin - numberWidth - innerMargin - arrowWidth;
|
||||
}
|
||||
ctx.fill(new Path2D(`M ${posX} ${midY} l ${arrowWidth} ${arrowHeight / 2} l 0 -${arrowHeight} L ${posX} ${midY} z`));
|
||||
xBoundsArrowLess[0] = posX;
|
||||
xBoundsArrowLess[1] = arrowWidth;
|
||||
posX += arrowWidth + innerMargin;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
const oldTextcolor = ctx.fillStyle;
|
||||
if (textColor) {
|
||||
ctx.fillStyle = textColor;
|
||||
}
|
||||
ctx.fillText(fitString(ctx, value.toFixed(2), numberWidth), posX + numberWidth / 2, midY);
|
||||
ctx.fillStyle = oldTextcolor;
|
||||
xBoundsNumber[0] = posX;
|
||||
xBoundsNumber[1] = numberWidth;
|
||||
posX += numberWidth + innerMargin;
|
||||
ctx.fill(new Path2D(`M ${posX} ${midY - arrowHeight / 2} l ${arrowWidth} ${arrowHeight / 2} l -${arrowWidth} ${arrowHeight / 2} v -${arrowHeight} z`));
|
||||
xBoundsArrowMore[0] = posX;
|
||||
xBoundsArrowMore[1] = arrowWidth;
|
||||
ctx.restore();
|
||||
return [xBoundsArrowLess, xBoundsNumber, xBoundsArrowMore];
|
||||
}
|
||||
drawNumberWidgetPart.WIDTH_TOTAL = 9 + 3 + 32 + 3 + 9;
|
||||
export function drawTogglePart(ctx, options) {
|
||||
const lowQuality = isLowQuality();
|
||||
ctx.save();
|
||||
const { posX, posY, height, value } = options;
|
||||
const toggleRadius = height * 0.36;
|
||||
const toggleBgWidth = height * 1.5;
|
||||
if (!lowQuality) {
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(posX + 4, posY + 4, toggleBgWidth - 8, height - 8, [height * 0.5]);
|
||||
ctx.globalAlpha = app.canvas.editor_alpha * 0.25;
|
||||
ctx.fillStyle = "rgba(255,255,255,0.45)";
|
||||
ctx.fill();
|
||||
ctx.globalAlpha = app.canvas.editor_alpha;
|
||||
}
|
||||
ctx.fillStyle = value === true ? "#89B" : "#888";
|
||||
const toggleX = lowQuality || value === false
|
||||
? posX + height * 0.5
|
||||
: value === true
|
||||
? posX + height
|
||||
: posX + height * 0.75;
|
||||
ctx.beginPath();
|
||||
ctx.arc(toggleX, posY + height * 0.5, toggleRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
return [posX, toggleBgWidth];
|
||||
}
|
||||
export function drawInfoIcon(ctx, x, y, size = 12) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x, y, size, size, [size * 0.1]);
|
||||
ctx.fillStyle = "#2f82ec";
|
||||
ctx.strokeStyle = "#0f2a5e";
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = "#FFF";
|
||||
ctx.lineWidth = 2;
|
||||
const midX = x + size / 2;
|
||||
const serifSize = size * 0.175;
|
||||
ctx.stroke(new Path2D(`
|
||||
M ${midX} ${y + size * 0.15}
|
||||
v 2
|
||||
M ${midX - serifSize} ${y + size * 0.45}
|
||||
h ${serifSize}
|
||||
v ${size * 0.325}
|
||||
h ${serifSize}
|
||||
h -${serifSize * 2}
|
||||
`));
|
||||
ctx.restore();
|
||||
}
|
||||
export function drawPlusIcon(ctx, x, midY, size = 12) {
|
||||
ctx.save();
|
||||
const s = size / 3;
|
||||
const plus = new Path2D(`
|
||||
M ${x} ${midY + s / 2}
|
||||
v-${s} h${s} v-${s} h${s}
|
||||
v${s} h${s} v${s} h-${s}
|
||||
v${s} h-${s} v-${s} h-${s}
|
||||
z
|
||||
`);
|
||||
ctx.lineJoin = "round";
|
||||
ctx.lineCap = "round";
|
||||
ctx.fillStyle = "#3a3";
|
||||
ctx.strokeStyle = "#383";
|
||||
ctx.fill(plus);
|
||||
ctx.stroke(plus);
|
||||
ctx.restore();
|
||||
}
|
||||
export function drawWidgetButton(ctx, options, text = null, isMouseDownedAndOver = false) {
|
||||
var _a;
|
||||
const borderRadius = isLowQuality() ? 0 : ((_a = options.borderRadius) !== null && _a !== void 0 ? _a : 4);
|
||||
ctx.save();
|
||||
if (!isLowQuality() && !isMouseDownedAndOver) {
|
||||
drawRoundedRectangle(ctx, {
|
||||
size: [options.size[0] - 2, options.size[1]],
|
||||
pos: [options.pos[0] + 1, options.pos[1] + 1],
|
||||
borderRadius,
|
||||
colorBackground: "#000000aa",
|
||||
colorStroke: "#000000aa",
|
||||
});
|
||||
}
|
||||
drawRoundedRectangle(ctx, {
|
||||
size: options.size,
|
||||
pos: [options.pos[0], options.pos[1] + (isMouseDownedAndOver ? 1 : 0)],
|
||||
borderRadius,
|
||||
colorBackground: isMouseDownedAndOver ? "#444" : LiteGraph.WIDGET_BGCOLOR,
|
||||
colorStroke: "transparent",
|
||||
});
|
||||
if (isLowQuality()) {
|
||||
ctx.restore();
|
||||
return;
|
||||
}
|
||||
if (!isMouseDownedAndOver) {
|
||||
drawRoundedRectangle(ctx, {
|
||||
size: [options.size[0] - 0.75, options.size[1] - 0.75],
|
||||
pos: options.pos,
|
||||
borderRadius: borderRadius - 0.5,
|
||||
colorBackground: "transparent",
|
||||
colorStroke: "#00000044",
|
||||
});
|
||||
drawRoundedRectangle(ctx, {
|
||||
size: [options.size[0] - 0.75, options.size[1] - 0.75],
|
||||
pos: [options.pos[0] + 0.75, options.pos[1] + 0.75],
|
||||
borderRadius: borderRadius - 0.5,
|
||||
colorBackground: "transparent",
|
||||
colorStroke: "#ffffff11",
|
||||
});
|
||||
}
|
||||
drawRoundedRectangle(ctx, {
|
||||
size: options.size,
|
||||
pos: [options.pos[0], options.pos[1] + (isMouseDownedAndOver ? 1 : 0)],
|
||||
borderRadius,
|
||||
colorBackground: "transparent",
|
||||
});
|
||||
if (!isLowQuality() && text) {
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
|
||||
ctx.fillText(text, options.size[0] / 2, options.pos[1] + options.size[1] / 2 + (isMouseDownedAndOver ? 1 : 0));
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
function isPrimitiveNode(node) {
|
||||
return node.type === "PrimitiveNode";
|
||||
}
|
||||
function getWidgetGetConfigSymbols(slot) {
|
||||
var _a;
|
||||
const widget = slot === null || slot === void 0 ? void 0 : slot.widget;
|
||||
if (!widget)
|
||||
return {};
|
||||
const syms = Object.getOwnPropertySymbols(widget || {});
|
||||
for (const sym of syms) {
|
||||
const symVal = widget[sym];
|
||||
const isGetConfig = typeof symVal === "function";
|
||||
let maybeCfg = isGetConfig ? symVal() : symVal;
|
||||
if (Array.isArray(maybeCfg) &&
|
||||
maybeCfg.length >= 2 &&
|
||||
typeof maybeCfg[0] === "string" &&
|
||||
(maybeCfg[0] === "*" || typeof ((_a = maybeCfg[1]) === null || _a === void 0 ? void 0 : _a.type) === "string")) {
|
||||
return isGetConfig ? { GET_CONFIG: sym } : { CONFIG: sym };
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
export function getWidgetConfig(slot) {
|
||||
var _a, _b, _c;
|
||||
const configSyms = getWidgetGetConfigSymbols(slot);
|
||||
const widget = slot.widget || {};
|
||||
return ((_c = (_a = (configSyms.CONFIG && widget[configSyms.CONFIG])) !== null && _a !== void 0 ? _a : (configSyms.GET_CONFIG && ((_b = widget[configSyms.GET_CONFIG]) === null || _b === void 0 ? void 0 : _b.call(widget)))) !== null && _c !== void 0 ? _c : ["*", {}]);
|
||||
}
|
||||
export function setWidgetConfig(slot, config) {
|
||||
var _a;
|
||||
if (!(slot === null || slot === void 0 ? void 0 : slot.widget))
|
||||
return;
|
||||
if (config) {
|
||||
const configSyms = getWidgetGetConfigSymbols(slot);
|
||||
const widget = slot.widget || {};
|
||||
if (configSyms.GET_CONFIG) {
|
||||
widget[configSyms.GET_CONFIG] = () => config;
|
||||
}
|
||||
else if (configSyms.CONFIG) {
|
||||
widget[configSyms.CONFIG] = config;
|
||||
}
|
||||
else {
|
||||
console.error("Cannot set widget Config. This is due to ComfyUI removing the ability to call legacy " +
|
||||
"JavaScript APIs that are now deprecated without new, supported APIs. It's possible " +
|
||||
"some things in rgthree-comfy do not work correctly. If you see this, please file a bug.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete slot.widget;
|
||||
}
|
||||
if ("link" in slot) {
|
||||
const link = app.graph.links[(_a = slot === null || slot === void 0 ? void 0 : slot.link) !== null && _a !== void 0 ? _a : -1];
|
||||
if (link) {
|
||||
const originNode = app.graph.getNodeById(link.origin_id);
|
||||
if (originNode && isPrimitiveNode(originNode)) {
|
||||
if (config) {
|
||||
originNode.recreateWidget();
|
||||
}
|
||||
else if (!app.configuringGraph) {
|
||||
originNode.disconnectOutput(0);
|
||||
originNode.onLastDisconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export function mergeIfValid(output, config2) {
|
||||
var _a;
|
||||
const config1 = getWidgetConfig(output);
|
||||
const customSpec = mergeInputSpec(config1, config2);
|
||||
if (customSpec) {
|
||||
setWidgetConfig(output, customSpec);
|
||||
}
|
||||
return (_a = customSpec === null || customSpec === void 0 ? void 0 : customSpec[1]) !== null && _a !== void 0 ? _a : null;
|
||||
}
|
||||
const mergeInputSpec = (spec1, spec2) => {
|
||||
const type1 = getInputSpecType(spec1);
|
||||
const type2 = getInputSpecType(spec2);
|
||||
if (type1 !== type2) {
|
||||
return null;
|
||||
}
|
||||
if (isIntInputSpec(spec1) || isFloatInputSpec(spec1)) {
|
||||
return mergeNumericInputSpec(spec1, spec2);
|
||||
}
|
||||
if (isComboInputSpec(spec1)) {
|
||||
return mergeComboInputSpec(spec1, spec2);
|
||||
}
|
||||
return mergeCommonInputSpec(spec1, spec2);
|
||||
};
|
||||
function getInputSpecType(inputSpec) {
|
||||
return isComboInputSpec(inputSpec) ? "COMBO" : inputSpec[0];
|
||||
}
|
||||
function isComboInputSpecV1(inputSpec) {
|
||||
return Array.isArray(inputSpec[0]);
|
||||
}
|
||||
function isIntInputSpec(inputSpec) {
|
||||
return inputSpec[0] === "INT";
|
||||
}
|
||||
function isFloatInputSpec(inputSpec) {
|
||||
return inputSpec[0] === "FLOAT";
|
||||
}
|
||||
function isComboInputSpecV2(inputSpec) {
|
||||
return inputSpec[0] === "COMBO";
|
||||
}
|
||||
function isComboInputSpec(inputSpec) {
|
||||
return isComboInputSpecV1(inputSpec) || isComboInputSpecV2(inputSpec);
|
||||
}
|
||||
const getRange = (options) => {
|
||||
var _a, _b;
|
||||
const min = (_a = options.min) !== null && _a !== void 0 ? _a : -Infinity;
|
||||
const max = (_b = options.max) !== null && _b !== void 0 ? _b : Infinity;
|
||||
return { min, max };
|
||||
};
|
||||
const mergeNumericInputSpec = (spec1, spec2) => {
|
||||
var _a, _b, _c, _d;
|
||||
const type = spec1[0];
|
||||
const options1 = (_a = spec1[1]) !== null && _a !== void 0 ? _a : {};
|
||||
const options2 = (_b = spec2[1]) !== null && _b !== void 0 ? _b : {};
|
||||
const range1 = getRange(options1);
|
||||
const range2 = getRange(options2);
|
||||
if (range1.min > range2.max || range1.max < range2.min) {
|
||||
return null;
|
||||
}
|
||||
const step1 = (_c = options1.step) !== null && _c !== void 0 ? _c : 1;
|
||||
const step2 = (_d = options2.step) !== null && _d !== void 0 ? _d : 1;
|
||||
const mergedOptions = {
|
||||
min: Math.max(range1.min, range2.min),
|
||||
max: Math.min(range1.max, range2.max),
|
||||
step: lcm(step1, step2),
|
||||
};
|
||||
return mergeCommonInputSpec([type, { ...options1, ...mergedOptions }], [type, { ...options2, ...mergedOptions }]);
|
||||
};
|
||||
const mergeComboInputSpec = (spec1, spec2) => {
|
||||
var _a, _b;
|
||||
const options1 = (_a = spec1[1]) !== null && _a !== void 0 ? _a : {};
|
||||
const options2 = (_b = spec2[1]) !== null && _b !== void 0 ? _b : {};
|
||||
const comboOptions1 = getComboSpecComboOptions(spec1);
|
||||
const comboOptions2 = getComboSpecComboOptions(spec2);
|
||||
const intersection = comboOptions1.filter((value) => comboOptions2.includes(value));
|
||||
if (intersection.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return mergeCommonInputSpec(["COMBO", { ...options1, options: intersection }], ["COMBO", { ...options2, options: intersection }]);
|
||||
};
|
||||
const mergeCommonInputSpec = (spec1, spec2) => {
|
||||
var _a, _b;
|
||||
const type = getInputSpecType(spec1);
|
||||
const options1 = (_a = spec1[1]) !== null && _a !== void 0 ? _a : {};
|
||||
const options2 = (_b = spec2[1]) !== null && _b !== void 0 ? _b : {};
|
||||
const compareKeys = [...new Set([...Object.keys(options1), ...Object.keys(options2)])].filter((key) => !IGNORE_KEYS.has(key));
|
||||
const mergeIsValid = compareKeys.every((key) => {
|
||||
const value1 = options1[key];
|
||||
const value2 = options2[key];
|
||||
return value1 === value2 || (value1 == null && value2 == null);
|
||||
});
|
||||
return mergeIsValid ? [type, { ...options1, ...options2 }] : null;
|
||||
};
|
||||
const IGNORE_KEYS = new Set([
|
||||
"default",
|
||||
"forceInput",
|
||||
"defaultInput",
|
||||
"control_after_generate",
|
||||
"multiline",
|
||||
"tooltip",
|
||||
"dynamicPrompts",
|
||||
]);
|
||||
function getComboSpecComboOptions(inputSpec) {
|
||||
var _a, _b;
|
||||
return (_b = (isComboInputSpecV2(inputSpec) ? (_a = inputSpec[1]) === null || _a === void 0 ? void 0 : _a.options : inputSpec[0])) !== null && _b !== void 0 ? _b : [];
|
||||
}
|
||||
const lcm = (a, b) => {
|
||||
return Math.abs(a * b) / gcd(a, b);
|
||||
};
|
||||
const gcd = (a, b) => {
|
||||
return b === 0 ? a : gcd(b, a % b);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
export function removeUnusedInputsFromEnd(node, minNumber = 1, nameMatch) {
|
||||
var _a;
|
||||
if (node.removed)
|
||||
return;
|
||||
for (let i = node.inputs.length - 1; i >= minNumber; i--) {
|
||||
if (!((_a = node.inputs[i]) === null || _a === void 0 ? void 0 : _a.link)) {
|
||||
if (!nameMatch || nameMatch.test(node.inputs[i].name)) {
|
||||
node.removeInput(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
55
custom_nodes/rgthree-comfy/web/comfyui/utils_menu.js
Normal file
55
custom_nodes/rgthree-comfy/web/comfyui/utils_menu.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { rgthreeApi } from "../../rgthree/common/rgthree_api.js";
|
||||
const PASS_THROUGH = function (item) {
|
||||
return item;
|
||||
};
|
||||
export async function showLoraChooser(event, callback, parentMenu, loras) {
|
||||
var _a, _b;
|
||||
const canvas = app.canvas;
|
||||
if (!loras) {
|
||||
loras = ["None", ...(await rgthreeApi.getLoras().then((loras) => loras.map((l) => l.file)))];
|
||||
}
|
||||
new LiteGraph.ContextMenu(loras, {
|
||||
event: event,
|
||||
parentMenu: parentMenu != null ? parentMenu : undefined,
|
||||
title: "Choose a lora",
|
||||
scale: Math.max(1, (_b = (_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1),
|
||||
className: "dark",
|
||||
callback,
|
||||
});
|
||||
}
|
||||
export function showNodesChooser(event, mapFn, callback, parentMenu) {
|
||||
var _a, _b;
|
||||
const canvas = app.canvas;
|
||||
const nodesOptions = app.graph._nodes
|
||||
.map(mapFn)
|
||||
.filter((e) => e != null);
|
||||
nodesOptions.sort((a, b) => {
|
||||
return a.value - b.value;
|
||||
});
|
||||
new LiteGraph.ContextMenu(nodesOptions, {
|
||||
event: event,
|
||||
parentMenu,
|
||||
title: "Choose a node id",
|
||||
scale: Math.max(1, (_b = (_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1),
|
||||
className: "dark",
|
||||
callback,
|
||||
});
|
||||
}
|
||||
export function showWidgetsChooser(event, node, mapFn, callback, parentMenu) {
|
||||
var _a, _b;
|
||||
const options = (node.widgets || [])
|
||||
.map(mapFn)
|
||||
.filter((e) => e != null);
|
||||
if (options.length) {
|
||||
const canvas = app.canvas;
|
||||
new LiteGraph.ContextMenu(options, {
|
||||
event,
|
||||
parentMenu,
|
||||
title: "Choose an input/widget",
|
||||
scale: Math.max(1, (_b = (_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1),
|
||||
className: "dark",
|
||||
callback,
|
||||
});
|
||||
}
|
||||
}
|
||||
291
custom_nodes/rgthree-comfy/web/comfyui/utils_widgets.js
Normal file
291
custom_nodes/rgthree-comfy/web/comfyui/utils_widgets.js
Normal file
@@ -0,0 +1,291 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { drawNodeWidget, drawWidgetButton, fitString, isLowQuality } from "./utils_canvas.js";
|
||||
export function drawLabelAndValue(ctx, label, value, width, posY, height, options) {
|
||||
var _a;
|
||||
const outerMargin = 15;
|
||||
const innerMargin = 10;
|
||||
const midY = posY + height / 2;
|
||||
ctx.save();
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
|
||||
const labelX = outerMargin + innerMargin + ((_a = options === null || options === void 0 ? void 0 : options.offsetLeft) !== null && _a !== void 0 ? _a : 0);
|
||||
ctx.fillText(label, labelX, midY);
|
||||
const valueXLeft = labelX + ctx.measureText(label).width + 7;
|
||||
const valueXRight = width - (outerMargin + innerMargin);
|
||||
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillText(fitString(ctx, value, valueXRight - valueXLeft), valueXRight, midY);
|
||||
ctx.restore();
|
||||
}
|
||||
export class RgthreeBaseWidget {
|
||||
constructor(name) {
|
||||
this.type = "custom";
|
||||
this.options = {};
|
||||
this.y = 0;
|
||||
this.last_y = 0;
|
||||
this.mouseDowned = null;
|
||||
this.isMouseDownedAndOver = false;
|
||||
this.hitAreas = {};
|
||||
this.downedHitAreasForMove = [];
|
||||
this.downedHitAreasForClick = [];
|
||||
this.name = name;
|
||||
}
|
||||
serializeValue(node, index) {
|
||||
return this.value;
|
||||
}
|
||||
clickWasWithinBounds(pos, bounds) {
|
||||
let xStart = bounds[0];
|
||||
let xEnd = xStart + (bounds.length > 2 ? bounds[2] : bounds[1]);
|
||||
const clickedX = pos[0] >= xStart && pos[0] <= xEnd;
|
||||
if (bounds.length === 2) {
|
||||
return clickedX;
|
||||
}
|
||||
return clickedX && pos[1] >= bounds[1] && pos[1] <= bounds[1] + bounds[3];
|
||||
}
|
||||
mouse(event, pos, node) {
|
||||
var _a, _b, _c;
|
||||
const canvas = app.canvas;
|
||||
if (event.type == "pointerdown") {
|
||||
this.mouseDowned = [...pos];
|
||||
this.isMouseDownedAndOver = true;
|
||||
this.downedHitAreasForMove.length = 0;
|
||||
this.downedHitAreasForClick.length = 0;
|
||||
let anyHandled = false;
|
||||
for (const part of Object.values(this.hitAreas)) {
|
||||
if (this.clickWasWithinBounds(pos, part.bounds)) {
|
||||
if (part.onMove) {
|
||||
this.downedHitAreasForMove.push(part);
|
||||
}
|
||||
if (part.onClick) {
|
||||
this.downedHitAreasForClick.push(part);
|
||||
}
|
||||
if (part.onDown) {
|
||||
const thisHandled = part.onDown.apply(this, [event, pos, node, part]);
|
||||
anyHandled = anyHandled || thisHandled == true;
|
||||
}
|
||||
part.wasMouseClickedAndIsOver = true;
|
||||
}
|
||||
}
|
||||
return (_a = this.onMouseDown(event, pos, node)) !== null && _a !== void 0 ? _a : anyHandled;
|
||||
}
|
||||
if (event.type == "pointerup") {
|
||||
if (!this.mouseDowned)
|
||||
return true;
|
||||
this.downedHitAreasForMove.length = 0;
|
||||
const wasMouseDownedAndOver = this.isMouseDownedAndOver;
|
||||
this.cancelMouseDown();
|
||||
let anyHandled = false;
|
||||
for (const part of Object.values(this.hitAreas)) {
|
||||
if (part.onUp && this.clickWasWithinBounds(pos, part.bounds)) {
|
||||
const thisHandled = part.onUp.apply(this, [event, pos, node, part]);
|
||||
anyHandled = anyHandled || thisHandled == true;
|
||||
}
|
||||
part.wasMouseClickedAndIsOver = false;
|
||||
}
|
||||
for (const part of this.downedHitAreasForClick) {
|
||||
if (this.clickWasWithinBounds(pos, part.bounds)) {
|
||||
const thisHandled = part.onClick.apply(this, [event, pos, node, part]);
|
||||
anyHandled = anyHandled || thisHandled == true;
|
||||
}
|
||||
}
|
||||
this.downedHitAreasForClick.length = 0;
|
||||
if (wasMouseDownedAndOver) {
|
||||
const thisHandled = this.onMouseClick(event, pos, node);
|
||||
anyHandled = anyHandled || thisHandled == true;
|
||||
}
|
||||
return (_b = this.onMouseUp(event, pos, node)) !== null && _b !== void 0 ? _b : anyHandled;
|
||||
}
|
||||
if (event.type == "pointermove") {
|
||||
this.isMouseDownedAndOver = !!this.mouseDowned;
|
||||
if (this.mouseDowned &&
|
||||
(pos[0] < 15 ||
|
||||
pos[0] > node.size[0] - 15 ||
|
||||
pos[1] < this.last_y ||
|
||||
pos[1] > this.last_y + LiteGraph.NODE_WIDGET_HEIGHT)) {
|
||||
this.isMouseDownedAndOver = false;
|
||||
}
|
||||
for (const part of Object.values(this.hitAreas)) {
|
||||
if (this.downedHitAreasForMove.includes(part)) {
|
||||
part.onMove.apply(this, [event, pos, node, part]);
|
||||
}
|
||||
if (this.downedHitAreasForClick.includes(part)) {
|
||||
part.wasMouseClickedAndIsOver = this.clickWasWithinBounds(pos, part.bounds);
|
||||
}
|
||||
}
|
||||
return (_c = this.onMouseMove(event, pos, node)) !== null && _c !== void 0 ? _c : true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
cancelMouseDown() {
|
||||
this.mouseDowned = null;
|
||||
this.isMouseDownedAndOver = false;
|
||||
this.downedHitAreasForMove.length = 0;
|
||||
}
|
||||
onMouseDown(event, pos, node) {
|
||||
return;
|
||||
}
|
||||
onMouseUp(event, pos, node) {
|
||||
return;
|
||||
}
|
||||
onMouseClick(event, pos, node) {
|
||||
return;
|
||||
}
|
||||
onMouseMove(event, pos, node) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
export class RgthreeBetterButtonWidget extends RgthreeBaseWidget {
|
||||
constructor(name, mouseClickCallback, label) {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
this.value = "";
|
||||
this.label = "";
|
||||
this.mouseClickCallback = mouseClickCallback;
|
||||
this.label = label || name;
|
||||
}
|
||||
draw(ctx, node, width, y, height) {
|
||||
drawWidgetButton(ctx, { size: [width - 30, height], pos: [15, y] }, this.label, this.isMouseDownedAndOver);
|
||||
}
|
||||
onMouseClick(event, pos, node) {
|
||||
return this.mouseClickCallback(event, pos, node);
|
||||
}
|
||||
}
|
||||
export class RgthreeBetterTextWidget extends RgthreeBaseWidget {
|
||||
constructor(name, value) {
|
||||
super(name);
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
draw(ctx, node, width, y, height) {
|
||||
const widgetData = drawNodeWidget(ctx, { size: [width, height], pos: [15, y] });
|
||||
if (!widgetData.lowQuality) {
|
||||
drawLabelAndValue(ctx, this.name, this.value, width, y, height);
|
||||
}
|
||||
}
|
||||
mouse(event, pos, node) {
|
||||
const canvas = app.canvas;
|
||||
if (event.type == "pointerdown") {
|
||||
canvas.prompt("Label", this.value, (v) => (this.value = v), event);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export class RgthreeDividerWidget extends RgthreeBaseWidget {
|
||||
constructor(widgetOptions) {
|
||||
super("divider");
|
||||
this.value = {};
|
||||
this.options = { serialize: false };
|
||||
this.type = "custom";
|
||||
this.widgetOptions = {
|
||||
marginTop: 7,
|
||||
marginBottom: 7,
|
||||
marginLeft: 15,
|
||||
marginRight: 15,
|
||||
color: LiteGraph.WIDGET_OUTLINE_COLOR,
|
||||
thickness: 1,
|
||||
};
|
||||
Object.assign(this.widgetOptions, widgetOptions || {});
|
||||
}
|
||||
draw(ctx, node, width, posY, h) {
|
||||
if (this.widgetOptions.thickness) {
|
||||
ctx.strokeStyle = this.widgetOptions.color;
|
||||
const x = this.widgetOptions.marginLeft;
|
||||
const y = posY + this.widgetOptions.marginTop;
|
||||
const w = width - this.widgetOptions.marginLeft - this.widgetOptions.marginRight;
|
||||
ctx.stroke(new Path2D(`M ${x} ${y} h ${w}`));
|
||||
}
|
||||
}
|
||||
computeSize(width) {
|
||||
return [
|
||||
width,
|
||||
this.widgetOptions.marginTop + this.widgetOptions.marginBottom + this.widgetOptions.thickness,
|
||||
];
|
||||
}
|
||||
}
|
||||
export class RgthreeLabelWidget extends RgthreeBaseWidget {
|
||||
constructor(name, widgetOptions) {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
this.options = { serialize: false };
|
||||
this.value = "";
|
||||
this.widgetOptions = {};
|
||||
this.posY = 0;
|
||||
Object.assign(this.widgetOptions, widgetOptions);
|
||||
}
|
||||
update(widgetOptions) {
|
||||
Object.assign(this.widgetOptions, widgetOptions);
|
||||
}
|
||||
draw(ctx, node, width, posY, height) {
|
||||
var _a;
|
||||
this.posY = posY;
|
||||
ctx.save();
|
||||
let text = (_a = this.widgetOptions.text) !== null && _a !== void 0 ? _a : this.name;
|
||||
if (typeof text === "function") {
|
||||
text = text();
|
||||
}
|
||||
ctx.textAlign = this.widgetOptions.align || "left";
|
||||
ctx.fillStyle = this.widgetOptions.color || LiteGraph.WIDGET_TEXT_COLOR;
|
||||
const oldFont = ctx.font;
|
||||
if (this.widgetOptions.italic) {
|
||||
ctx.font = "italic " + ctx.font;
|
||||
}
|
||||
if (this.widgetOptions.size) {
|
||||
ctx.font = ctx.font.replace(/\d+px/, `${this.widgetOptions.size}px`);
|
||||
}
|
||||
const midY = posY + height / 2;
|
||||
ctx.textBaseline = "middle";
|
||||
if (this.widgetOptions.align === "center") {
|
||||
ctx.fillText(text, node.size[0] / 2, midY);
|
||||
}
|
||||
else {
|
||||
ctx.fillText(text, 15, midY);
|
||||
}
|
||||
ctx.font = oldFont;
|
||||
if (this.widgetOptions.actionLabel === "__PLUS_ICON__") {
|
||||
const plus = new Path2D(`M${node.size[0] - 15 - 2} ${posY + 7} v4 h-4 v4 h-4 v-4 h-4 v-4 h4 v-4 h4 v4 h4 z`);
|
||||
ctx.lineJoin = "round";
|
||||
ctx.lineCap = "round";
|
||||
ctx.fillStyle = "#3a3";
|
||||
ctx.strokeStyle = "#383";
|
||||
ctx.fill(plus);
|
||||
ctx.stroke(plus);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
mouse(event, nodePos, node) {
|
||||
if (event.type !== "pointerdown" ||
|
||||
isLowQuality() ||
|
||||
!this.widgetOptions.actionLabel ||
|
||||
!this.widgetOptions.actionCallback) {
|
||||
return false;
|
||||
}
|
||||
const pos = [nodePos[0], nodePos[1] - this.posY];
|
||||
const rightX = node.size[0] - 15;
|
||||
if (pos[0] > rightX || pos[0] < rightX - 16) {
|
||||
return false;
|
||||
}
|
||||
this.widgetOptions.actionCallback(event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
export class RgthreeInvisibleWidget extends RgthreeBaseWidget {
|
||||
constructor(name, type, value, serializeValueFn) {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
this.value = value;
|
||||
this.serializeValueFn = serializeValueFn;
|
||||
}
|
||||
draw() {
|
||||
return;
|
||||
}
|
||||
computeSize(width) {
|
||||
return [0, 0];
|
||||
}
|
||||
serializeValue(node, index) {
|
||||
return this.serializeValueFn != null
|
||||
? this.serializeValueFn(node, index)
|
||||
: super.serializeValue(node, index);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user