mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-14 15:32:35 +08:00
Restore more core extensions
This commit is contained in:
parent
229bd2765c
commit
90edf93e57
@ -8,19 +8,15 @@ const id = "Comfy.InvertMenuScrolling";
|
|||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: id,
|
name: id,
|
||||||
init() {
|
init() {
|
||||||
let invert = false;
|
|
||||||
hook(LiteGraph, "onContextMenuCreated", (orig, contextMenu) => {
|
hook(LiteGraph, "onContextMenuCreated", (orig, contextMenu) => {
|
||||||
orig?.(contextMenu);
|
orig?.(contextMenu);
|
||||||
contextMenu.invert_scrolling = invert;
|
contextMenu.options.invert_scrolling = localStorage[`Comfy.Settings.${id}`] === "true";
|
||||||
})
|
})
|
||||||
app.ui.settings.addSetting({
|
app.ui.settings.addSetting({
|
||||||
id,
|
id,
|
||||||
name: "Invert Menu Scrolling",
|
name: "Invert Menu Scrolling",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
defaultValue: false,
|
defaultValue: false
|
||||||
onChange(value) {
|
|
||||||
invert = value;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const ext = {
|
|||||||
})),
|
})),
|
||||||
onChange(value) {
|
onChange(value) {
|
||||||
app.canvas.links_render_mode = +value;
|
app.canvas.links_render_mode = +value;
|
||||||
app.graph.setDirtyCanvas(true);
|
app.canvas.draw(true, true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app, ComfyApp } from "../../scripts/app.js";
|
||||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||||
import { ComfyApp } from "../../scripts/app.js";
|
|
||||||
import { api } from "../../scripts/api.js"
|
import { api } from "../../scripts/api.js"
|
||||||
import { ClipspaceDialog } from "./clipspace.js";
|
import { ClipspaceDialog } from "./clipspace.js";
|
||||||
|
|
||||||
@ -657,4 +656,4 @@ app.registerExtension({
|
|||||||
const context_predicate = () => ComfyApp.clipspace && ComfyApp.clipspace.imgs && ComfyApp.clipspace.imgs.length > 0
|
const context_predicate = () => ComfyApp.clipspace && ComfyApp.clipspace.imgs && ComfyApp.clipspace.imgs.length > 0
|
||||||
ClipspaceDialog.registerButton("MaskEditor", context_predicate, ComfyApp.open_maskeditor);
|
ClipspaceDialog.registerButton("MaskEditor", context_predicate, ComfyApp.open_maskeditor);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||||
import { ComfyGraphNode } from "../../scripts/graphNode.js";
|
import { ComfyGraphNode } from "../../scripts/graphNode.js";
|
||||||
import { LiteGraph } from "../../lib/litegraph.core.js"
|
import { LiteGraph, LGraphCanvas } from "../../lib/litegraph.core.js"
|
||||||
|
|
||||||
// Node that add notes to your project
|
// Node that add notes to your project
|
||||||
|
|
||||||
@ -12,7 +12,8 @@ app.registerExtension({
|
|||||||
color=LGraphCanvas.node_colors.yellow.color;
|
color=LGraphCanvas.node_colors.yellow.color;
|
||||||
bgcolor=LGraphCanvas.node_colors.yellow.bgcolor;
|
bgcolor=LGraphCanvas.node_colors.yellow.bgcolor;
|
||||||
groupcolor = LGraphCanvas.node_colors.yellow.groupcolor;
|
groupcolor = LGraphCanvas.node_colors.yellow.groupcolor;
|
||||||
constructor() {
|
constructor(title) {
|
||||||
|
super(title)
|
||||||
if (!this.properties) {
|
if (!this.properties) {
|
||||||
this.properties = {};
|
this.properties = {};
|
||||||
this.properties.text="";
|
this.properties.text="";
|
||||||
@ -30,11 +31,10 @@ app.registerExtension({
|
|||||||
LiteGraph.registerNodeType({
|
LiteGraph.registerNodeType({
|
||||||
class: NoteNode,
|
class: NoteNode,
|
||||||
title_mode: LiteGraph.NORMAL_TITLE,
|
title_mode: LiteGraph.NORMAL_TITLE,
|
||||||
|
category: "utils",
|
||||||
type: "Note",
|
type: "Note",
|
||||||
title: "Note",
|
title: "Note",
|
||||||
collapsable: true,
|
collapsable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
NoteNode.category = "utils";
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,7 +8,7 @@ app.registerExtension({
|
|||||||
name: "Comfy.RerouteNode",
|
name: "Comfy.RerouteNode",
|
||||||
registerCustomNodes() {
|
registerCustomNodes() {
|
||||||
class RerouteNode extends ComfyGraphNode {
|
class RerouteNode extends ComfyGraphNode {
|
||||||
constructor() {
|
onNodeCreated() {
|
||||||
if (!this.properties) {
|
if (!this.properties) {
|
||||||
this.properties = {};
|
this.properties = {};
|
||||||
}
|
}
|
||||||
@ -18,137 +18,137 @@ app.registerExtension({
|
|||||||
this.addInput("", "*");
|
this.addInput("", "*");
|
||||||
this.addOutput(this.properties.showOutputText ? "*" : "", "*");
|
this.addOutput(this.properties.showOutputText ? "*" : "", "*");
|
||||||
|
|
||||||
this.onConnectionsChange = function (type, index, connected, link_info) {
|
// This node is purely frontend and does not impact the resulting prompt so should not be serialized
|
||||||
this.applyOrientation();
|
this.isVirtualNode = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent multiple connections to different types when we have no input
|
onConnectionsChange(type, index, connected, link_info) {
|
||||||
if (connected && type === LiteGraph.OUTPUT) {
|
this.applyOrientation();
|
||||||
// Ignore wildcard nodes as these will be updated to real types
|
|
||||||
const types = new Set(this.outputs[0].links.map((l) => app.graph.links[l].type).filter((t) => t !== "*"));
|
// Prevent multiple connections to different types when we have no input
|
||||||
if (types.size > 1) {
|
if (connected && type === LiteGraph.OUTPUT) {
|
||||||
const linksToDisconnect = [];
|
// Ignore wildcard nodes as these will be updated to real types
|
||||||
for (let i = 0; i < this.outputs[0].links.length - 1; i++) {
|
const types = new Set(this.outputs[0].links.map((l) => app.graph.links[l].type).filter((t) => t !== "*"));
|
||||||
const linkId = this.outputs[0].links[i];
|
if (types.size > 1) {
|
||||||
const link = app.graph.links[linkId];
|
const linksToDisconnect = [];
|
||||||
linksToDisconnect.push(link);
|
for (let i = 0; i < this.outputs[0].links.length - 1; i++) {
|
||||||
}
|
const linkId = this.outputs[0].links[i];
|
||||||
for (const link of linksToDisconnect) {
|
const link = app.graph.links[linkId];
|
||||||
const node = app.graph.getNodeById(link.target_id);
|
linksToDisconnect.push(link);
|
||||||
node.disconnectInput(link.target_slot);
|
}
|
||||||
}
|
for (const link of linksToDisconnect) {
|
||||||
|
const node = app.graph.getNodeById(link.target_id);
|
||||||
|
node.disconnectInput(link.target_slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find root input
|
// Find root input
|
||||||
let currentNode = this;
|
let currentNode = this;
|
||||||
let updateNodes = [];
|
let updateNodes = [];
|
||||||
let inputType = null;
|
let inputType = null;
|
||||||
let inputNode = null;
|
let inputNode = null;
|
||||||
while (currentNode) {
|
while (currentNode) {
|
||||||
updateNodes.unshift(currentNode);
|
updateNodes.unshift(currentNode);
|
||||||
const linkId = currentNode.inputs[0].link;
|
const linkId = currentNode.inputs[0].link;
|
||||||
if (linkId !== null) {
|
if (linkId !== null) {
|
||||||
const link = app.graph.links[linkId];
|
const link = app.graph.links[linkId];
|
||||||
const node = app.graph.getNodeById(link.origin_id);
|
const node = app.graph.getNodeById(link.origin_id);
|
||||||
const type = node.constructor.type;
|
const type = node.constructor.type;
|
||||||
if (type === "Reroute") {
|
if (type === "Reroute") {
|
||||||
if (node === this) {
|
if (node === this) {
|
||||||
// We've found a circle
|
// We've found a circle
|
||||||
currentNode.disconnectInput(link.target_slot);
|
currentNode.disconnectInput(link.target_slot);
|
||||||
currentNode = null;
|
currentNode = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Move the previous node
|
// Move the previous node
|
||||||
currentNode = node;
|
currentNode = node;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We've found the end
|
|
||||||
inputNode = currentNode;
|
|
||||||
inputType = node.outputs[link.origin_slot]?.type ?? null;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This path has no input node
|
// We've found the end
|
||||||
currentNode = null;
|
inputNode = currentNode;
|
||||||
|
inputType = node.outputs[link.origin_slot]?.type ?? null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// This path has no input node
|
||||||
|
currentNode = null;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find all outputs
|
// Find all outputs
|
||||||
const nodes = [this];
|
const nodes = [this];
|
||||||
let outputType = null;
|
let outputType = null;
|
||||||
while (nodes.length) {
|
while (nodes.length) {
|
||||||
currentNode = nodes.pop();
|
currentNode = nodes.pop();
|
||||||
const outputs = (currentNode.outputs ? currentNode.outputs[0].links : []) || [];
|
const outputs = (currentNode.outputs ? currentNode.outputs[0].links : []) || [];
|
||||||
if (outputs.length) {
|
if (outputs.length) {
|
||||||
for (const linkId of outputs) {
|
for (const linkId of outputs) {
|
||||||
const link = app.graph.links[linkId];
|
const link = app.graph.links[linkId];
|
||||||
|
|
||||||
// When disconnecting sometimes the link is still registered
|
// When disconnecting sometimes the link is still registered
|
||||||
if (!link) continue;
|
if (!link) continue;
|
||||||
|
|
||||||
const node = app.graph.getNodeById(link.target_id);
|
const node = app.graph.getNodeById(link.target_id);
|
||||||
const type = node.constructor.type;
|
const type = node.constructor.type;
|
||||||
|
|
||||||
if (type === "Reroute") {
|
if (type === "Reroute") {
|
||||||
// Follow reroute nodes
|
// Follow reroute nodes
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
updateNodes.push(node);
|
updateNodes.push(node);
|
||||||
|
} else {
|
||||||
|
// We've found an output
|
||||||
|
const nodeOutType = node.inputs && node.inputs[link?.target_slot] && node.inputs[link.target_slot].type ? node.inputs[link.target_slot].type : null;
|
||||||
|
if (inputType && nodeOutType !== inputType) {
|
||||||
|
// The output doesnt match our input so disconnect it
|
||||||
|
node.disconnectInput(link.target_slot);
|
||||||
} else {
|
} else {
|
||||||
// We've found an output
|
outputType = nodeOutType;
|
||||||
const nodeOutType = node.inputs && node.inputs[link?.target_slot] && node.inputs[link.target_slot].type ? node.inputs[link.target_slot].type : null;
|
|
||||||
if (inputType && nodeOutType !== inputType) {
|
|
||||||
// The output doesnt match our input so disconnect it
|
|
||||||
node.disconnectInput(link.target_slot);
|
|
||||||
} else {
|
|
||||||
outputType = nodeOutType;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// No more outputs for this path
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// No more outputs for this path
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const displayType = inputType || outputType || "*";
|
const displayType = inputType || outputType || "*";
|
||||||
const color = app.canvas.link_type_colors[displayType];
|
const color = app.canvas.link_type_colors[displayType];
|
||||||
|
|
||||||
// Update the types of each node
|
// Update the types of each node
|
||||||
for (const node of updateNodes) {
|
for (const node of updateNodes) {
|
||||||
// If we dont have an input type we are always wildcard but we'll show the output type
|
// If we dont have an input type we are always wildcard but we'll show the output type
|
||||||
// This lets you change the output link to a different type and all nodes will update
|
// This lets you change the output link to a different type and all nodes will update
|
||||||
node.outputs[0].type = inputType || "*";
|
node.outputs[0].type = inputType || "*";
|
||||||
node.__outputType = displayType;
|
node.__outputType = displayType;
|
||||||
node.outputs[0].name = node.properties.showOutputText ? displayType : "";
|
node.outputs[0].name = node.properties.showOutputText ? displayType : "";
|
||||||
node.size = node.computeSize();
|
node.size = node.computeSize();
|
||||||
node.applyOrientation();
|
node.applyOrientation();
|
||||||
|
|
||||||
for (const l of node.outputs[0].links || []) {
|
for (const l of node.outputs[0].links || []) {
|
||||||
const link = app.graph.links[l];
|
const link = app.graph.links[l];
|
||||||
if (link) {
|
|
||||||
link.color = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputNode) {
|
|
||||||
const link = app.graph.links[inputNode.inputs[0].link];
|
|
||||||
if (link) {
|
if (link) {
|
||||||
link.color = color;
|
link.color = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
this.clone = function () {
|
if (inputNode) {
|
||||||
const cloned = RerouteNode.prototype.clone.apply(this);
|
const link = app.graph.links[inputNode.inputs[0].link];
|
||||||
cloned.removeOutput(0);
|
if (link) {
|
||||||
cloned.addOutput(this.properties.showOutputText ? "*" : "", "*");
|
link.color = color;
|
||||||
cloned.size = cloned.computeSize();
|
}
|
||||||
return cloned;
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// This node is purely frontend and does not impact the resulting prompt so should not be serialized
|
clone() {
|
||||||
this.isVirtualNode = true;
|
const cloned = RerouteNode.prototype.clone.apply(this);
|
||||||
|
cloned.removeOutput(0);
|
||||||
|
cloned.addOutput(this.properties.showOutputText ? "*" : "", "*");
|
||||||
|
cloned.size = cloned.computeSize();
|
||||||
|
return cloned;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExtraMenuOptions(_, options) {
|
getExtraMenuOptions(_, options) {
|
||||||
@ -224,11 +224,10 @@ app.registerExtension({
|
|||||||
LiteGraph.registerNodeType({
|
LiteGraph.registerNodeType({
|
||||||
class: RerouteNode,
|
class: RerouteNode,
|
||||||
title_mode: LiteGraph.NO_TITLE,
|
title_mode: LiteGraph.NO_TITLE,
|
||||||
|
category: "utils",
|
||||||
type: "Reroute",
|
type: "Reroute",
|
||||||
title: "Reroute",
|
title: "Reroute",
|
||||||
collapsable: false,
|
collapsable: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
RerouteNode.category = "utils";
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -28,7 +28,7 @@ app.registerExtension({
|
|||||||
slot_types_default_out: {},
|
slot_types_default_out: {},
|
||||||
slot_types_default_in: {},
|
slot_types_default_in: {},
|
||||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||||
var nodeId = nodeData.name;
|
var nodeId = nodeData.name;
|
||||||
var inputs = [];
|
var inputs = [];
|
||||||
inputs = nodeData["input"]["required"]; //only show required inputs to reduce the mess also not logical to create node with optional inputs
|
inputs = nodeData["input"]["required"]; //only show required inputs to reduce the mess also not logical to create node with optional inputs
|
||||||
for (const inputKey in inputs) {
|
for (const inputKey in inputs) {
|
||||||
@ -79,7 +79,6 @@ app.registerExtension({
|
|||||||
this.setDefaults(maxNum);
|
this.setDefaults(maxNum);
|
||||||
},
|
},
|
||||||
setDefaults(maxNum) {
|
setDefaults(maxNum) {
|
||||||
|
|
||||||
LiteGraph.slot_types_default_out = {};
|
LiteGraph.slot_types_default_out = {};
|
||||||
LiteGraph.slot_types_default_in = {};
|
LiteGraph.slot_types_default_in = {};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { LiteGraph } from "../../lib/litegraph.core.js"
|
import { LiteGraph, LGraphCanvas } from "../../lib/litegraph.core.js"
|
||||||
|
|
||||||
// Shift + drag/resize to snap to grid
|
// Shift + drag/resize to snap to grid
|
||||||
|
|
||||||
@ -22,69 +22,5 @@ app.registerExtension({
|
|||||||
LiteGraph.CANVAS_GRID_SIZE = +value;
|
LiteGraph.CANVAS_GRID_SIZE = +value;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// After moving a node, if the shift key is down align it to grid
|
|
||||||
const onNodeMoved = app.canvas.onNodeMoved;
|
|
||||||
app.canvas.onNodeMoved = function (node) {
|
|
||||||
const r = onNodeMoved?.apply(this, arguments);
|
|
||||||
|
|
||||||
if (app.shiftDown) {
|
|
||||||
// Ensure all selected nodes are realigned
|
|
||||||
for (const id in this.selected_nodes) {
|
|
||||||
this.selected_nodes[id].alignToGrid();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
};
|
|
||||||
|
|
||||||
// When a node is added, add a resize handler to it so we can fix align the size with the grid
|
|
||||||
const onNodeAdded = app.graph.onNodeAdded;
|
|
||||||
app.graph.onNodeAdded = function (node) {
|
|
||||||
const onResize = node.onResize;
|
|
||||||
node.onResize = function () {
|
|
||||||
if (app.shiftDown) {
|
|
||||||
const w = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.size[0] / LiteGraph.CANVAS_GRID_SIZE);
|
|
||||||
const h = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.size[1] / LiteGraph.CANVAS_GRID_SIZE);
|
|
||||||
node.size[0] = w;
|
|
||||||
node.size[1] = h;
|
|
||||||
}
|
|
||||||
return onResize?.apply(this, arguments);
|
|
||||||
};
|
|
||||||
return onNodeAdded?.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draw a preview of where the node will go if holding shift and the node is selected
|
|
||||||
const origDrawNode = LGraphCanvas.prototype.drawNode;
|
|
||||||
LGraphCanvas.prototype.drawNode = function (node, ctx) {
|
|
||||||
if (app.shiftDown && this.node_dragged && node.id in this.selected_nodes) {
|
|
||||||
const x = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[0] / LiteGraph.CANVAS_GRID_SIZE);
|
|
||||||
const y = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[1] / LiteGraph.CANVAS_GRID_SIZE);
|
|
||||||
|
|
||||||
const shiftX = x - node.pos[0];
|
|
||||||
let shiftY = y - node.pos[1];
|
|
||||||
|
|
||||||
let w, h;
|
|
||||||
if (node.flags.collapsed) {
|
|
||||||
w = node._collapsed_width;
|
|
||||||
h = LiteGraph.NODE_TITLE_HEIGHT;
|
|
||||||
shiftY -= LiteGraph.NODE_TITLE_HEIGHT;
|
|
||||||
} else {
|
|
||||||
w = node.size[0];
|
|
||||||
h = node.size[1];
|
|
||||||
let titleMode = node.constructor.title_mode;
|
|
||||||
if (titleMode !== LiteGraph.TRANSPARENT_TITLE && titleMode !== LiteGraph.NO_TITLE) {
|
|
||||||
h += LiteGraph.NODE_TITLE_HEIGHT;
|
|
||||||
shiftY -= LiteGraph.NODE_TITLE_HEIGHT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const f = ctx.fillStyle;
|
|
||||||
ctx.fillStyle = "rgba(100, 100, 100, 0.5)";
|
|
||||||
ctx.fillRect(shiftX, shiftY, w, h);
|
|
||||||
ctx.fillStyle = f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return origDrawNode.apply(this, arguments);
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ComfyWidgets, addValueControlWidget } from "../../scripts/widgets.js";
|
import { ComfyWidgets, addValueControlWidget } from "../../scripts/widgets.js";
|
||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
|
import { ComfyGraphNode } from "../../scripts/graphNode.js";
|
||||||
import { LiteGraph } from "../../lib/litegraph.core.js"
|
import { LiteGraph } from "../../lib/litegraph.core.js"
|
||||||
|
|
||||||
const CONVERTED_TYPE = "converted-widget";
|
const CONVERTED_TYPE = "converted-widget";
|
||||||
@ -199,8 +200,9 @@ app.registerExtension({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
registerCustomNodes() {
|
registerCustomNodes() {
|
||||||
class PrimitiveNode {
|
class PrimitiveNode extends ComfyGraphNode {
|
||||||
constructor() {
|
constructor(title) {
|
||||||
|
super(title);
|
||||||
this.addOutput("connect to widget input", "*");
|
this.addOutput("connect to widget input", "*");
|
||||||
this.serialize_widgets = true;
|
this.serialize_widgets = true;
|
||||||
this.isVirtualNode = true;
|
this.isVirtualNode = true;
|
||||||
@ -214,7 +216,7 @@ app.registerExtension({
|
|||||||
for (const l of node.outputs[0].links) {
|
for (const l of node.outputs[0].links) {
|
||||||
const linkInfo = app.graph.links[l];
|
const linkInfo = app.graph.links[l];
|
||||||
const n = node.graph.getNodeById(linkInfo.target_id);
|
const n = node.graph.getNodeById(linkInfo.target_id);
|
||||||
if (n.type == "Reroute") {
|
if (n.type === "Reroute") {
|
||||||
links = links.concat(get_links(n));
|
links = links.concat(get_links(n));
|
||||||
} else {
|
} else {
|
||||||
links.push(l);
|
links.push(l);
|
||||||
@ -408,7 +410,7 @@ app.registerExtension({
|
|||||||
class: PrimitiveNode,
|
class: PrimitiveNode,
|
||||||
type: "PrimitiveNode",
|
type: "PrimitiveNode",
|
||||||
title: "Primitive",
|
title: "Primitive",
|
||||||
|
category: "utils"
|
||||||
});
|
});
|
||||||
PrimitiveNode.category = "utils";
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -82,7 +82,7 @@ const S = class {
|
|||||||
static registerNodeAndSlotType(t, e, i = !1) {
|
static registerNodeAndSlotType(t, e, i = !1) {
|
||||||
let n;
|
let n;
|
||||||
if (typeof t == "string" ? n = S.registered_node_types[t] : "type" in t ? n = S.registered_node_types[t.type] : n = t, !n)
|
if (typeof t == "string" ? n = S.registered_node_types[t] : "type" in t ? n = S.registered_node_types[t.type] : n = t, !n)
|
||||||
throw "Node not registered!" + t;
|
throw new Error("Node not registered!" + t);
|
||||||
var s = n.class.__litegraph_type__;
|
var s = n.class.__litegraph_type__;
|
||||||
if (typeof e == "string")
|
if (typeof e == "string")
|
||||||
var r = e.split(",");
|
var r = e.split(",");
|
||||||
@ -7102,7 +7102,7 @@ class X {
|
|||||||
return s.close(), d.preventDefault(), !0;
|
return s.close(), d.preventDefault(), !0;
|
||||||
},
|
},
|
||||||
!0
|
!0
|
||||||
), i.scroll_speed = i.scroll_speed || 0.1, i.invert_scrolling = i.invert_scrolling || !1, o.addEventListener("wheel", this.onMouseWheel.bind(this), !0), o.addEventListener("mousewheel", this.onMouseWheel.bind(this), !0), this.root = o, i.title) {
|
), i.scroll_speed || (i.scroll_speed = 0.1), o.addEventListener("wheel", this.onMouseWheel.bind(this), !0), o.addEventListener("mousewheel", this.onMouseWheel.bind(this), !0), this.root = o, i.title) {
|
||||||
var a = document.createElement("div");
|
var a = document.createElement("div");
|
||||||
a.className = "litemenu-title", a.innerHTML = i.title, o.appendChild(a);
|
a.className = "litemenu-title", a.innerHTML = i.title, o.appendChild(a);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -244,369 +244,6 @@ export class ComfyApp {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds special context menu handling for nodes
|
|
||||||
* e.g. this adds Open Image functionality for nodes that show images
|
|
||||||
* @param {*} node The node to add the menu handler
|
|
||||||
*/
|
|
||||||
#addNodeContextMenuHandler(node) {
|
|
||||||
node.prototype.getExtraMenuOptions = function (_, options) {
|
|
||||||
if (this.imgs) {
|
|
||||||
// If this node has images then we add an open in new tab item
|
|
||||||
let img;
|
|
||||||
if (this.imageIndex != null) {
|
|
||||||
// An image is selected so select that
|
|
||||||
img = this.imgs[this.imageIndex];
|
|
||||||
} else if (this.overIndex != null) {
|
|
||||||
// No image is selected but one is hovered
|
|
||||||
img = this.imgs[this.overIndex];
|
|
||||||
}
|
|
||||||
if (img) {
|
|
||||||
options.unshift(
|
|
||||||
{
|
|
||||||
content: "Open Image",
|
|
||||||
callback: () => {
|
|
||||||
let url = new URL(img.src);
|
|
||||||
url.searchParams.delete('preview');
|
|
||||||
window.open(url, "_blank")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: "Save Image",
|
|
||||||
callback: () => {
|
|
||||||
const a = document.createElement("a");
|
|
||||||
let url = new URL(img.src);
|
|
||||||
url.searchParams.delete('preview');
|
|
||||||
a.href = url;
|
|
||||||
a.setAttribute("download", new URLSearchParams(url.search).get("filename"));
|
|
||||||
document.body.append(a);
|
|
||||||
a.click();
|
|
||||||
requestAnimationFrame(() => a.remove());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.push({
|
|
||||||
content: "Bypass",
|
|
||||||
callback: (obj) => { if (this.mode === 4) this.mode = 0; else this.mode = 4; this.graph.change(); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// prevent conflict of clipspace content
|
|
||||||
if(!ComfyApp.clipspace_return_node) {
|
|
||||||
options.push({
|
|
||||||
content: "Copy (Clipspace)",
|
|
||||||
callback: (obj) => { ComfyApp.copyToClipspace(this); }
|
|
||||||
});
|
|
||||||
|
|
||||||
if(ComfyApp.clipspace != null) {
|
|
||||||
options.push({
|
|
||||||
content: "Paste (Clipspace)",
|
|
||||||
callback: () => { ComfyApp.pasteFromClipspace(this); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ComfyApp.isImageNode(this)) {
|
|
||||||
options.push({
|
|
||||||
content: "Open in MaskEditor",
|
|
||||||
callback: (obj) => {
|
|
||||||
ComfyApp.copyToClipspace(this);
|
|
||||||
ComfyApp.clipspace_return_node = this;
|
|
||||||
ComfyApp.open_maskeditor();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#addNodeKeyHandler(node) {
|
|
||||||
const app = this;
|
|
||||||
const origNodeOnKeyDown = node.prototype.onKeyDown;
|
|
||||||
|
|
||||||
node.prototype.onKeyDown = function(e) {
|
|
||||||
if (origNodeOnKeyDown && origNodeOnKeyDown.apply(this, e) === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.flags.collapsed || !this.imgs || this.imageIndex === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let handled = false;
|
|
||||||
|
|
||||||
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
|
|
||||||
if (e.key === "ArrowLeft") {
|
|
||||||
this.imageIndex -= 1;
|
|
||||||
} else if (e.key === "ArrowRight") {
|
|
||||||
this.imageIndex += 1;
|
|
||||||
}
|
|
||||||
this.imageIndex %= this.imgs.length;
|
|
||||||
|
|
||||||
if (this.imageIndex < 0) {
|
|
||||||
this.imageIndex = this.imgs.length + this.imageIndex;
|
|
||||||
}
|
|
||||||
handled = true;
|
|
||||||
} else if (e.key === "Escape") {
|
|
||||||
this.imageIndex = null;
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handled === true) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds Custom drawing logic for nodes
|
|
||||||
* e.g. Draws images and handles thumbnail navigation on nodes that output images
|
|
||||||
* @param {*} node The node to add the draw handler
|
|
||||||
*/
|
|
||||||
#addDrawBackgroundHandler(node) {
|
|
||||||
const app = this;
|
|
||||||
|
|
||||||
function getImageTop(node) {
|
|
||||||
let shiftY;
|
|
||||||
if (node.imageOffset != null) {
|
|
||||||
shiftY = node.imageOffset;
|
|
||||||
} else {
|
|
||||||
if (node.widgets?.length) {
|
|
||||||
const w = node.widgets[node.widgets.length - 1];
|
|
||||||
shiftY = w.last_y;
|
|
||||||
if (w.computeSize) {
|
|
||||||
shiftY += w.computeSize()[1] + 4;
|
|
||||||
}
|
|
||||||
else if(w.computedHeight) {
|
|
||||||
shiftY += w.computedHeight;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
shiftY += LiteGraph.NODE_WIDGET_HEIGHT + 4;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
shiftY = node.computeSize()[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return shiftY;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.prototype.setSizeForImage = function () {
|
|
||||||
if (this.inputHeight) {
|
|
||||||
this.setSize(this.size);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const minHeight = getImageTop(this) + 220;
|
|
||||||
if (this.size[1] < minHeight) {
|
|
||||||
this.setSize([this.size[0], minHeight]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
node.prototype.onDrawBackground = function (ctx) {
|
|
||||||
if (!this.flags.collapsed) {
|
|
||||||
let imgURLs = []
|
|
||||||
let imagesChanged = false
|
|
||||||
|
|
||||||
const output = app.nodeOutputs[this.id + ""];
|
|
||||||
if (output && output.images) {
|
|
||||||
if (this.images !== output.images) {
|
|
||||||
this.images = output.images;
|
|
||||||
imagesChanged = true;
|
|
||||||
imgURLs = imgURLs.concat(output.images.map(params => {
|
|
||||||
return api.apiURL("/view?" + new URLSearchParams(params).toString() + app.getPreviewFormatParam());
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const preview = app.nodePreviewImages[this.id + ""]
|
|
||||||
if (this.preview !== preview) {
|
|
||||||
this.preview = preview
|
|
||||||
imagesChanged = true;
|
|
||||||
if (preview != null) {
|
|
||||||
imgURLs.push(preview);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imagesChanged) {
|
|
||||||
this.imageIndex = null;
|
|
||||||
if (imgURLs.length > 0) {
|
|
||||||
Promise.all(
|
|
||||||
imgURLs.map((src) => {
|
|
||||||
return new Promise((r) => {
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => r(img);
|
|
||||||
img.onerror = () => r(null);
|
|
||||||
img.src = src
|
|
||||||
});
|
|
||||||
})
|
|
||||||
).then((imgs) => {
|
|
||||||
if ((!output || this.images === output.images) && (!preview || this.preview === preview)) {
|
|
||||||
this.imgs = imgs.filter(Boolean);
|
|
||||||
this.setSizeForImage?.();
|
|
||||||
app.graph.setDirtyCanvas(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.imgs = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.imgs && this.imgs.length) {
|
|
||||||
const canvas = graph.list_of_graphcanvas[0];
|
|
||||||
const mouse = canvas.graph_mouse;
|
|
||||||
if (!canvas.pointer_is_down && this.pointerDown) {
|
|
||||||
if (mouse[0] === this.pointerDown.pos[0] && mouse[1] === this.pointerDown.pos[1]) {
|
|
||||||
this.imageIndex = this.pointerDown.index;
|
|
||||||
}
|
|
||||||
this.pointerDown = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let w = this.imgs[0].naturalWidth;
|
|
||||||
let h = this.imgs[0].naturalHeight;
|
|
||||||
let imageIndex = this.imageIndex;
|
|
||||||
const numImages = this.imgs.length;
|
|
||||||
if (numImages === 1 && !imageIndex) {
|
|
||||||
this.imageIndex = imageIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const shiftY = getImageTop(this);
|
|
||||||
|
|
||||||
let dw = this.size[0];
|
|
||||||
let dh = this.size[1];
|
|
||||||
dh -= shiftY;
|
|
||||||
|
|
||||||
if (imageIndex == null) {
|
|
||||||
let best = 0;
|
|
||||||
let cellWidth;
|
|
||||||
let cellHeight;
|
|
||||||
let cols = 0;
|
|
||||||
let shiftX = 0;
|
|
||||||
for (let c = 1; c <= numImages; c++) {
|
|
||||||
const rows = Math.ceil(numImages / c);
|
|
||||||
const cW = dw / c;
|
|
||||||
const cH = dh / rows;
|
|
||||||
const scaleX = cW / w;
|
|
||||||
const scaleY = cH / h;
|
|
||||||
|
|
||||||
const scale = Math.min(scaleX, scaleY, 1);
|
|
||||||
const imageW = w * scale;
|
|
||||||
const imageH = h * scale;
|
|
||||||
const area = imageW * imageH * numImages;
|
|
||||||
|
|
||||||
if (area > best) {
|
|
||||||
best = area;
|
|
||||||
cellWidth = imageW;
|
|
||||||
cellHeight = imageH;
|
|
||||||
cols = c;
|
|
||||||
shiftX = c * ((cW - imageW) / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let anyHovered = false;
|
|
||||||
this.imageRects = [];
|
|
||||||
for (let i = 0; i < numImages; i++) {
|
|
||||||
const img = this.imgs[i];
|
|
||||||
const row = Math.floor(i / cols);
|
|
||||||
const col = i % cols;
|
|
||||||
const x = col * cellWidth + shiftX;
|
|
||||||
const y = row * cellHeight + shiftY;
|
|
||||||
if (!anyHovered) {
|
|
||||||
anyHovered = LiteGraph.isInsideRectangle(
|
|
||||||
mouse[0],
|
|
||||||
mouse[1],
|
|
||||||
x + this.pos[0],
|
|
||||||
y + this.pos[1],
|
|
||||||
cellWidth,
|
|
||||||
cellHeight
|
|
||||||
);
|
|
||||||
if (anyHovered) {
|
|
||||||
this.overIndex = i;
|
|
||||||
let value = 110;
|
|
||||||
if (canvas.pointer_is_down) {
|
|
||||||
if (!this.pointerDown || this.pointerDown.index !== i) {
|
|
||||||
this.pointerDown = { index: i, pos: [...mouse] };
|
|
||||||
}
|
|
||||||
value = 125;
|
|
||||||
}
|
|
||||||
ctx.filter = `contrast(${value}%) brightness(${value}%)`;
|
|
||||||
canvas.canvas.style.cursor = "pointer";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.imageRects.push([x, y, cellWidth, cellHeight]);
|
|
||||||
ctx.drawImage(img, x, y, cellWidth, cellHeight);
|
|
||||||
ctx.filter = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!anyHovered) {
|
|
||||||
this.pointerDown = null;
|
|
||||||
this.overIndex = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Draw individual
|
|
||||||
const scaleX = dw / w;
|
|
||||||
const scaleY = dh / h;
|
|
||||||
const scale = Math.min(scaleX, scaleY, 1);
|
|
||||||
|
|
||||||
w *= scale;
|
|
||||||
h *= scale;
|
|
||||||
|
|
||||||
let x = (dw - w) / 2;
|
|
||||||
let y = (dh - h) / 2 + shiftY;
|
|
||||||
ctx.drawImage(this.imgs[imageIndex], x, y, w, h);
|
|
||||||
|
|
||||||
const drawButton = (x, y, sz, text) => {
|
|
||||||
const hovered = LiteGraph.isInsideRectangle(mouse[0], mouse[1], x + this.pos[0], y + this.pos[1], sz, sz);
|
|
||||||
let fill = "#333";
|
|
||||||
let textFill = "#fff";
|
|
||||||
let isClicking = false;
|
|
||||||
if (hovered) {
|
|
||||||
canvas.canvas.style.cursor = "pointer";
|
|
||||||
if (canvas.pointer_is_down) {
|
|
||||||
fill = "#1e90ff";
|
|
||||||
isClicking = true;
|
|
||||||
} else {
|
|
||||||
fill = "#eee";
|
|
||||||
textFill = "#000";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.pointerWasDown = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fillStyle = fill;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.roundRect(x, y, sz, sz, [4]);
|
|
||||||
ctx.fill();
|
|
||||||
ctx.fillStyle = textFill;
|
|
||||||
ctx.font = "12px Arial";
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.fillText(text, x + 15, y + 20);
|
|
||||||
|
|
||||||
return isClicking;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (numImages > 1) {
|
|
||||||
if (drawButton(x + w - 35, y + h - 35, 30, `${this.imageIndex + 1}/${numImages}`)) {
|
|
||||||
let i = this.imageIndex + 1 >= numImages ? 0 : this.imageIndex + 1;
|
|
||||||
if (!this.pointerDown || !this.pointerDown.index === i) {
|
|
||||||
this.pointerDown = { index: i, pos: [...mouse] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawButton(x + w - 35, y + 5, 30, `x`)) {
|
|
||||||
if (!this.pointerDown || !this.pointerDown.index === null) {
|
|
||||||
this.pointerDown = { index: null, pos: [...mouse] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a handler allowing drag+drop of files onto the window to load workflows
|
* Adds a handler allowing drag+drop of files onto the window to load workflows
|
||||||
*/
|
*/
|
||||||
@ -860,6 +497,9 @@ export class ComfyApp {
|
|||||||
const defs = await api.getNodeDefs();
|
const defs = await api.getNodeDefs();
|
||||||
await this.registerNodesFromDefs(defs);
|
await this.registerNodesFromDefs(defs);
|
||||||
await this.#invokeExtensionsAsync("registerCustomNodes");
|
await this.#invokeExtensionsAsync("registerCustomNodes");
|
||||||
|
|
||||||
|
// Hide litegraph Subgraph from menu until it's implemented
|
||||||
|
LiteGraph.registered_node_types["graph/subgraph"].hide_in_node_lists = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerNodesFromDefs(defs) {
|
async registerNodesFromDefs(defs) {
|
||||||
@ -882,7 +522,7 @@ export class ComfyApp {
|
|||||||
|
|
||||||
const ctor = class extends ComfyBackendNode {
|
const ctor = class extends ComfyBackendNode {
|
||||||
constructor(title) {
|
constructor(title) {
|
||||||
super(title, app, nodeId, nodeData);
|
super(title, nodeId, nodeData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -892,63 +532,8 @@ export class ComfyApp {
|
|||||||
desc: `ComfyNode: ${nodeId}`
|
desc: `ComfyNode: ${nodeId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// const node = Object.assign(
|
|
||||||
// function ComfyNode() {
|
|
||||||
// var inputs = nodeData["input"]["required"];
|
|
||||||
// if (nodeData["input"]["optional"] != undefined){
|
|
||||||
// inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"])
|
|
||||||
// }
|
|
||||||
// const config = { minWidth: 1, minHeight: 1 };
|
|
||||||
// for (const inputName in inputs) {
|
|
||||||
// const inputData = inputs[inputName];
|
|
||||||
// const type = inputData[0];
|
|
||||||
|
|
||||||
// if(inputData[1]?.forceInput) {
|
|
||||||
// this.addInput(inputName, type);
|
|
||||||
// } else {
|
|
||||||
// if (Array.isArray(type)) {
|
|
||||||
// // Enums
|
|
||||||
// Object.assign(config, widgets.COMBO(this, inputName, inputData, app) || {});
|
|
||||||
// } else if (`${type}:${inputName}` in widgets) {
|
|
||||||
// // Support custom widgets by Type:Name
|
|
||||||
// Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData, app) || {});
|
|
||||||
// } else if (type in widgets) {
|
|
||||||
// // Standard type widgets
|
|
||||||
// Object.assign(config, widgets[type](this, inputName, inputData, app) || {});
|
|
||||||
// } else {
|
|
||||||
// // Node connection inputs
|
|
||||||
// this.addInput(inputName, type);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for (const o in nodeData["output"]) {
|
|
||||||
// const output = nodeData["output"][o];
|
|
||||||
// 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(config.minWidth, s[0] * 1.5);
|
|
||||||
// s[1] = Math.max(config.minHeight, s[1]);
|
|
||||||
// this.size = s;
|
|
||||||
// this.serialize_widgets = true;
|
|
||||||
|
|
||||||
// app.#invokeExtensionsAsync("nodeCreated", this);
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// title: nodeData.display_name || nodeData.name,
|
|
||||||
// comfyClass: nodeData.name,
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// node.prototype.comfyClass = nodeData.name;
|
|
||||||
node.type = nodeId;
|
node.type = nodeId;
|
||||||
|
|
||||||
// this.#addNodeContextMenuHandler(node);
|
|
||||||
// this.#addDrawBackgroundHandler(node, app);
|
|
||||||
// this.#addNodeKeyHandler(node);
|
|
||||||
|
|
||||||
await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData);
|
await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData);
|
||||||
LiteGraph.registerNodeType(node);
|
LiteGraph.registerNodeType(node);
|
||||||
node.category = nodeData.category;
|
node.category = nodeData.category;
|
||||||
|
|||||||
@ -6,236 +6,289 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
this.app = app;
|
this.app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
processKey(e) {
|
/*
|
||||||
const res = super.processKey(e);
|
* SnapToGrid feature
|
||||||
|
*/
|
||||||
|
|
||||||
if (res === false) {
|
// After moving a node, if the shift key is down align it to grid
|
||||||
return res;
|
onNodeMoved(node) {
|
||||||
}
|
const r = super.onNodeMoved?.(node);
|
||||||
|
|
||||||
if (!this.graph) {
|
if (app.shiftDown) {
|
||||||
return;
|
// Ensure all selected nodes are realigned
|
||||||
}
|
for (const id in this.selected_nodes) {
|
||||||
|
this.selected_nodes[id].alignToGrid();
|
||||||
var block_default = false;
|
|
||||||
|
|
||||||
if (e.target.localName == "input") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.type == "keydown") {
|
|
||||||
// Ctrl + M mute/unmute
|
|
||||||
if (e.keyCode == 77 && e.ctrlKey) {
|
|
||||||
if (this.selected_nodes) {
|
|
||||||
for (var i in this.selected_nodes) {
|
|
||||||
if (this.selected_nodes[i].mode === 2) { // never
|
|
||||||
this.selected_nodes[i].mode = 0; // always
|
|
||||||
} else {
|
|
||||||
this.selected_nodes[i].mode = 2; // never
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
block_default = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode == 66 && e.ctrlKey) {
|
return r;
|
||||||
if (this.selected_nodes) {
|
|
||||||
for (var i in this.selected_nodes) {
|
|
||||||
if (this.selected_nodes[i].mode === 4) { // never
|
|
||||||
this.selected_nodes[i].mode = 0; // always
|
|
||||||
} else {
|
|
||||||
this.selected_nodes[i].mode = 4; // never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block_default = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.graph.change();
|
processKey(e) {
|
||||||
|
const res = super.processKey(e);
|
||||||
|
|
||||||
if (block_default) {
|
if (res === false) {
|
||||||
e.preventDefault();
|
return res;
|
||||||
e.stopImmediatePropagation();
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
if (!this.graph) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
processMouseDown(e) {
|
var block_default = false;
|
||||||
const res = super.processMouseDown(e);
|
|
||||||
|
|
||||||
this.selected_group_moving = false;
|
if (e.target.localName == "input") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.selected_group && !this.selected_group_resizing) {
|
if (e.type == "keydown") {
|
||||||
var font_size =
|
// Ctrl + M mute/unmute
|
||||||
this.selected_group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;
|
if (e.keyCode == 77 && e.ctrlKey) {
|
||||||
var height = font_size * 1.4;
|
if (this.selected_nodes) {
|
||||||
|
for (var i in this.selected_nodes) {
|
||||||
|
if (this.selected_nodes[i].mode === 2) { // never
|
||||||
|
this.selected_nodes[i].mode = 0; // always
|
||||||
|
} else {
|
||||||
|
this.selected_nodes[i].mode = 2; // never
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block_default = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Move group by header
|
if (e.keyCode == 66 && e.ctrlKey) {
|
||||||
if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, this.selected_group.pos[0], this.selected_group.pos[1], this.selected_group.size[0], height)) {
|
if (this.selected_nodes) {
|
||||||
this.selected_group_moving = true;
|
for (var i in this.selected_nodes) {
|
||||||
}
|
if (this.selected_nodes[i].mode === 4) { // never
|
||||||
}
|
this.selected_nodes[i].mode = 0; // always
|
||||||
|
} else {
|
||||||
|
this.selected_nodes[i].mode = 4; // never
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block_default = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
this.graph.change();
|
||||||
}
|
|
||||||
|
|
||||||
processMouseMove(e) {
|
if (block_default) {
|
||||||
const orig_selected_group = this.selected_group;
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving) {
|
return res;
|
||||||
this.selected_group = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = super.processMouseMove(e);
|
|
||||||
|
|
||||||
if (orig_selected_group && !this.selected_group_resizing && !this.selected_group_moving) {
|
|
||||||
this.selected_group = orig_selected_group;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws group header bar
|
|
||||||
*/
|
|
||||||
drawGroups(canvas, ctx) {
|
|
||||||
if (!this.graph) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var groups = this.graph._groups;
|
processMouseDown(e) {
|
||||||
|
const res = super.processMouseDown(e);
|
||||||
|
|
||||||
ctx.save();
|
this.selected_group_moving = false;
|
||||||
ctx.globalAlpha = 0.7 * this.editor_alpha;
|
|
||||||
|
|
||||||
for (var i = 0; i < groups.length; ++i) {
|
if (this.selected_group && !this.selected_group_resizing) {
|
||||||
var group = groups[i];
|
var font_size =
|
||||||
|
this.selected_group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;
|
||||||
|
var height = font_size * 1.4;
|
||||||
|
|
||||||
if (!LiteGraph.overlapBounding(this.visible_area, group._bounding)) {
|
// Move group by header
|
||||||
continue;
|
if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, this.selected_group.pos[0], this.selected_group.pos[1], this.selected_group.size[0], height)) {
|
||||||
} //out of the visible area
|
this.selected_group_moving = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.fillStyle = group.color || "#335";
|
return res;
|
||||||
ctx.strokeStyle = group.color || "#335";
|
}
|
||||||
var pos = group._pos;
|
|
||||||
var size = group._size;
|
|
||||||
ctx.globalAlpha = 0.25 * this.editor_alpha;
|
|
||||||
ctx.beginPath();
|
|
||||||
var font_size =
|
|
||||||
group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;
|
|
||||||
ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], font_size * 1.4);
|
|
||||||
ctx.fill();
|
|
||||||
ctx.globalAlpha = this.editor_alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.restore();
|
processMouseMove(e) {
|
||||||
|
const orig_selected_group = this.selected_group;
|
||||||
|
|
||||||
const res = super.drawGroups(canvas, ctx);
|
if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving) {
|
||||||
return res;
|
this.selected_group = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const res = super.processMouseMove(e);
|
||||||
* Draws node highlights (executing, drag drop) and progress bar
|
|
||||||
*/
|
|
||||||
drawNodeShape(node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
|
|
||||||
const res = super.drawNodeShape(node, ctx, size, fgcolor, bgcolor, selected, mouse_over);
|
|
||||||
|
|
||||||
const nodeErrors = app.lastNodeErrors?.[node.id];
|
if (orig_selected_group && !this.selected_group_resizing && !this.selected_group_moving) {
|
||||||
|
this.selected_group = orig_selected_group;
|
||||||
|
}
|
||||||
|
|
||||||
let color = null;
|
return res;
|
||||||
let lineWidth = 1;
|
}
|
||||||
if (node.id === +app.runningNodeId) {
|
|
||||||
color = "#0f0";
|
|
||||||
} else if (app.dragOverNode && node.id === app.dragOverNode.id) {
|
|
||||||
color = "dodgerblue";
|
|
||||||
}
|
|
||||||
else if (nodeErrors?.errors) {
|
|
||||||
color = "red";
|
|
||||||
lineWidth = 2;
|
|
||||||
}
|
|
||||||
else if (app.lastExecutionError && +app.lastExecutionError.node_id === node.id) {
|
|
||||||
color = "#f0f";
|
|
||||||
lineWidth = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color) {
|
/**
|
||||||
const shape = node.shape || BuiltInSlotShape.ROUND_SHAPE;
|
* Draws group header bar
|
||||||
ctx.lineWidth = lineWidth;
|
*/
|
||||||
ctx.globalAlpha = 0.8;
|
drawGroups(canvas, ctx) {
|
||||||
ctx.beginPath();
|
if (!this.graph) {
|
||||||
if (shape == BuiltInSlotShape.BOX_SHAPE)
|
return;
|
||||||
ctx.rect(-6, -6 - LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT);
|
}
|
||||||
else if (shape == BuiltInSlotShape.ROUND_SHAPE || (shape == BuiltInSlotShape.CARD_SHAPE && node.flags.collapsed))
|
|
||||||
ctx.roundRect(
|
|
||||||
-6,
|
|
||||||
-6 - LiteGraph.NODE_TITLE_HEIGHT,
|
|
||||||
12 + size[0] + 1,
|
|
||||||
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
|
||||||
this.round_radius * 2
|
|
||||||
);
|
|
||||||
else if (shape == BuiltInSlotShape.CARD_SHAPE)
|
|
||||||
ctx.roundRect(
|
|
||||||
-6,
|
|
||||||
-6 - LiteGraph.NODE_TITLE_HEIGHT,
|
|
||||||
12 + size[0] + 1,
|
|
||||||
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
|
||||||
[this.round_radius * 2, this.round_radius * 2, 2, 2]
|
|
||||||
);
|
|
||||||
else if (shape == BuiltInSlotShape.CIRCLE_SHAPE)
|
|
||||||
ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2);
|
|
||||||
ctx.strokeStyle = color;
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.strokeStyle = fgcolor;
|
|
||||||
ctx.globalAlpha = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.progress && node.id === +app.runningNodeId) {
|
var groups = this.graph._groups;
|
||||||
ctx.fillStyle = "green";
|
|
||||||
ctx.fillRect(0, 0, size[0] * (app.progress.value / app.progress.max), 6);
|
|
||||||
ctx.fillStyle = bgcolor;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Highlight inputs that failed validation
|
ctx.save();
|
||||||
if (nodeErrors) {
|
ctx.globalAlpha = 0.7 * this.editor_alpha;
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.strokeStyle = "red";
|
for (var i = 0; i < groups.length; ++i) {
|
||||||
for (const error of nodeErrors.errors) {
|
var group = groups[i];
|
||||||
if (error.extra_info && error.extra_info.input_name) {
|
|
||||||
const inputIndex = node.findInputSlot(error.extra_info.input_name)
|
if (!LiteGraph.overlapBounding(this.visible_area, group._bounding)) {
|
||||||
if (inputIndex !== -1) {
|
continue;
|
||||||
let pos = node.getConnectionPos(true, inputIndex);
|
} //out of the visible area
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(pos[0] - node.pos[0], pos[1] - node.pos[1], 12, 0, 2 * Math.PI, false)
|
ctx.fillStyle = group.color || "#335";
|
||||||
ctx.stroke();
|
ctx.strokeStyle = group.color || "#335";
|
||||||
}
|
var pos = group._pos;
|
||||||
|
var size = group._size;
|
||||||
|
ctx.globalAlpha = 0.25 * this.editor_alpha;
|
||||||
|
ctx.beginPath();
|
||||||
|
var font_size =
|
||||||
|
group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;
|
||||||
|
ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], font_size * 1.4);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.globalAlpha = this.editor_alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
const res = super.drawGroups(canvas, ctx);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws node highlights (executing, drag drop) and progress bar
|
||||||
|
*/
|
||||||
|
drawNodeShape(node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
|
||||||
|
const res = super.drawNodeShape(node, ctx, size, fgcolor, bgcolor, selected, mouse_over);
|
||||||
|
|
||||||
|
const nodeErrors = app.lastNodeErrors?.[node.id];
|
||||||
|
|
||||||
|
let color = null;
|
||||||
|
let lineWidth = 1;
|
||||||
|
if (node.id === +app.runningNodeId) {
|
||||||
|
color = "#0f0";
|
||||||
|
} else if (app.dragOverNode && node.id === app.dragOverNode.id) {
|
||||||
|
color = "dodgerblue";
|
||||||
|
}
|
||||||
|
else if (nodeErrors?.errors) {
|
||||||
|
color = "red";
|
||||||
|
lineWidth = 2;
|
||||||
|
}
|
||||||
|
else if (app.lastExecutionError && +app.lastExecutionError.node_id === node.id) {
|
||||||
|
color = "#f0f";
|
||||||
|
lineWidth = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color) {
|
||||||
|
const shape = node.shape || BuiltInSlotShape.ROUND_SHAPE;
|
||||||
|
ctx.lineWidth = lineWidth;
|
||||||
|
ctx.globalAlpha = 0.8;
|
||||||
|
ctx.beginPath();
|
||||||
|
if (shape == BuiltInSlotShape.BOX_SHAPE)
|
||||||
|
ctx.rect(-6, -6 - LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT);
|
||||||
|
else if (shape == BuiltInSlotShape.ROUND_SHAPE || (shape == BuiltInSlotShape.CARD_SHAPE && node.flags.collapsed))
|
||||||
|
ctx.roundRect(
|
||||||
|
-6,
|
||||||
|
-6 - LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
|
12 + size[0] + 1,
|
||||||
|
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
|
this.round_radius * 2
|
||||||
|
);
|
||||||
|
else if (shape == BuiltInSlotShape.CARD_SHAPE)
|
||||||
|
ctx.roundRect(
|
||||||
|
-6,
|
||||||
|
-6 - LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
|
12 + size[0] + 1,
|
||||||
|
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
|
[this.round_radius * 2, this.round_radius * 2, 2, 2]
|
||||||
|
);
|
||||||
|
else if (shape == BuiltInSlotShape.CIRCLE_SHAPE)
|
||||||
|
ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2);
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.strokeStyle = fgcolor;
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.progress && node.id === +app.runningNodeId) {
|
||||||
|
ctx.fillStyle = "green";
|
||||||
|
ctx.fillRect(0, 0, size[0] * (app.progress.value / app.progress.max), 6);
|
||||||
|
ctx.fillStyle = bgcolor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight inputs that failed validation
|
||||||
|
if (nodeErrors) {
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeStyle = "red";
|
||||||
|
for (const error of nodeErrors.errors) {
|
||||||
|
if (error.extra_info && error.extra_info.input_name) {
|
||||||
|
const inputIndex = node.findInputSlot(error.extra_info.input_name)
|
||||||
|
if (inputIndex !== -1) {
|
||||||
|
let pos = node.getConnectionPos(true, inputIndex);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(pos[0] - node.pos[0], pos[1] - node.pos[1], 12, 0, 2 * Math.PI, false)
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawNode(node, ctx) {
|
||||||
|
const editor_alpha = this.editor_alpha;
|
||||||
|
const old_color = node.bgcolor;
|
||||||
|
|
||||||
|
if (node.mode === 2) { // never
|
||||||
|
this.editor_alpha = 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.mode === 4) { // never
|
||||||
|
node.bgcolor = "#FF00FF";
|
||||||
|
this.editor_alpha = 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#drawNodeGridPreview(node, ctx);
|
||||||
|
const res = super.drawNode(node, ctx);
|
||||||
|
|
||||||
|
this.editor_alpha = editor_alpha;
|
||||||
|
node.bgcolor = old_color;
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SnapToGrid feature
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Draw a preview of where the node will go if holding shift and the node is selected
|
||||||
|
#drawNodeGridPreview = function (node, ctx) {
|
||||||
|
if (app.shiftDown && this.node_dragged && node.id in this.selected_nodes) {
|
||||||
|
const x = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[0] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
|
const y = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[1] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
|
|
||||||
|
const shiftX = x - node.pos[0];
|
||||||
|
let shiftY = y - node.pos[1];
|
||||||
|
|
||||||
|
let w, h;
|
||||||
|
if (node.flags.collapsed) {
|
||||||
|
w = node._collapsed_width;
|
||||||
|
h = LiteGraph.NODE_TITLE_HEIGHT;
|
||||||
|
shiftY -= LiteGraph.NODE_TITLE_HEIGHT;
|
||||||
|
} else {
|
||||||
|
w = node.size[0];
|
||||||
|
h = node.size[1];
|
||||||
|
let titleMode = node.constructor.title_mode;
|
||||||
|
if (titleMode !== LiteGraph.TRANSPARENT_TITLE && titleMode !== LiteGraph.NO_TITLE) {
|
||||||
|
h += LiteGraph.NODE_TITLE_HEIGHT;
|
||||||
|
shiftY -= LiteGraph.NODE_TITLE_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const f = ctx.fillStyle;
|
||||||
|
ctx.fillStyle = "rgba(100, 100, 100, 0.5)";
|
||||||
|
ctx.fillRect(shiftX, shiftY, w, h);
|
||||||
|
ctx.fillStyle = f;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
drawNode(node, ctx) {
|
|
||||||
var editor_alpha = this.editor_alpha;
|
|
||||||
var old_color = node.bgcolor;
|
|
||||||
|
|
||||||
if (node.mode === 2) { // never
|
|
||||||
this.editor_alpha = 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.mode === 4) { // never
|
|
||||||
node.bgcolor = "#FF00FF";
|
|
||||||
this.editor_alpha = 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = super.drawNode(node, ctx);
|
|
||||||
|
|
||||||
this.editor_alpha = editor_alpha;
|
|
||||||
node.bgcolor = old_color;
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,140 @@ import { LiteGraph, LGraphNode, LGraphCanvas, BuiltInSlotType, BuiltInSlotShape
|
|||||||
import { ComfyWidgets } from "./widgets.js";
|
import { ComfyWidgets } from "./widgets.js";
|
||||||
import { iterateNodeDefOutputs, iterateNodeDefInputs } from "./nodeDef.js";
|
import { iterateNodeDefOutputs, iterateNodeDefInputs } from "./nodeDef.js";
|
||||||
import { api } from "./api.js";
|
import { api } from "./api.js";
|
||||||
|
import { ComfyApp } from "./app.js"
|
||||||
|
|
||||||
export class ComfyGraphNode extends LGraphNode {
|
export class ComfyGraphNode extends LGraphNode {
|
||||||
constructor(title, app) {
|
constructor(title) {
|
||||||
super(title)
|
super(title)
|
||||||
this.app = app;
|
|
||||||
this.serialize_widgets = true;
|
this.serialize_widgets = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onKeyDown(e) {
|
||||||
|
if (super.onKeyDown && super.onKeyDown.apply(this, e) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.flags.collapsed || !this.imgs || this.imageIndex === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let handled = false;
|
||||||
|
|
||||||
|
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
|
||||||
|
if (e.key === "ArrowLeft") {
|
||||||
|
this.imageIndex -= 1;
|
||||||
|
} else if (e.key === "ArrowRight") {
|
||||||
|
this.imageIndex += 1;
|
||||||
|
}
|
||||||
|
this.imageIndex %= this.imgs.length;
|
||||||
|
|
||||||
|
if (this.imageIndex < 0) {
|
||||||
|
this.imageIndex = this.imgs.length + this.imageIndex;
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
} else if (e.key === "Escape") {
|
||||||
|
this.imageIndex = null;
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled === true) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SnapToGrid functionality
|
||||||
|
*/
|
||||||
|
onResize() {
|
||||||
|
if (app.shiftDown) {
|
||||||
|
const w = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.size[0] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
|
const h = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.size[1] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
|
node.size[0] = w;
|
||||||
|
node.size[1] = h;
|
||||||
|
}
|
||||||
|
return super.onResize?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds special context menu handling for nodes
|
||||||
|
* e.g. this adds Open Image functionality for nodes that show images
|
||||||
|
* @param {*} node The node to add the menu handler
|
||||||
|
*/
|
||||||
|
getExtraMenuOptions(_, options) {
|
||||||
|
if (super.getExtraMenuOptions)
|
||||||
|
super.getExtraMenuOptions(_, options);
|
||||||
|
|
||||||
|
if (this.imgs) {
|
||||||
|
// If this node has images then we add an open in new tab item
|
||||||
|
let img;
|
||||||
|
if (this.imageIndex != null) {
|
||||||
|
// An image is selected so select that
|
||||||
|
img = this.imgs[this.imageIndex];
|
||||||
|
} else if (this.overIndex != null) {
|
||||||
|
// No image is selected but one is hovered
|
||||||
|
img = this.imgs[this.overIndex];
|
||||||
|
}
|
||||||
|
if (img) {
|
||||||
|
options.unshift(
|
||||||
|
{
|
||||||
|
content: "Open Image",
|
||||||
|
callback: () => {
|
||||||
|
let url = new URL(img.src);
|
||||||
|
url.searchParams.delete('preview');
|
||||||
|
window.open(url, "_blank")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Save Image",
|
||||||
|
callback: () => {
|
||||||
|
const a = document.createElement("a");
|
||||||
|
let url = new URL(img.src);
|
||||||
|
url.searchParams.delete('preview');
|
||||||
|
a.href = url;
|
||||||
|
a.setAttribute("download", new URLSearchParams(url.search).get("filename"));
|
||||||
|
document.body.append(a);
|
||||||
|
a.click();
|
||||||
|
requestAnimationFrame(() => a.remove());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
content: "Bypass",
|
||||||
|
callback: (obj) => { if (this.mode === 4) this.mode = 0; else this.mode = 4; this.graph.change(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// prevent conflict of clipspace content
|
||||||
|
if(!ComfyApp.clipspace_return_node) {
|
||||||
|
options.push({
|
||||||
|
content: "Copy (Clipspace)",
|
||||||
|
callback: (obj) => { ComfyApp.copyToClipspace(this); }
|
||||||
|
});
|
||||||
|
|
||||||
|
if(ComfyApp.clipspace != null) {
|
||||||
|
options.push({
|
||||||
|
content: "Paste (Clipspace)",
|
||||||
|
callback: () => { ComfyApp.pasteFromClipspace(this); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ComfyApp.isImageNode(this)) {
|
||||||
|
options.push({
|
||||||
|
content: "Open in MaskEditor",
|
||||||
|
callback: (obj) => {
|
||||||
|
ComfyApp.copyToClipspace(this);
|
||||||
|
ComfyApp.clipspace_return_node = this;
|
||||||
|
ComfyApp.open_maskeditor();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
getImageTop() {
|
getImageTop() {
|
||||||
let shiftY;
|
let shiftY;
|
||||||
if (this.imageOffset != null) {
|
if (this.imageOffset != null) {
|
||||||
@ -253,8 +379,8 @@ const defaultInputConfigs = {}
|
|||||||
|
|
||||||
|
|
||||||
export class ComfyBackendNode extends ComfyGraphNode {
|
export class ComfyBackendNode extends ComfyGraphNode {
|
||||||
constructor(title, app, comfyClass, nodeDef) {
|
constructor(title, comfyClass, nodeDef) {
|
||||||
super(title, app)
|
super(title)
|
||||||
this.type = comfyClass; // XXX: workaround dependency in LGraphNode.addInput()
|
this.type = comfyClass; // XXX: workaround dependency in LGraphNode.addInput()
|
||||||
this.displayName = nodeDef.display_name;
|
this.displayName = nodeDef.display_name;
|
||||||
this.comfyNodeDef = nodeDef;
|
this.comfyNodeDef = nodeDef;
|
||||||
|
|||||||
@ -200,7 +200,7 @@ function addMultilineWidget(node, name, opts, app) {
|
|||||||
// if it goes off screen quickly, the input may not be removed
|
// if it goes off screen quickly, the input may not be removed
|
||||||
// this shifts it off screen so it can be moved back if the node is visible.
|
// this shifts it off screen so it can be moved back if the node is visible.
|
||||||
for (let n in window.app.graph._nodes) {
|
for (let n in window.app.graph._nodes) {
|
||||||
n = graph._nodes[n];
|
n = window.app.graph._nodes[n];
|
||||||
for (let w in n.widgets) {
|
for (let w in n.widgets) {
|
||||||
let wid = n.widgets[w];
|
let wid = n.widgets[w];
|
||||||
if (Object.hasOwn(wid, "inputEl")) {
|
if (Object.hasOwn(wid, "inputEl")) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user