Changed name of mirror node to try discourage manual adding.

Fixed callback functions and restoring callbacks when the last mirror is deleted.
If the original is deleted then the mirror tells the user that it is broken.
Disabled creating a mirror of something with no widgets.
This commit is contained in:
GroxicTinch
2025-05-16 15:44:00 +08:00
parent 79b714a3c0
commit f77ed52acb
2 changed files with 110 additions and 51 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ NODE_CLASS_MAPPINGS = {
}
NODE_DISPLAY_NAME_MAPPINGS = {
"UINode": "UINode [GroxicTinch]",
"UINode": "Mirror node [Do not add manually!]",
}
WEB_DIRECTORY = "./web/comfyui"
+109 -50
View File
@@ -1,9 +1,7 @@
import { app } from "../../scripts/app.js";
// [FIXME] Delete the UINode if the original node is deleted
// [FIXME] Order of widgets doesnt persist properly when refreshing page
// [FIXME] elements arent working (preview image, customtext)
// [FIXME] adding multiple mirrors will cause callback restoring to break
// [TODO] Add a way to get taken to the original node from the UINode
// [TODO] Combine many nodes into one UINode
@@ -27,6 +25,9 @@ function createMirror(node) {
uiNode.inputs = [];
uiNode.outputs = [];
node.properties._easyui_mirrorNodeIds = node.properties._easyui_mirrorNodeIds || [];
node.properties._easyui_mirrorNodeIds.push(uiNode.id);
app.canvas.setDirty(true);
app.canvas.draw(true);
@@ -38,7 +39,7 @@ app.registerExtension({
beforeRegisterNodeDef(nodeTypeInfo) {
const proto = nodeTypeInfo.prototype;
const original = proto.getExtraMenuOptions;
const original = proto.getExtraMenuOptions;
// hide the UINode from the node list
if (nodeTypeInfo.comfyClass === 'UINode') {
@@ -50,14 +51,16 @@ app.registerExtension({
if (original) {
original.apply(this, arguments);
}
options.push(null); // inserts a divider
// 2) if it's a UINode, Don't add “Create Mirror Copy”
if (this.type !== 'UINode') {
options.push({
content: '🖼️ EasyUI – Create Mirror Copy',
callback: () => createMirror(this)
});
if(this.widgets.length > 0) {
options.push({
content: '🖼️ EasyUI – Create Mirror Copy',
callback: () => createMirror(this)
});
}
options.push({
content: '🖼️ EasyUI [DEBUG]',
callback: () => {
@@ -82,15 +85,15 @@ app.registerExtension({
// Add configure override for UINode to restore widgets on load
if (nodeTypeInfo.comfyClass === 'UINode') {
const originalOnAdded = proto.onAdded;
const originalOnRemoved = proto.onRemoved;
const originalConfigure = proto.configure;
const originalOnRemoved = proto.onRemoved;
const originalDrawTitleBox = proto.drawTitleBox;
proto.onAdded = function() {
// call any existing onAdded first
if (originalOnAdded) originalOnAdded.apply(this, arguments);
// if this UINode was NOT created via createMirror (no original_node_id),
// add a label or change its title
// if this UINode was NOT created via createMirror (no original_node_id), change title
setTimeout(() => {
// Need timeout or else this.properties.original_node_id isnt set in time for the mirrored uinodes
if (!this.properties?.original_node_id) {
@@ -100,8 +103,8 @@ app.registerExtension({
element.innerText = 'This UINode is not linked. It should not be created manually. Please right click on a node and select "🖼️ EasyUI – Create Mirror Copy"';
this.addDOMWidget('Notice', 'customtext', element);
this.width = 355;
this.height = 122;
this.size[0] = 355;
this.size[1] = 122;
// force a redraw so the new widget/title shows up immediately
app.canvas.setDirty(true);
@@ -129,23 +132,49 @@ app.registerExtension({
};
proto.onRemoved = function() {
// 1) restore any original callbacks
const originalNodeId = this.properties.original_node_id;
const originalNode = app.canvas.graph.getNodeById(originalNodeId);
if (originalNode && originalNode.widgets) {
originalNode.widgets.forEach(w => {
if (w._easyui_originalCallback) {
w.callback = w._easyui_originalCallback;
delete w._easyui_originalCallback;
if (w._easyui_mirrorCallbacks && w._easyui_mirrorCallbacks.has(this.id)) {
w._easyui_mirrorCallbacks.delete(this.id);
if (w._easyui_mirrorCallbacks.size === 0) {
if (w._easyui_originalCallback) {
w.callback = w._easyui_originalCallback;
delete w._easyui_originalCallback;
}
delete w._easyui_mirrorCallbacks;
}
}
});
}
// 2) call the original onRemoved if there was one
// Call the original onRemoved if there was one
if (originalOnRemoved) {
originalOnRemoved.apply(this, arguments);
}
}
proto.drawTitleBox = function(ctx) {
const originalNodeId = this.properties.original_node_id;
if (originalNodeId) {
const originalNode = app.canvas.graph.getNodeById(originalNodeId);
if (!originalNode) {
console.warn(`Original node with ID ${originalNodeId} not found.`);
this.title = '❌ Orphaned UINode';
this.properties.original_node_id = null;
this.widgets = [];
const element = document.createElement('span');
element.innerText = 'This UINode is not linked. The original Node was deleted';
this.addDOMWidget('Notice', 'customtext', element);
}
}
if (originalDrawTitleBox) {
originalDrawTitleBox.apply(this, arguments);
}
}
}
}
});
@@ -316,7 +345,7 @@ function cloneWidgets(originalNode, uiNode) {
uiNode.properties.hiddenWidgets = uiNode.properties.hiddenWidgets || [];
uiNode.properties.widgetOrder = uiNode.properties.widgetOrder || [];
if(originalNode && originalNode.widgets) {
if (originalNode && originalNode.widgets) {
// Create a map of original widgets for easier lookup
const widgetMap = new Map(originalNode.widgets.map(widget => [widget.name, widget]));
@@ -324,12 +353,12 @@ function cloneWidgets(originalNode, uiNode) {
const orderedWidgets = uiNode.properties.widgetOrder
.map(name => widgetMap.get(name))
.filter(widget => widget !== undefined);
// Add any remaining widgets that weren't in the saved order
const remainingWidgets = originalNode.widgets.filter(
widget => !uiNode.properties.widgetOrder.includes(widget.name)
);
// Combine ordered and remaining widgets
const allWidgets = [...orderedWidgets, ...remainingWidgets];
@@ -337,7 +366,7 @@ function cloneWidgets(originalNode, uiNode) {
const type = origW.type || 'string';
const options = origW.options || {};
try {
// Example: Mark some widgets to be hidden initially
// Do not show nodes that are currently inputs
const isHidden = uiNode.properties.hiddenWidgets.includes(origW.name);
if (origW.computedDisabled || isHidden || ignoredWidgets.includes(type)) {
if (!isHidden) {
@@ -345,11 +374,15 @@ function cloneWidgets(originalNode, uiNode) {
}
return;
}
const oldCallback = origW.callback;
origW._easyui_originalCallback = oldCallback;
// Store original callback and initialize mirror callbacks
if (!origW._easyui_originalCallback) {
origW._easyui_originalCallback = origW.callback;
origW._easyui_mirrorCallbacks = new Map();
}
var newW;
switch (type) {
case 'custom':
// [TODO]
@@ -358,29 +391,36 @@ function cloneWidgets(originalNode, uiNode) {
const element = origW.element.cloneNode(true);
newW = uiNode.addDOMWidget(origW.name, type, element);
newW.inputEl = element;
// initialize value
newW.isSyncing = false;
// Initialize value
newW.value = origW.value;
// 2) Listen on the cloned textarea → push back to original
// Mirror → Original
element.addEventListener('input', e => {
if (newW.isSyncing) return;
newW.isSyncing = true;
origW.element.value = e.target.value;
origW.value = e.target.value;
if (typeof origW.callback === 'function') {
origW.callback.call(origW, e.target.value);
}
app.canvas.setDirty(true);
newW.isSyncing = false;
});
// 3) Listen on the original textarea → push forward to clone
origW.element.addEventListener('input', e => {
if (newW.isSyncing) return;
newW.isSyncing = true;
element.value = e.target.value;
newW.value = e.target.value;
app.canvas.setDirty(true);
newW.isSyncing = false;
});
// Original → Mirror
if (!origW._easyui_mirrorCallbacks.has(uiNode.id)) {
origW._easyui_mirrorCallbacks.set(uiNode.id, function(v) {
if (!newW.isSyncing) {
newW.isSyncing = true;
element.value = v;
newW.value = v;
newW.isSyncing = false;
}
});
}
// Note: Original event listener should be set once elsewhere
break;
default:
newW = uiNode.addWidget(
@@ -388,24 +428,43 @@ function cloneWidgets(originalNode, uiNode) {
origW.name,
origW.value,
function(...args) {
if (newW.isSyncing) return;
newW.isSyncing = true;
const v = args[0];
origW.value = v;
if (typeof oldCallback === 'function') {
oldCallback.call(origW, ...args);
if (typeof origW.callback === 'function') {
origW.callback.call(origW, v);
}
app.canvas.setDirty(true);
newW.isSyncing = false;
},
options
);
newW.isSyncing = false;
origW.callback = function(...args) {
const v = args[0];
if (typeof oldCallback === 'function') {
oldCallback.call(this, ...args);
}
newW.value = v;
app.canvas.setDirty(true);
};
if (origW._easyui_mirrorCallbacks.size === 0) {
origW.callback = function(...args) {
for (const [key, value] of origW._easyui_mirrorCallbacks) {
if (typeof value === 'function') {
value.call(this, args[0]);
}
}
if (typeof origW._easyui_originalCallback === 'function') {
origW._easyui_originalCallback.call(this, ...args);
}
app.canvas.setDirty(true);
};
}
if (!origW._easyui_mirrorCallbacks.has(uiNode.id)) {
origW._easyui_mirrorCallbacks.set(uiNode.id, function(v) {
if (!newW.isSyncing) {
newW.isSyncing = true;
newW.value = v;
newW.isSyncing = false;
}
});
}
break;
}
} catch (err) {