Files
logicalor-comfyui_multi_rep…/js/multi_replace.js
T
2025-12-16 09:31:49 +10:00

186 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { app } from "../../scripts/app.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
/**
* ComfyUI Multi-Replace Extension
* Handles dynamic addition of find/replace pair inputs
*/
app.registerExtension({
name: "comfyui-multi-replace",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "FindReplacePairs") {
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
if (origOnNodeCreated) {
origOnNodeCreated.apply(this, arguments);
}
this.pairCount = 1;
this.serialize_widgets = true;
// Add the "Add Pair" button
this.addWidget("button", "➕ Add Pair", null, () => {
this.addNewPair();
});
// Add the "Remove Last Pair" button
this.addWidget("button", "➖ Remove Last Pair", null, () => {
this.removeLastPair();
});
// Store reference to button widgets
this.addPairButton = this.widgets[this.widgets.length - 2];
this.removePairButton = this.widgets[this.widgets.length - 1];
this.updateRemoveButtonState();
};
// Add a new find/replace pair
nodeType.prototype.addNewPair = function() {
this.pairCount++;
const idx = this.pairCount;
// Insert new widgets before the buttons
const buttonIndex = this.widgets.indexOf(this.addPairButton);
// Create find widget
const findWidget = ComfyWidgets.STRING(
this,
`find_${idx}`,
["STRING", { default: "", multiline: false }],
app
).widget;
// Create replace widget
const replaceWidget = ComfyWidgets.STRING(
this,
`replace_${idx}`,
["STRING", { default: "", multiline: false }],
app
).widget;
// Move the new widgets before the buttons
this.widgets.splice(this.widgets.length - 2, 2);
this.widgets.splice(buttonIndex, 0, findWidget, replaceWidget);
// Re-add buttons at end
this.widgets.push(this.addPairButton, this.removePairButton);
this.updateRemoveButtonState();
this.setSize(this.computeSize());
app.graph.setDirtyCanvas(true, true);
};
// Remove the last find/replace pair
nodeType.prototype.removeLastPair = function() {
if (this.pairCount <= 1) return;
const idx = this.pairCount;
const findName = `find_${idx}`;
const replaceName = `replace_${idx}`;
// Remove widgets
this.widgets = this.widgets.filter(w =>
w.name !== findName && w.name !== replaceName
);
// Remove inputs if they were converted
const findInputIdx = this.inputs?.findIndex(i => i.name === findName);
if (findInputIdx !== undefined && findInputIdx >= 0) {
this.removeInput(findInputIdx);
}
const replaceInputIdx = this.inputs?.findIndex(i => i.name === replaceName);
if (replaceInputIdx !== undefined && replaceInputIdx >= 0) {
this.removeInput(replaceInputIdx);
}
this.pairCount--;
this.updateRemoveButtonState();
this.setSize(this.computeSize());
app.graph.setDirtyCanvas(true, true);
};
// Update remove button enabled state
nodeType.prototype.updateRemoveButtonState = function() {
if (this.removePairButton) {
this.removePairButton.disabled = this.pairCount <= 1;
}
};
// Override getExtraMenuOptions to add right-click options
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function(_, options) {
if (origGetExtraMenuOptions) {
origGetExtraMenuOptions.apply(this, arguments);
}
options.unshift(
{
content: "Add Find/Replace Pair",
callback: () => this.addNewPair()
},
{
content: "Remove Last Pair",
disabled: this.pairCount <= 1,
callback: () => this.removeLastPair()
},
null // separator
);
};
// Handle serialization to save dynamic widgets
const origOnSerialize = nodeType.prototype.onSerialize;
nodeType.prototype.onSerialize = function(info) {
if (origOnSerialize) {
origOnSerialize.apply(this, arguments);
}
info.pairCount = this.pairCount;
};
// Handle deserialization to restore dynamic widgets
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function(info) {
// Restore additional pairs before calling original
if (info.pairCount && info.pairCount > 1) {
const targetCount = info.pairCount;
// Add pairs up to saved count
while (this.pairCount < targetCount) {
this.addNewPair();
}
}
if (origOnConfigure) {
origOnConfigure.apply(this, arguments);
}
// Restore widget values from saved state
if (info.widgets_values) {
let valueIdx = 0;
for (const widget of this.widgets) {
if (widget.type !== "button" && valueIdx < info.widgets_values.length) {
widget.value = info.widgets_values[valueIdx];
valueIdx++;
}
}
}
};
// Allow converting widgets to inputs for connections
const origOnInputAdded = nodeType.prototype.onInputAdded;
nodeType.prototype.onInputAdded = function(input) {
if (origOnInputAdded) {
origOnInputAdded.apply(this, arguments);
}
};
}
},
async nodeCreated(node) {
// Nothing special needed - widgets automatically support "Convert to Input"
}
});