Initial changes for litegraph.ts compat

This commit is contained in:
space-nuko 2023-08-22 21:46:20 -05:00
parent d7b3b0f8c1
commit c13f148df9
21 changed files with 7696 additions and 14729 deletions

View File

@ -1,5 +1,6 @@
import {app} from "../../scripts/app.js";
import {$el} from "../../scripts/ui.js";
import { LiteGraph, LGraphCanvas } from "../../lib/litegraph.core.js"
// Manage color palettes
@ -341,8 +342,8 @@ app.registerExtension({
if (colorPalette.colors) {
// Sets the colors of node slots and links
if (colorPalette.colors.node_slot) {
Object.assign(app.canvas.default_connection_color_byType, colorPalette.colors.node_slot);
Object.assign(LGraphCanvas.link_type_colors, colorPalette.colors.node_slot);
Object.assign(LGraphCanvas.DEFAULT_CONNECTION_COLORS_BY_TYPE, colorPalette.colors.node_slot);
Object.assign(app.canvas.link_type_colors, colorPalette.colors.node_slot);
}
// Sets the colors of the LiteGraph objects
if (colorPalette.colors.litegraph_base) {

View File

@ -1,4 +1,5 @@
import {app} from "../../scripts/app.js";
import { app } from "../../scripts/app.js";
import { LiteGraph } from "../../lib/litegraph.core.js"
// Adds filtering to combo context menus
@ -141,7 +142,7 @@ const ext = {
return ctx;
};
LiteGraph.ContextMenu.prototype = ctxMenu.prototype;
// LiteGraph.ContextMenu.prototype = ctxMenu.prototype;
},
}

View File

@ -1,4 +1,5 @@
import { app } from "../../scripts/app.js";
import { LiteGraph } from "../../lib/litegraph.core.js"
// Inverts the scrolling of context menus

View File

@ -1,4 +1,5 @@
import { app } from "../../scripts/app.js";
import { LiteGraph, LINK_RENDER_MODE_NAMES } from "../../lib/litegraph.core.js"
const id = "Comfy.LinkRenderMode";
const ext = {
@ -9,7 +10,7 @@ const ext = {
name: "Link Render Mode",
defaultValue: 2,
type: "combo",
options: LiteGraph.LINK_RENDER_MODES.map((m, i) => ({
options: LINK_RENDER_MODE_NAMES.map((m, i) => ({
value: i,
text: m,
selected: i == app.canvas.links_render_mode,

View File

@ -1,5 +1,6 @@
import { app } from "../../scripts/app.js";
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { LGraphCanvas } from "../../lib/litegraph.core.js"
// Adds the ability to save and add multiple nodes as a template
// To save:

View File

@ -1,11 +1,14 @@
import {app} from "../../scripts/app.js";
import {ComfyWidgets} from "../../scripts/widgets.js";
import { app } from "../../scripts/app.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { ComfyGraphNode } from "../../scripts/graphNode.js";
import { LiteGraph } from "../../lib/litegraph.core.js"
// Node that add notes to your project
app.registerExtension({
name: "Comfy.NoteNode",
registerCustomNodes() {
class NoteNode {
class NoteNode extends ComfyGraphNode {
color=LGraphCanvas.node_colors.yellow.color;
bgcolor=LGraphCanvas.node_colors.yellow.bgcolor;
groupcolor = LGraphCanvas.node_colors.yellow.groupcolor;
@ -19,22 +22,18 @@ app.registerExtension({
this.serialize_widgets = true;
this.isVirtualNode = true;
}
}
// Load default visibility
LiteGraph.registerNodeType(
"Note",
Object.assign(NoteNode, {
title_mode: LiteGraph.NORMAL_TITLE,
title: "Note",
collapsable: true,
})
);
LiteGraph.registerNodeType({
class: NoteNode,
title_mode: LiteGraph.NORMAL_TITLE,
type: "Note",
title: "Note",
collapsable: true,
});
NoteNode.category = "utils";
},

View File

@ -1,11 +1,13 @@
import { app } from "../../scripts/app.js";
import { ComfyGraphNode } from "../../scripts/graphNode.js";
import { LiteGraph } from "../../lib/litegraph.core.js"
// Node that allows you to redirect connections for cleaner graphs
app.registerExtension({
name: "Comfy.RerouteNode",
registerCustomNodes() {
class RerouteNode {
class RerouteNode extends ComfyGraphNode {
constructor() {
if (!this.properties) {
this.properties = {};
@ -109,7 +111,7 @@ app.registerExtension({
}
const displayType = inputType || outputType || "*";
const color = LGraphCanvas.link_type_colors[displayType];
const color = app.canvas.link_type_colors[displayType];
// Update the types of each node
for (const node of updateNodes) {
@ -219,14 +221,13 @@ app.registerExtension({
// Load default visibility
RerouteNode.setDefaultTextVisibility(!!localStorage["Comfy.RerouteNode.DefaultVisibility"]);
LiteGraph.registerNodeType(
"Reroute",
Object.assign(RerouteNode, {
title_mode: LiteGraph.NO_TITLE,
title: "Reroute",
collapsable: false,
})
);
LiteGraph.registerNodeType({
class: RerouteNode,
title_mode: LiteGraph.NO_TITLE,
type: "Reroute",
title: "Reroute",
collapsable: false,
});
RerouteNode.category = "utils";
},

View File

@ -2,6 +2,7 @@ import { app } from "../../scripts/app.js";
// Use widget values and dates in output filenames
/*
app.registerExtension({
name: "Comfy.SaveImageExtraOutput",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
@ -98,3 +99,4 @@ app.registerExtension({
}
},
});
*/

View File

@ -1,5 +1,7 @@
import { app } from "../../scripts/app.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { LiteGraph } from "../../lib/litegraph.core.js"
// Adds defaults for quickly adding nodes with middle click on the input/output
app.registerExtension({

View File

@ -1,4 +1,5 @@
import { app } from "../../scripts/app.js";
import { LiteGraph } from "../../lib/litegraph.core.js"
// Shift + drag/resize to snap to grid

View File

@ -1,5 +1,6 @@
import { ComfyWidgets, addValueControlWidget } from "../../scripts/widgets.js";
import { app } from "../../scripts/app.js";
import { LiteGraph } from "../../lib/litegraph.core.js"
const CONVERTED_TYPE = "converted-widget";
const VALID_TYPES = ["STRING", "combo", "number", "BOOLEAN"];
@ -95,8 +96,8 @@ app.registerExtension({
name: "Comfy.WidgetInputs",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// Add menu options to conver to/from widgets
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function (_, options) {
const origGetExtraMenuOptions = nodeType.class.prototype.getExtraMenuOptions;
nodeType.class.prototype.getExtraMenuOptions = function (_, options) {
const r = origGetExtraMenuOptions ? origGetExtraMenuOptions.apply(this, arguments) : undefined;
if (this.widgets) {
@ -131,8 +132,8 @@ app.registerExtension({
};
// On initial configure of nodes hide all converted widgets
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const origOnConfigure = nodeType.class.prototype.onConfigure;
nodeType.class.prototype.onConfigure = function () {
const r = origOnConfigure ? origOnConfigure.apply(this, arguments) : undefined;
if (this.inputs) {
@ -161,9 +162,9 @@ app.registerExtension({
}
// Double click a widget input to automatically attach a primitive
const origOnInputDblClick = nodeType.prototype.onInputDblClick;
const origOnInputDblClick = nodeType.class.prototype.onInputDblClick;
const ignoreDblClick = Symbol();
nodeType.prototype.onInputDblClick = function (slot) {
nodeType.class.prototype.onInputDblClick = function (slot) {
const r = origOnInputDblClick ? origOnInputDblClick.apply(this, arguments) : undefined;
const input = this.inputs[slot];
@ -403,12 +404,11 @@ app.registerExtension({
}
}
LiteGraph.registerNodeType(
"PrimitiveNode",
Object.assign(PrimitiveNode, {
title: "Primitive",
})
);
LiteGraph.registerNodeType({
class: PrimitiveNode,
type: "PrimitiveNode",
title: "Primitive",
});
PrimitiveNode.category = "utils";
},
});

View File

@ -7,12 +7,10 @@
<link rel="stylesheet" type="text/css" href="./lib/litegraph.css" />
<link rel="stylesheet" type="text/css" href="./style.css" />
<link rel="stylesheet" type="text/css" href="./user.css" />
<script type="text/javascript" src="./lib/litegraph.core.js"></script>
<script type="text/javascript" src="./lib/litegraph.extensions.js" defer></script>
<script type="module">
import { app } from "./scripts/app.js";
await app.setup();
window.app = app;
await app.setup();
window.graph = app.graph;
</script>
</head>

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +0,0 @@
/**
* Changes the background color of the canvas.
*
* @method updateBackground
* @param {image} String
* @param {clearBackgroundColor} String
* @
*/
LGraphCanvas.prototype.updateBackground = function (image, clearBackgroundColor) {
this._bg_img = new Image();
this._bg_img.name = image;
this._bg_img.src = image;
this._bg_img.onload = () => {
this.draw(true, true);
};
this.background_image = image;
this.clear_background = true;
this.clear_background_color = clearBackgroundColor;
this._pattern = null
}

View File

@ -4,6 +4,10 @@ import { ComfyUI, $el } from "./ui.js";
import { api } from "./api.js";
import { defaultGraph } from "./defaultGraph.js";
import { getPngMetadata, importA1111, getLatentMetadata } from "./pnginfo.js";
import { LiteGraph } from "../lib/litegraph.core.js"
import ComfyGraph from "./graph.js";
import ComfyGraphCanvas from "./graphCanvas.js";
import { ComfyBackendNode } from "./graphNode.js";
/**
* @typedef {import("types/comfy").ComfyExtension} ComfyExtension
@ -690,270 +694,6 @@ export class ComfyApp {
});
}
/**
* Handle mouse
*
* Move group by header
*/
#addProcessMouseHandler() {
const self = this;
const origProcessMouseDown = LGraphCanvas.prototype.processMouseDown;
LGraphCanvas.prototype.processMouseDown = function(e) {
const res = origProcessMouseDown.apply(this, arguments);
this.selected_group_moving = false;
if (this.selected_group && !this.selected_group_resizing) {
var font_size =
this.selected_group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;
var height = font_size * 1.4;
// Move group by header
if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, this.selected_group.pos[0], this.selected_group.pos[1], this.selected_group.size[0], height)) {
this.selected_group_moving = true;
}
}
return res;
}
const origProcessMouseMove = LGraphCanvas.prototype.processMouseMove;
LGraphCanvas.prototype.processMouseMove = function(e) {
const orig_selected_group = this.selected_group;
if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving) {
this.selected_group = null;
}
const res = origProcessMouseMove.apply(this, arguments);
if (orig_selected_group && !this.selected_group_resizing && !this.selected_group_moving) {
this.selected_group = orig_selected_group;
}
return res;
};
}
/**
* Handle keypress
*
* Ctrl + M mute/unmute selected nodes
*/
#addProcessKeyHandler() {
const self = this;
const origProcessKey = LGraphCanvas.prototype.processKey;
LGraphCanvas.prototype.processKey = function(e) {
const res = origProcessKey.apply(this, arguments);
if (res === false) {
return res;
}
if (!this.graph) {
return;
}
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) {
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();
if (block_default) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
return res;
};
}
/**
* Draws group header bar
*/
#addDrawGroupsHandler() {
const self = this;
const origDrawGroups = LGraphCanvas.prototype.drawGroups;
LGraphCanvas.prototype.drawGroups = function(canvas, ctx) {
if (!this.graph) {
return;
}
var groups = this.graph._groups;
ctx.save();
ctx.globalAlpha = 0.7 * this.editor_alpha;
for (var i = 0; i < groups.length; ++i) {
var group = groups[i];
if (!LiteGraph.overlapBounding(this.visible_area, group._bounding)) {
continue;
} //out of the visible area
ctx.fillStyle = group.color || "#335";
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 = origDrawGroups.apply(this, arguments);
return res;
}
}
/**
* Draws node highlights (executing, drag drop) and progress bar
*/
#addDrawNodeHandler() {
const origDrawNodeShape = LGraphCanvas.prototype.drawNodeShape;
const self = this;
LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
const res = origDrawNodeShape.apply(this, arguments);
const nodeErrors = self.lastNodeErrors?.[node.id];
let color = null;
let lineWidth = 1;
if (node.id === +self.runningNodeId) {
color = "#0f0";
} else if (self.dragOverNode && node.id === self.dragOverNode.id) {
color = "dodgerblue";
}
else if (nodeErrors?.errors) {
color = "red";
lineWidth = 2;
}
else if (self.lastExecutionError && +self.lastExecutionError.node_id === node.id) {
color = "#f0f";
lineWidth = 2;
}
if (color) {
const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;
ctx.lineWidth = lineWidth;
ctx.globalAlpha = 0.8;
ctx.beginPath();
if (shape == LiteGraph.BOX_SHAPE)
ctx.rect(-6, -6 - LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT);
else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.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 == LiteGraph.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 == LiteGraph.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 (self.progress && node.id === +self.runningNodeId) {
ctx.fillStyle = "green";
ctx.fillRect(0, 0, size[0] * (self.progress.value / self.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;
};
const origDrawNode = LGraphCanvas.prototype.drawNode;
LGraphCanvas.prototype.drawNode = function (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 = origDrawNode.apply(this, arguments);
this.editor_alpha = editor_alpha;
node.bgcolor = old_color;
return res;
};
}
/**
* Handles updates from the API socket
*/
@ -1056,11 +796,8 @@ export class ComfyApp {
canvasEl.tabIndex = "1";
document.body.prepend(canvasEl);
this.#addProcessMouseHandler();
this.#addProcessKeyHandler();
this.graph = new LGraph();
const canvas = (this.canvas = new LGraphCanvas(canvasEl, this.graph));
this.graph = new ComfyGraph();
const canvas = (this.canvas = new ComfyGraphCanvas(canvasEl, this.graph));
this.ctx = canvasEl.getContext("2d");
LiteGraph.release_link_on_empty_shows_menu = true;
@ -1106,8 +843,6 @@ export class ComfyApp {
// Save current workflow automatically
setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000);
this.#addDrawNodeHandler();
this.#addDrawGroupsHandler();
this.#addApiUpdateHandlers();
this.#addDropHandler();
this.#addPasteHandler();
@ -1130,74 +865,92 @@ export class ComfyApp {
async registerNodesFromDefs(defs) {
await this.#invokeExtensionsAsync("addCustomNodeDefs", defs);
// // Generate list of known widgets
// const widgets = Object.assign(
// {},
// ComfyWidgets,
// ...(await this.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean)
// );
// Generate list of known widgets
const widgets = Object.assign(
{},
ComfyWidgets,
...(await this.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean)
);
const customWidgets = (await app.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean);
ComfyWidgets.customWidgets = customWidgets;
// Register a node for each definition
for (const nodeId in defs) {
const nodeData = defs[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);
}
}
}
const ctor = class extends ComfyBackendNode {
constructor(title) {
super(title, nodeId, nodeData);
}
}
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 node = {
class: ctor,
title: nodeData.display_name || nodeData.name,
desc: `ComfyNode: ${nodeId}`
}
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;
// 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];
app.#invokeExtensionsAsync("nodeCreated", this);
},
{
title: nodeData.display_name || nodeData.name,
comfyClass: nodeData.name,
}
);
node.prototype.comfyClass = nodeData.name;
// 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);
// }
// }
// }
this.#addNodeContextMenuHandler(node);
this.#addDrawBackgroundHandler(node, app);
this.#addNodeKeyHandler(node);
// 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;
// this.#addNodeContextMenuHandler(node);
// this.#addDrawBackgroundHandler(node, app);
// this.#addNodeKeyHandler(node);
await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData);
LiteGraph.registerNodeType(nodeId, node);
LiteGraph.registerNodeType(node);
node.category = nodeData.category;
}
}

5
web/scripts/graph.js Normal file
View File

@ -0,0 +1,5 @@
import { LGraph } from "../lib/litegraph.core.js"
export default class ComfyGraph extends LGraph {
}

236
web/scripts/graphCanvas.js Normal file
View File

@ -0,0 +1,236 @@
import { LGraphCanvas } from "../lib/litegraph.core.js"
export default class ComfyGraphCanvas extends LGraphCanvas {
processKey(e) {
const res = super.processKey(e);
if (res === false) {
return res;
}
if (!this.graph) {
return;
}
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) {
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();
if (block_default) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
return res;
}
processMouseDown(e) {
const res = super.processMouseDown(e);
this.selected_group_moving = false;
if (this.selected_group && !this.selected_group_resizing) {
var font_size =
this.selected_group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;
var height = font_size * 1.4;
// Move group by header
if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, this.selected_group.pos[0], this.selected_group.pos[1], this.selected_group.size[0], height)) {
this.selected_group_moving = true;
}
}
return res;
}
processMouseMove(e) {
const orig_selected_group = this.selected_group;
if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving) {
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;
ctx.save();
ctx.globalAlpha = 0.7 * this.editor_alpha;
for (var i = 0; i < groups.length; ++i) {
var group = groups[i];
if (!LiteGraph.overlapBounding(this.visible_area, group._bounding)) {
continue;
} //out of the visible area
ctx.fillStyle = group.color || "#335";
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 = self.lastNodeErrors?.[node.id];
let color = null;
let lineWidth = 1;
if (node.id === +self.runningNodeId) {
color = "#0f0";
} else if (self.dragOverNode && node.id === self.dragOverNode.id) {
color = "dodgerblue";
}
else if (nodeErrors?.errors) {
color = "red";
lineWidth = 2;
}
else if (self.lastExecutionError && +self.lastExecutionError.node_id === node.id) {
color = "#f0f";
lineWidth = 2;
}
if (color) {
const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;
ctx.lineWidth = lineWidth;
ctx.globalAlpha = 0.8;
ctx.beginPath();
if (shape == LiteGraph.BOX_SHAPE)
ctx.rect(-6, -6 - LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT);
else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.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 == LiteGraph.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 == LiteGraph.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 (self.progress && node.id === +self.runningNodeId) {
ctx.fillStyle = "green";
ctx.fillRect(0, 0, size[0] * (self.progress.value / self.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) {
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;
}
}

86
web/scripts/graphNode.js Normal file
View File

@ -0,0 +1,86 @@
import { LGraphNode, LGraphCanvas, BuiltInSlotType, BuiltInSlotShape } from "../lib/litegraph.core.js"
import { ComfyWidgets } from "./widgets.js";
import { iterateNodeDefOutputs, iterateNodeDefInputs } from "./nodeDef.js";
export class ComfyGraphNode extends LGraphNode {
}
// comfy class -> input name -> input config
const defaultInputConfigs = {}
export class ComfyBackendNode extends ComfyGraphNode {
constructor(title, comfyClass, nodeDef) {
super(title)
this.type = comfyClass; // XXX: workaround dependency in LGraphNode.addInput()
this.displayName = nodeDef.display_name;
this.comfyNodeDef = nodeDef;
this.comfyClass = comfyClass;
this.isBackendNode = true;
const color = LGraphCanvas.node_colors["yellow"];
if (this.color == null)
this.color = color.color
if (this.bgColor == null)
this.bgColor = color.bgColor
this.#setup(nodeDef)
// if (nodeDef.output_node) {
// this.addOutput("OUTPUT", BuiltInSlotType.EVENT, { color_off: "rebeccapurple", color_on: "rebeccapurple" });
// }
}
get isOutputNode() {
return this.comfyNodeDef.output_node;
}
#setup(nodeDef) {
defaultInputConfigs[this.type] = {}
const widgets = Object.assign({}, ComfyWidgets, ComfyWidgets.customWidgets);
for (const [inputName, inputData] of iterateNodeDefInputs(nodeDef)) {
const config = {};
const [type, opts] = inputData;
if (opts?.forceInput) {
if (Array.isArray(type)) {
throw new Error(`Can't have forceInput set to true for an enum type! ${type}`)
}
this.addInput(inputName, type);
} else {
if (Array.isArray(type)) {
// Enums
Object.assign(config, widgets.COMBO(this, inputName, inputData) || {});
} else if (`${type}:${inputName}` in widgets) {
// Support custom ComfyWidgets by Type:Name
Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData) || {});
} else if (type in widgets) {
// Standard type ComfyWidgets
Object.assign(config, widgets[type](this, inputName, inputData) || {});
} else {
// Node connection inputs (backend)
this.addInput(inputName, type);
}
}
if ("widgetNodeType" in config)
ComfyBackendNode.defaultInputConfigs[this.type][inputName] = config.config
}
for (const output of iterateNodeDefOutputs(nodeDef)) {
const outputShape = output.is_list ? BuiltInSlotShape.GRID_SHAPE : BuiltInSlotShape.CIRCLE_SHAPE;
this.addOutput(output.name, output.type, { shape: outputShape });
}
this.serialize_widgets = false;
// app.#invokeExtensionsAsync("nodeCreated", this);
}
// onExecuted(outputData) {
// console.warn("onExecuted outputs", outputData)
// this.triggerSlot(0, outputData)
// }
}

26
web/scripts/nodeDef.js Normal file
View File

@ -0,0 +1,26 @@
import { ComfyWidgets } from "./widgets.js"
import { range } from "./utils.js"
export function isBackendNodeDefInputType(inputName, type) {
const widgets = Object.assign({}, ComfyWidgets, ComfyWidgets.customWidgets);
return !Array.isArray(type) && !(type in ComfyWidgets) && !(`${type}:${inputName}` in ComfyWidgets);
}
export function iterateNodeDefInputs(def) {
var inputs = def.input.required || {}
if (def.input.optional != null) {
inputs = Object.assign({}, def.input.required, def.input.optional)
}
return Object.entries(inputs);
}
export function iterateNodeDefOutputs(def) {
const outputCount = def.output ? def.output.length : 0;
return range(outputCount).map(i => {
return {
type: def.output[i],
name: def.output_name[i] || def.output[i],
is_list: def.output_is_list[i],
}
})
}

3
web/scripts/utils.js Normal file
View File

@ -0,0 +1,3 @@
export function range(size, startAt = 0) {
return [...Array(size).keys()].map(i => i + startAt);
}

View File

@ -157,7 +157,7 @@ function addMultilineWidget(node, name, opts, app) {
// Calculate it here instead
computeSize(node.size);
}
const visible = app.canvas.ds.scale > 0.5 && this.type === "customtext";
const visible = window.app.canvas.ds.scale > 0.5 && this.type === "customtext";
const margin = 10;
const elRect = ctx.canvas.getBoundingClientRect();
const transform = new DOMMatrix()
@ -176,7 +176,7 @@ function addMultilineWidget(node, name, opts, app) {
position: "absolute",
background: (!node.color)?'':node.color,
color: (!node.color)?'':'white',
zIndex: app.graph._nodes.indexOf(node),
zIndex: window.app.graph._nodes.indexOf(node),
});
this.inputEl.hidden = !visible;
},
@ -195,11 +195,11 @@ function addMultilineWidget(node, name, opts, app) {
node.addCustomWidget(widget);
app.canvas.onDrawBackground = function () {
window.app.canvas.onDrawBackground = function () {
// Draw node isnt fired once the node is off the screen
// 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.
for (let n in app.graph._nodes) {
for (let n in window.app.graph._nodes) {
n = graph._nodes[n];
for (let w in n.widgets) {
let wid = n.widgets[w];
@ -251,7 +251,7 @@ function addMultilineWidget(node, name, opts, app) {
}
function isSlider(display, app) {
if (app.ui.settings.getSettingValue("Comfy.DisableSliders")) {
if (window.app.ui.settings.getSettingValue("Comfy.DisableSliders")) {
return "number"
}
@ -321,7 +321,7 @@ export const ComfyWidgets = {
const img = new Image();
img.onload = () => {
node.imgs = [img];
app.graph.setDirtyCanvas(true);
window.app.graph.setDirtyCanvas(true);
};
let folder_separator = name.lastIndexOf("/");
let subfolder = "";
@ -329,7 +329,7 @@ export const ComfyWidgets = {
subfolder = name.substring(0, folder_separator);
name = name.substring(folder_separator + 1);
}
img.src = api.apiURL(`/view?filename=${name}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}`);
img.src = api.apiURL(`/view?filename=${name}&type=input&subfolder=${subfolder}${window.app.getPreviewFormatParam()}`);
node.setSizeForImage?.();
}
@ -457,3 +457,5 @@ export const ComfyWidgets = {
return { widget: uploadWidget };
},
};
export const customWidgets = []