diff --git a/package-lock.json b/package-lock.json index 72f5c6b..ab0658f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,13 +4,19 @@ "requires": true, "packages": { "": { - "name": "rgthree-comfy", "devDependencies": { + "@comfyorg/litegraph": "^0.11.3", "prettier": "^3.3.3", "sass": "^1.77.8", "typescript": "^5.5.4" } }, + "node_modules/@comfyorg/litegraph": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.11.3.tgz", + "integrity": "sha512-OEZJMFbC4WwsIW+w42MA8cwMY/Uu+OL5tQfvdhrlIj51G/TZv1yvU7tNPeu0gZM24Vm5rzF9IxXF8aajD9BIlw==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", diff --git a/package.json b/package.json index 1ad1e56..570b802 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "devDependencies": { "prettier": "^3.3.3", "typescript": "^5.5.4", - "sass": "^1.77.8" + "sass": "^1.77.8", + "@comfyorg/litegraph": "^0.11.3" }, "scripts": { "build": "./__build__.py || python .\\__build__.py" diff --git a/src_web/comfyui/any_switch.ts b/src_web/comfyui/any_switch.ts index 88c1d0f..7ecae45 100644 --- a/src_web/comfyui/any_switch.ts +++ b/src_web/comfyui/any_switch.ts @@ -1,4 +1,4 @@ -import type { INodeInputSlot, INodeOutputSlot, LLink } from "typings/litegraph.js"; +import type { INodeInputSlot, INodeOutputSlot, LLink } from "@litegraph/litegraph.js"; import type { ComfyApp, ComfyNodeConstructor, ComfyObjectInfo } from "typings/comfy.js"; import { app } from "scripts/app.js"; diff --git a/src_web/comfyui/base_any_input_connected_node.ts b/src_web/comfyui/base_any_input_connected_node.ts index ca0fcc8..5305acb 100644 --- a/src_web/comfyui/base_any_input_connected_node.ts +++ b/src_web/comfyui/base_any_input_connected_node.ts @@ -1,4 +1,3 @@ -import type { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js"; import type { Vector2, LLink, @@ -6,11 +5,12 @@ import type { INodeOutputSlot, LGraphNode as TLGraphNode, IWidget, -} from "typings/litegraph.js"; + ISlotType, +} from "@litegraph/litegraph.js"; -import { app } from "scripts/app.js"; -import { RgthreeBaseVirtualNode } from "./base_node.js"; -import { rgthree } from "./rgthree.js"; +import {app} from "scripts/app.js"; +import {RgthreeBaseVirtualNode} from "./base_node.js"; +import {rgthree} from "./rgthree.js"; import { PassThroughFollowing, @@ -21,6 +21,8 @@ import { getConnectedOutputNodes, getConnectedOutputNodesAndFilterPassThroughs, } from "./utils.js"; +import {ConnectByTypeOptions} from "@litegraph/LGraphNode.js"; +import {TWidgetType, IWidgetOptions} from "@litegraph/types/widgets.js"; /** * A Virtual Node that allows any node's output to connect to it. @@ -47,7 +49,7 @@ export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode { } override clone() { - const cloned = super.clone(); + const cloned = super.clone()!; // Copying to clipboard (and also, creating node templates) work by cloning nodes and, for some // reason, it manually manipulates the cloned data. So, we want to keep the present input slots // so if it's pasted/templatized the data is correct. Otherwise, clear the inputs and so the new @@ -83,7 +85,7 @@ export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode { * Ensures we have at least one empty input at the end, returns true if changes were made, or false * if no changes were needed. */ - private stabilizeInputsOutputs() : boolean { + private stabilizeInputsOutputs(): boolean { let changed = false; const hasEmptyInput = !this.inputs[this.inputs.length - 1]?.link; if (!hasEmptyInput) { @@ -142,7 +144,7 @@ export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode { * Handles stabilization of linked nodes. To be overridden. Should return true if changes were * made, or false if no changes were needed. */ - handleLinkedNodesStabilization(linkedNodes: TLGraphNode[]) : boolean { + handleLinkedNodesStabilization(linkedNodes: TLGraphNode[]): boolean { linkedNodes; // No-op, but makes overridding in VSCode cleaner. throw new Error("handleLinkedNodesStabilization should be overridden."); } @@ -181,13 +183,13 @@ export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode { return super.addInput(name, type, extra_info); } - override addWidget( - type: T["type"], + override addWidget( + type: TWidgetType, name: string, - value: T["value"], - callback?: T["callback"] | string, - options?: T["options"], - ) { + value: string | number | boolean | object, + callback: IWidget["callback"] | string | null, + options?: IWidgetOptions | string, + ): IWidget { (this as any)._tempWidth = this.size[0]; return super.addWidget(type, name, value, callback, options); } @@ -286,12 +288,12 @@ export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode { * If something is dropped on us, just add it to the bottom. onConnectInput should already cancel * if it's disallowed. */ - override connectByTypeOutput( - slot: string | number, + override connectByTypeOutput( + slot: number | string, sourceNode: TLGraphNode, - sourceSlotType: string, - optsIn: string, - ): T | null { + sourceSlotType: ISlotType, + optsIn?: ConnectByTypeOptions, + ): LLink | null { const lastInput = this.inputs[this.inputs.length - 1]; if (!lastInput?.link && lastInput?.type === "*") { var sourceSlot = sourceNode.findOutputSlotByType(sourceSlotType, false, true); @@ -323,23 +325,25 @@ export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode { // that and instead take the next free one. If that doesn't work, then we'll give it to the old // method. const oldLGraphNodeConnectByType = LGraphNode.prototype.connectByType; -LGraphNode.prototype.connectByType = function connectByType( - slot: string | number, - sourceNode: TLGraphNode, - sourceSlotType: string, - optsIn: string, -): T | null { - // If we're droppiong on a node, and the last input is free and an "*" type, then connect there +LGraphNode.prototype.connectByType = function connectByType( + slot: number | string, + targetNode: TLGraphNode, + targetSlotType: ISlotType, + optsIn?: ConnectByTypeOptions, +): LLink | null { + // If we're dropping on a node, and the last input is free and an "*" type, then connect there // first... - if (sourceNode.inputs) { - for (const [index, input] of sourceNode.inputs.entries()) { + if (targetNode.inputs) { + for (const [index, input] of targetNode.inputs.entries()) { if (!input.link && input.type === "*") { - this.connect(slot, sourceNode, index); + this.connect(slot, targetNode, index); return null; } } } - return ((oldLGraphNodeConnectByType && - oldLGraphNodeConnectByType.call(this, slot, sourceNode, sourceSlotType, optsIn)) || - null) as T; + return ( + (oldLGraphNodeConnectByType && + oldLGraphNodeConnectByType.call(this, slot, targetNode, targetSlotType, optsIn)) || + null + ); }; diff --git a/src_web/comfyui/base_node.ts b/src_web/comfyui/base_node.ts index b2779da..b8274c2 100644 --- a/src_web/comfyui/base_node.ts +++ b/src_web/comfyui/base_node.ts @@ -1,26 +1,25 @@ -import type { ComfyNodeConstructor, ComfyObjectInfo, NodeMode } from "typings/comfy.js"; +import type {ComfyNodeConstructor, ComfyObjectInfo} from "typings/comfy.js"; import type { IWidget, - SerializedLGraphNode, - LGraphNode as TLGraphNode, LGraphCanvas, - ContextMenuItem, - INodeOutputSlot, - INodeInputSlot, -} from "typings/litegraph.js"; -import type { RgthreeBaseServerNodeConstructor, RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js"; + IContextMenuValue, + IFoundSlot, + LGraphEventMode, +} from "@litegraph/litegraph.js"; +import type {ISerialisedNode} from "@litegraph/types/serialisation.js"; +import type {RgthreeBaseServerNodeConstructor} from "typings/rgthree.js"; -import { ComfyWidgets } from "scripts/widgets.js"; -import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js"; -import { app } from "scripts/app.js"; -import { LogLevel, rgthree } from "./rgthree.js"; -import { addHelpMenuItem } from "./utils.js"; -import { RgthreeHelpDialog } from "rgthree/common/dialog.js"; +import {ComfyWidgets} from "scripts/widgets.js"; +import {SERVICE as KEY_EVENT_SERVICE} from "./services/key_events_services.js"; +import {app} from "scripts/app.js"; +import {LogLevel, rgthree} from "./rgthree.js"; +import {addHelpMenuItem} from "./utils.js"; +import {RgthreeHelpDialog} from "rgthree/common/dialog.js"; import { importIndividualNodesInnerOnDragDrop, importIndividualNodesInnerOnDragOver, } from "./feature_import_individual_nodes.js"; -import { defineProperty } from "rgthree/common/shared_utils.js"; +import {defineProperty} from "rgthree/common/shared_utils.js"; /** * A base node with standard methods, directly extending the LGraphNode. @@ -33,9 +32,12 @@ export abstract class RgthreeBaseNode extends LGraphNode { static exposedActions: string[] = []; static override title: string = "__NEED_CLASS_TITLE__"; - static category = "rgthree"; + static override category = "rgthree"; static _category = "rgthree"; // `category` seems to get reset by comfy, so reset to this after. + /** Our constructor ensures there's a widget array, so we get rid of the nullability. */ + override widgets!: IWidget[]; + /** * The comfyClass is property ComfyUI and extensions may care about, even through it is only for * server nodes. RgthreeBaseServerNode below overrides this with the expected value and we just @@ -58,7 +60,8 @@ export abstract class RgthreeBaseNode extends LGraphNode { _tempWidth = 0; /** Private Mode member so we can override the setter/getter and call an `onModeChange`. */ - private rgthree_mode: NodeMode; + private rgthree_mode?: LGraphEventMode; + /** An internal bool set when `onConstructed` is run. */ private __constructed__ = false; /** The help dialog. */ @@ -84,11 +87,11 @@ export abstract class RgthreeBaseNode extends LGraphNode { this.checkAndRunOnConstructed(); }); - defineProperty(this, 'mode', { + defineProperty(this, "mode", { get: () => { return this.rgthree_mode; }, - set: (mode: NodeMode) => { + set: (mode: LGraphEventMode) => { if (this.rgthree_mode != mode) { const oldMode = this.rgthree_mode; this.rgthree_mode = mode; @@ -134,7 +137,7 @@ export abstract class RgthreeBaseNode extends LGraphNode { return this.__constructed__; } - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode): void { this.configuring = true; super.configure(info); // Fix https://github.com/comfyanonymous/ComfyUI/issues/1448 locally. @@ -149,17 +152,17 @@ export abstract class RgthreeBaseNode extends LGraphNode { * Override clone for, at the least, deep-copying properties. */ override clone() { - const cloned = super.clone(); - // This is whild, but LiteGraph clone doesn't deep clone data, so we will. We'll use structured - // clone, which most browsers in 2022 support, but but we'll check. - if (cloned.properties && !!window.structuredClone) { + const cloned = super.clone()!; + // This is wild, but LiteGraph doesn't deep clone data, so we will. We'll use structured clone, + // which most browsers in 2022 support, but but we'll check. + if (cloned?.properties && !!window.structuredClone) { cloned.properties = structuredClone(cloned.properties); } return cloned; } /** When a mode change, we want all connected nodes to match. */ - onModeChange(from: NodeMode, to: NodeMode) { + onModeChange(from: LGraphEventMode | undefined, to: LGraphEventMode) { // Override } @@ -175,6 +178,9 @@ export abstract class RgthreeBaseNode extends LGraphNode { * Guess this doesn't exist in Litegraph... */ removeWidget(widgetOrSlot?: IWidget | number) { + if (!this.widgets) { + return; + } if (typeof widgetOrSlot === "number") { this.widgets.splice(widgetOrSlot, 1); } else if (widgetOrSlot) { @@ -191,23 +197,20 @@ export abstract class RgthreeBaseNode extends LGraphNode { * it's default logic. This bakes it so child nodes can call this instead (and this doesn't set * getSlotMenuOptions for all child nodes in case it doesn't exist). */ - defaultGetSlotMenuOptions(slot: { - input?: INodeInputSlot; - output?: INodeOutputSlot; - }): ContextMenuItem[] | null { - const menu_info: ContextMenuItem[] = []; + defaultGetSlotMenuOptions(slot: IFoundSlot): IContextMenuValue[] { + const menu_info: IContextMenuValue[] = []; if (slot?.output?.links?.length) { - menu_info.push({ content: "Disconnect Links", slot: slot }); + menu_info.push({content: "Disconnect Links", slot}); } let inputOrOutput = slot.input || slot.output; if (inputOrOutput) { if (inputOrOutput.removable) { menu_info.push( - inputOrOutput.locked ? { content: "Cannot remove" } : { content: "Remove Slot", slot }, + inputOrOutput.locked ? {content: "Cannot remove"} : {content: "Remove Slot", slot}, ); } if (!inputOrOutput.nameLocked) { - menu_info.push({ content: "Rename Slot", slot }); + menu_info.push({content: "Rename Slot", slot}); } } return menu_info; @@ -250,7 +253,10 @@ export abstract class RgthreeBaseNode extends LGraphNode { KEY_EVENT_SERVICE.handleKeyDownOrUp(event); } - override getExtraMenuOptions(canvas: LGraphCanvas, options: ContextMenuItem[]): void { + override getExtraMenuOptions( + canvas: LGraphCanvas, + options: (IContextMenuValue | null)[], + ): (IContextMenuValue | null)[] { // Some other extensions override getExtraMenuOptions on the nodeType as it comes through from // the server, so we can call out to that if we don't have our own. if (super.getExtraMenuOptions) { @@ -266,6 +272,7 @@ export abstract class RgthreeBaseNode extends LGraphNode { if (help) { addHelpMenuItem(this, help, options); } + return options; } } @@ -339,7 +346,7 @@ export class RgthreeBaseServerNode extends RgthreeBaseNode { const WIDGETS = this.getWidgets(); - const config: { minWidth: number; minHeight: number; widget?: null | { options: any } } = { + const config: {minWidth: number; minHeight: number; widget?: null | {options: any}} = { minWidth: 1, minHeight: 1, widget: null, @@ -392,7 +399,7 @@ export class RgthreeBaseServerNode extends RgthreeBaseNode { const outputShape = nodeData["output_is_list"][o] ? LiteGraph.GRID_SHAPE : LiteGraph.CIRCLE_SHAPE; - this.addOutput(outputName, output, { shape: outputShape }); + this.addOutput(outputName, output, {shape: outputShape}); } const s = this.computeSize(); diff --git a/src_web/comfyui/base_node_collector.ts b/src_web/comfyui/base_node_collector.ts index 73e76ba..099b6d6 100644 --- a/src_web/comfyui/base_node_collector.ts +++ b/src_web/comfyui/base_node_collector.ts @@ -1,4 +1,4 @@ -import type { INodeOutputSlot, LGraphNode } from "typings/litegraph.js"; +import type { INodeOutputSlot, LGraphNode } from "@litegraph/litegraph.js"; import { rgthree } from "./rgthree.js"; import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js"; import { @@ -25,7 +25,7 @@ export class BaseCollectorNode extends BaseAnyInputConnectedNode { } override clone() { - const cloned = super.clone(); + const cloned = super.clone()!; return cloned; } diff --git a/src_web/comfyui/base_node_mode_changer.ts b/src_web/comfyui/base_node_mode_changer.ts index e8fc47c..fdd2673 100644 --- a/src_web/comfyui/base_node_mode_changer.ts +++ b/src_web/comfyui/base_node_mode_changer.ts @@ -1,9 +1,6 @@ -import type { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js"; -import type { - LGraphNode as TLGraphNode, - IWidget, - SerializedLGraphNode, -} from "typings/litegraph.js"; +import type { LGraphNode, IWidget} from "@litegraph/litegraph.js"; +import type { ISerialisedNode } from "@litegraph/types/serialisation.js"; + import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js"; import { PassThroughFollowing } from "./utils.js"; import { wait } from "rgthree/common/shared_utils.js"; @@ -38,7 +35,7 @@ export class BaseNodeModeChanger extends BaseAnyInputConnectedNode { return super.onConstructed(); } - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode): void { // Patch a small issue (~14h) where multiple OPT_CONNECTIONS may have been created. // https://github.com/rgthree/rgthree-comfy/issues/206 // TODO: This can probably be removed within a few weeks. @@ -48,7 +45,7 @@ export class BaseNodeModeChanger extends BaseAnyInputConnectedNode { super.configure(info); } - override handleLinkedNodesStabilization(linkedNodes: TLGraphNode[]) { + override handleLinkedNodesStabilization(linkedNodes: LGraphNode[]) { let changed = false; for (const [index, node] of linkedNodes.entries()) { let widget = this.widgets && this.widgets[index]; @@ -70,7 +67,7 @@ export class BaseNodeModeChanger extends BaseAnyInputConnectedNode { return changed; } - private setWidget(widget: IWidget, linkedNode: TLGraphNode, forceValue?: boolean) { + private setWidget(widget: IWidget, linkedNode: LGraphNode, forceValue?: boolean) { let changed = false; const value = forceValue == null ? linkedNode.mode === this.modeOn : forceValue; let name = `Enable ${linkedNode.title}`; @@ -82,7 +79,7 @@ export class BaseNodeModeChanger extends BaseAnyInputConnectedNode { (widget as any).doModeChange = (forceValue?: boolean, skipOtherNodeCheck?: boolean) => { let newValue = forceValue == null ? linkedNode.mode === this.modeOff : forceValue; if (skipOtherNodeCheck !== true) { - if (newValue && this.properties?.["toggleRestriction"]?.includes(" one")) { + if (newValue && (this.properties?.["toggleRestriction"] as string)?.includes(" one")) { for (const widget of this.widgets) { (widget as any).doModeChange(false, true); } diff --git a/src_web/comfyui/base_power_prompt.ts b/src_web/comfyui/base_power_prompt.ts index e0e262e..3c28bf9 100644 --- a/src_web/comfyui/base_power_prompt.ts +++ b/src_web/comfyui/base_power_prompt.ts @@ -1,16 +1,17 @@ -import { api } from "scripts/api.js"; import type { LLink, - IComboWidget, LGraphNode, INodeOutputSlot, INodeInputSlot, IWidget, - SerializedLGraphNode, -} from "typings/litegraph.js"; -import type { ComfyObjectInfo, ComfyGraphNode } from "typings/comfy.js"; -import { wait } from "rgthree/common/shared_utils.js"; -import { rgthree } from "./rgthree.js"; +} from "@litegraph/litegraph.js"; +import type {ISerialisedNode} from "@litegraph/types/serialisation.js"; +import type {IComboWidget} from "@litegraph/types/widgets.js"; +import type {ComfyObjectInfo, ComfyGraphNode} from "typings/comfy.js"; + +import {api} from "scripts/api.js"; +import {wait} from "rgthree/common/shared_utils.js"; +import {rgthree} from "./rgthree.js"; /** Wraps a node instance keeping closure without mucking the finicky types. */ export class PowerPrompt { @@ -18,8 +19,8 @@ export class PowerPrompt { readonly node: ComfyGraphNode; readonly promptEl: HTMLTextAreaElement; nodeData: ComfyObjectInfo; - readonly combos: { [key: string]: IComboWidget } = {}; - readonly combosValues: { [key: string]: string[] } = {}; + readonly combos: {[key: string]: IComboWidget} = {}; + readonly combosValues: {[key: string]: string[]} = {}; boundOnFreshNodeDefs!: (event: CustomEvent) => void; private configuring = false; @@ -33,13 +34,13 @@ export class PowerPrompt { this.nodeData = nodeData; this.isSimple = this.nodeData.name.includes("Simple"); - this.promptEl = (node.widgets[0]! as any).inputEl; + this.promptEl = (node.widgets![0]! as any).inputEl; this.addAndHandleKeyboardLoraEditWeight(); this.patchNodeRefresh(); const oldConfigure = this.node.configure; - this.node.configure = (info: SerializedLGraphNode) => { + this.node.configure = (info: ISerialisedNode) => { this.configuring = true; oldConfigure?.apply(this.node, [info]); this.configuring = false; @@ -109,17 +110,18 @@ export class PowerPrompt { const onPropertyChanged = this.node.onPropertyChanged; this.node.onPropertyChanged = (property: string, value: any, prevValue: any) => { - onPropertyChanged && onPropertyChanged.call(this, property, value, prevValue); + const v = onPropertyChanged && onPropertyChanged.call(this.node, property, value, prevValue); if (property === "combos_filter") { this.refreshCombos(this.nodeData); } + return v ?? true; }; // Strip all widgets but prompt (we'll re-add them in refreshCombos) // this.node.widgets.splice(1); - for (let i = this.node.widgets.length - 1; i >= 0; i--) { - if (this.shouldRemoveServerWidget(this.node.widgets[i]!)) { - this.node.widgets.splice(i, 1); + for (let i = this.node.widgets!.length - 1; i >= 0; i--) { + if (this.shouldRemoveServerWidget(this.node.widgets![i]!)) { + this.node.widgets!.splice(i, 1); } } @@ -187,9 +189,9 @@ export class PowerPrompt { refreshCombos(nodeData: ComfyObjectInfo) { this.nodeData = nodeData; let filter: RegExp | null = null; - if (this.node.properties["combos_filter"]?.trim()) { + if ((this.node.properties["combos_filter"] as string)?.trim()) { try { - filter = new RegExp(this.node.properties["combos_filter"].trim(), "i"); + filter = new RegExp((this.node.properties["combos_filter"] as string).trim(), "i"); } catch (e) { console.error(`Could not parse "${filter}" for Regular Expression`, e); filter = null; @@ -235,7 +237,7 @@ export class PowerPrompt { } else if (key.includes("lora")) { this.insertSelectionText(``); } - this.combos[key]!.value = values[0]; + this.combos[key]!.value = values[0]!; }); } }, @@ -243,7 +245,7 @@ export class PowerPrompt { values, serialize: true, // Don't include this in prompt. }, - ); + ) as IComboWidget; (this.combos[key]! as any).oldComputeSize = this.combos[key]!.computeSize; let node = this.node; this.combos[key]!.computeSize = function (width: number) { @@ -251,16 +253,16 @@ export class PowerPrompt { width, LiteGraph.NODE_WIDGET_HEIGHT, ]; - if (this === node.widgets[node.widgets.length - 1]) { + if (this === node.widgets![node.widgets!.length - 1]) { size[1] += 10; } return size; }; } this.combos[key]!.options!.values = values; - this.combos[key]!.value = values[0]; + this.combos[key]!.value = values[0]!; } else if (!shouldShow && this.combos[key]) { - this.node.widgets.splice(this.node.widgets.indexOf(this.combos[key]!), 1); + this.node.widgets!.splice(this.node.widgets!.indexOf(this.combos[key]!), 1); delete this.combos[key]; } } else if (key.startsWith("values")) { diff --git a/src_web/comfyui/bookmark.ts b/src_web/comfyui/bookmark.ts index c5aa4f4..e771e06 100644 --- a/src_web/comfyui/bookmark.ts +++ b/src_web/comfyui/bookmark.ts @@ -1,15 +1,12 @@ -import { app } from "scripts/app.js"; -import { RgthreeBaseVirtualNode } from "./base_node.js"; -import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js"; -import { NodeTypesString } from "./constants.js"; -import type { - LGraph, - LGraphCanvas, - INumberWidget, - LGraphNode, - Vector2, -} from "typings/litegraph.js"; -import { getClosestOrSelf, queryOne } from "rgthree/common/utils_dom.js"; +import type {LGraph, LGraphCanvas, LGraphNode} from "@litegraph/litegraph.js"; +import type {Point} from "@litegraph/interfaces.js"; +import type {CanvasMouseEvent} from "@litegraph/types/events.js"; + +import {app} from "scripts/app.js"; +import {RgthreeBaseVirtualNode} from "./base_node.js"; +import {SERVICE as KEY_EVENT_SERVICE} from "./services/key_events_services.js"; +import {NodeTypesString} from "./constants.js"; +import {getClosestOrSelf, queryOne} from "rgthree/common/utils_dom.js"; /** * A bookmark node. Can be placed anywhere in the workflow, and given a shortcut key that will @@ -61,7 +58,7 @@ export class Bookmark extends RgthreeBaseVirtualNode { y: 8, }, ); - this.addWidget("number", "zoom", 1, (value: number) => {}, { + this.addWidget("number", "zoom", 1, (value: number) => {}, { y: 8 + LiteGraph.NODE_WIDGET_HEIGHT + 4, max: 2, min: 0.5, @@ -79,7 +76,7 @@ export class Bookmark extends RgthreeBaseVirtualNode { // } get shortcutKey(): string { - return this.widgets[0]?.value?.toLocaleLowerCase() ?? ""; + return (this.widgets[0]?.value as string)?.toLocaleLowerCase() ?? ""; } override onAdded(graph: LGraph): void { @@ -90,15 +87,15 @@ export class Bookmark extends RgthreeBaseVirtualNode { KEY_EVENT_SERVICE.removeEventListener("keydown", this.keypressBound as EventListener); } - onKeypress(event: CustomEvent<{ originalEvent: KeyboardEvent }>) { + onKeypress(event: CustomEvent<{originalEvent: KeyboardEvent}>) { const originalEvent = event.detail.originalEvent; const target = (originalEvent.target as HTMLElement)!; if (getClosestOrSelf(target, 'input,textarea,[contenteditable="true"]')) { return; } - // Only the shortcut keys are held down, otionally including "shift". - if (KEY_EVENT_SERVICE.areOnlyKeysDown(this.widgets[0]!.value, true)) { + // Only the shortcut keys are held down, optionally including "shift". + if (KEY_EVENT_SERVICE.areOnlyKeysDown(this.widgets[0]!.value as string, true)) { this.canvasToBookmark(); originalEvent.preventDefault(); originalEvent.stopPropagation(); @@ -108,9 +105,9 @@ export class Bookmark extends RgthreeBaseVirtualNode { /** * Called from LiteGraph's `processMouseDown` after it would invoke the input box for the * shortcut_key, so we check if it exists and then add our own event listener so we can track the - * keys down for the user. + * keys down for the user. Note, blocks drag if the return is truthy. */ - override onMouseDown(event: MouseEvent, pos: Vector2, graphCanvas: LGraphCanvas): void { + override onMouseDown(event: CanvasMouseEvent, pos: Point, graphCanvas: LGraphCanvas): boolean { const input = queryOne(".graphdialog > input.value"); if (input && input.value === this.widgets[0]?.value) { input.addEventListener("keydown", (e) => { @@ -121,6 +118,7 @@ export class Bookmark extends RgthreeBaseVirtualNode { input.value = Object.keys(KEY_EVENT_SERVICE.downKeys).join(" + "); }); } + return false; } canvasToBookmark() { diff --git a/src_web/comfyui/bypasser.ts b/src_web/comfyui/bypasser.ts index f469012..c7fb383 100644 --- a/src_web/comfyui/bypasser.ts +++ b/src_web/comfyui/bypasser.ts @@ -1,7 +1,8 @@ -import { app } from "scripts/app.js"; -import { BaseNodeModeChanger } from "./base_node_mode_changer.js"; -import { NodeTypesString } from "./constants.js"; -import type { LGraphNode } from "typings/litegraph.js"; +import type {LGraphNode} from "@litegraph/litegraph.js"; + +import {app} from "scripts/app.js"; +import {BaseNodeModeChanger} from "./base_node_mode_changer.js"; +import {NodeTypesString} from "./constants.js"; const MODE_BYPASS = 4; const MODE_ALWAYS = 0; @@ -23,15 +24,15 @@ class BypasserNode extends BaseNodeModeChanger { override async handleAction(action: string) { if (action === "Bypass all") { - for (const widget of this.widgets) { + for (const widget of this.widgets || []) { this.forceWidgetOff(widget, true); } } else if (action === "Enable all") { - for (const widget of this.widgets) { + for (const widget of this.widgets || []) { this.forceWidgetOn(widget, true); } } else if (action === "Toggle all") { - for (const widget of this.widgets) { + for (const widget of this.widgets || []) { this.forceWidgetToggle(widget, true); } } diff --git a/src_web/comfyui/context.ts b/src_web/comfyui/context.ts index b87583a..d0a6afc 100644 --- a/src_web/comfyui/context.ts +++ b/src_web/comfyui/context.ts @@ -4,9 +4,11 @@ import type { LGraphCanvas as TLGraphCanvas, LGraphNode as TLGraphNode, LLink, -} from "typings/litegraph.js"; -import type { ComfyNodeConstructor, ComfyObjectInfo } from "typings/comfy.js"; -import { app } from "scripts/app.js"; + ISlotType, +} from "@litegraph/litegraph.js"; +import type {ComfyNodeConstructor, ComfyObjectInfo} from "typings/comfy.js"; + +import {app} from "scripts/app.js"; import { IoDirection, addConnectionLayoutSupport, @@ -14,12 +16,13 @@ import { matchLocalSlotsToServer, replaceNode, } from "./utils.js"; -import { RgthreeBaseServerNode } from "./base_node.js"; -import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js"; -import { RgthreeBaseServerNodeConstructor } from "typings/rgthree.js"; -import { debounce, wait } from "rgthree/common/shared_utils.js"; -import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js"; -import { NodeTypesString } from "./constants.js"; +import {RgthreeBaseServerNode} from "./base_node.js"; +import {SERVICE as KEY_EVENT_SERVICE} from "./services/key_events_services.js"; +import {RgthreeBaseServerNodeConstructor} from "typings/rgthree.js"; +import {debounce, wait} from "rgthree/common/shared_utils.js"; +import {removeUnusedInputsFromEnd} from "./utils_inputs_outputs.js"; +import {NodeTypesString} from "./constants.js"; +import {ConnectByTypeOptions} from "@litegraph/LGraphNode.js"; /** * Takes a non-context node and determins for its input or output slot, if there is a valid @@ -109,21 +112,19 @@ export class BaseContextNode extends RgthreeBaseServerNode { ctx.font = oldFont; } - override connectByType( - slot: string | number, - sourceNode: TLGraphNode, - sourceSlotType: string, - optsIn: string, - ): T | null { - let canConnect = - super.connectByType && - super.connectByType.call(this, slot, sourceNode, sourceSlotType, optsIn); + override connectByType( + slot: number | string, + targetNode: TLGraphNode, + targetSlotType: ISlotType, + optsIn?: ConnectByTypeOptions, + ): LLink | null { + let canConnect = super.connectByType?.call(this, slot, targetNode, targetSlotType, optsIn); if (!super.connectByType) { canConnect = LGraphNode.prototype.connectByType.call( this, slot, - sourceNode, - sourceSlotType, + targetNode, + targetSlotType, optsIn, ); } @@ -132,28 +133,32 @@ export class BaseContextNode extends RgthreeBaseServerNode { // Okay, we've dragged a context and it can't connect.. let's connect all the other nodes. // Unfortunately, we don't know which are null now, so we'll just connect any that are // not already connected. - for (const [index, input] of (sourceNode.inputs || []).entries()) { + for (const [index, input] of (targetNode.inputs || []).entries()) { if (input.link && !ctrlKey) { continue; } - const thisOutputSlot = findMatchingIndexByTypeOrName(sourceNode, input, this.outputs); + const thisOutputSlot = findMatchingIndexByTypeOrName(targetNode, input, this.outputs); if (thisOutputSlot > -1) { - this.connect(thisOutputSlot, sourceNode, index); + this.connect(thisOutputSlot, targetNode, index); } } } return null; } - override connectByTypeOutput( - slot: string | number, + override connectByTypeOutput( + slot: number | string, sourceNode: TLGraphNode, - sourceSlotType: string, - optsIn: string, - ): T | null { - let canConnect = - super.connectByTypeOutput && - super.connectByTypeOutput.call(this, slot, sourceNode, sourceSlotType, optsIn); + sourceSlotType: ISlotType, + optsIn?: ConnectByTypeOptions, + ): LLink | null { + let canConnect = super.connectByTypeOutput?.call( + this, + slot, + sourceNode, + sourceSlotType, + optsIn, + ); if (!super.connectByType) { canConnect = LGraphNode.prototype.connectByTypeOutput.call( this, @@ -426,7 +431,7 @@ const contextNodes = [ ContextMergeNode, ContextMergeBigNode, ]; -const contextTypeToServerDef: { [type: string]: ComfyObjectInfo } = {}; +const contextTypeToServerDef: {[type: string]: ComfyObjectInfo} = {}; function fixBadConfigs(node: ContextNode) { // Dumb mistake, but let's fix our mispelling. This will probably need to stay in perpetuity to diff --git a/src_web/comfyui/display_any.ts b/src_web/comfyui/display_any.ts index 095dae0..e78b7f0 100644 --- a/src_web/comfyui/display_any.ts +++ b/src_web/comfyui/display_any.ts @@ -1,9 +1,10 @@ -import { app } from "scripts/app.js"; -import { ComfyWidgets } from "scripts/widgets.js"; -import type { LGraphNode as TLGraphNode } from "typings/litegraph.js"; -import type { ComfyApp, ComfyNodeConstructor, ComfyObjectInfo } from "typings/comfy.js"; -import { addConnectionLayoutSupport } from "./utils.js"; -import { rgthree } from "./rgthree.js"; +import type {LGraphNode as TLGraphNode} from "@litegraph/litegraph.js"; +import type {ComfyApp, ComfyNodeConstructor, ComfyObjectInfo} from "typings/comfy.js"; + +import {app} from "scripts/app.js"; +import {ComfyWidgets} from "scripts/widgets.js"; +import {addConnectionLayoutSupport} from "./utils.js"; +import {rgthree} from "./rgthree.js"; let hasShownAlertForUpdatingInt = false; @@ -22,7 +23,7 @@ app.registerExtension({ (this as any).showValueWidget = ComfyWidgets["STRING"]( this, "output", - ["STRING", { multiline: true }], + ["STRING", {multiline: true}], app, ).widget; (this as any).showValueWidget.inputEl!.readOnly = true; diff --git a/src_web/comfyui/dynamic_context.ts b/src_web/comfyui/dynamic_context.ts index f637647..eaf4fbe 100644 --- a/src_web/comfyui/dynamic_context.ts +++ b/src_web/comfyui/dynamic_context.ts @@ -1,3 +1,13 @@ +import type { + IContextMenuValue, + IFoundSlot, + INodeInputSlot, + INodeOutputSlot, + ISlotType, + LGraphNode, + LLink, +} from "@litegraph/litegraph.js"; + import {app} from "scripts/app.js"; import { IoDirection, @@ -12,7 +22,6 @@ import { } from "./services/context_service.js"; import {NodeTypesString} from "./constants.js"; import {removeUnusedInputsFromEnd} from "./utils_inputs_outputs.js"; -import {INodeInputSlot, INodeOutputSlot, INodeSlot, LGraphNode, LLink} from "typings/litegraph.js"; import {ComfyNodeConstructor, ComfyObjectInfo} from "typings/comfy.js"; import {DynamicContextNodeBase} from "./dynamic_context_base.js"; import {SERVICE as CONFIG_SERVICE} from "./services/config_service.js"; @@ -40,11 +49,11 @@ export class DynamicContextNode extends DynamicContextNodeBase { } override onConnectionsChange( - type: number, + type: ISlotType, slotIndex: number, isConnected: boolean, - link: LLink, - ioSlot: INodeSlot, + link: LLink | null | undefined, + ioSlot: INodeInputSlot | INodeOutputSlot, ): void { super.onConnectionsChange?.call(this, type, slotIndex, isConnected, link, ioSlot); if (this.configuring) { @@ -225,11 +234,7 @@ export class DynamicContextNode extends DynamicContextNodeBase { } } - override getSlotMenuOptions(slot: { - slot: number; - input?: INodeInputSlot | undefined; - output?: INodeOutputSlot | undefined; - }) { + override getSlotMenuOptions(slot: IFoundSlot): IContextMenuValue[] { const editable = this.isOwnedInput(slot.input!.name) && this.type !== "*"; return [ { diff --git a/src_web/comfyui/dynamic_context_base.ts b/src_web/comfyui/dynamic_context_base.ts index 6fe703b..a763264 100644 --- a/src_web/comfyui/dynamic_context_base.ts +++ b/src_web/comfyui/dynamic_context_base.ts @@ -1,4 +1,4 @@ -import type {INodeInputSlot} from "typings/litegraph.js"; +import type {INodeInputSlot} from "@litegraph/litegraph.js"; import {BaseContextNode} from "./context.js"; import {ComfyNodeConstructor, ComfyObjectInfo} from "typings/comfy.js"; @@ -19,7 +19,7 @@ const REGEX_EMPTY_INPUT = /^\+\s*$/; export type InputLike = { name: string; - type: string | -1; + type: number | string; label?: string; link: number | null; removable?: boolean; @@ -96,7 +96,7 @@ export class DynamicContextNodeBase extends BaseContextNode { } } override clone() { - const cloned = super.clone(); + const cloned = super.clone()! as DynamicContextNodeBase; while (cloned.inputs.length > 1) { cloned.removeInput(cloned.inputs.length - 1); } diff --git a/src_web/comfyui/dynamic_context_switch.ts b/src_web/comfyui/dynamic_context_switch.ts index 34c7db7..d08c019 100644 --- a/src_web/comfyui/dynamic_context_switch.ts +++ b/src_web/comfyui/dynamic_context_switch.ts @@ -1,5 +1,5 @@ import type {ComfyNodeConstructor, ComfyObjectInfo} from "typings/comfy.js"; -import type {INodeSlot, LGraphNode, LLink, LGraphCanvas} from "typings/litegraph.js"; +import type {INodeSlot, LGraphNode, LLink, LGraphCanvas, INodeInputSlot, INodeOutputSlot, ISlotType} from "@litegraph/litegraph.js"; import {app} from "scripts/app.js"; import {DynamicContextNodeBase, InputLike} from "./dynamic_context_base.js"; @@ -65,14 +65,8 @@ class DynamicContextSwitchNode extends DynamicContextNodeBase { this.scheduleHardRefresh(); } - override onConnectionsChange( - type: number, - slotIndex: number, - isConnected: boolean, - link: LLink, - ioSlot: INodeSlot, - ): void { - super.onConnectionsChange?.call(this, type, slotIndex, isConnected, link, ioSlot); + override onConnectionsChange(type: ISlotType, slotIndex: number, isConnected: boolean, link: LLink | null | undefined, inputOrOutput: INodeInputSlot | INodeOutputSlot): void { + super.onConnectionsChange?.call(this, type, slotIndex, isConnected, link, inputOrOutput); if (this.configuring) { return; } diff --git a/src_web/comfyui/fast_actions_button.ts b/src_web/comfyui/fast_actions_button.ts index 13faae2..eea4380 100644 --- a/src_web/comfyui/fast_actions_button.ts +++ b/src_web/comfyui/fast_actions_button.ts @@ -1,13 +1,14 @@ -import type { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js"; -import type { ComfyApp, ComfyWidget } from "typings/comfy.js"; -import type { IWidget, LGraph, LGraphNode, SerializedLGraphNode } from "typings/litegraph.js"; -import type { RgthreeBaseNode } from "./base_node.js"; +import type {LGraph, LGraphNode} from "@litegraph/litegraph.js"; +import type {IButtonWidget, IComboWidget, IWidget} from "@litegraph/types/widgets.js"; +import type {ComfyApp} from "typings/comfy.js"; +import type {RgthreeBaseNode} from "./base_node.js"; -import { app } from "scripts/app.js"; -import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js"; -import { NodeTypesString } from "./constants.js"; -import { addMenuItem } from "./utils.js"; -import { rgthree } from "./rgthree.js"; +import {app} from "scripts/app.js"; +import {BaseAnyInputConnectedNode} from "./base_any_input_connected_node.js"; +import {NodeTypesString} from "./constants.js"; +import {addMenuItem} from "./utils.js"; +import {rgthree} from "./rgthree.js"; +import {ISerialisedNode} from "@litegraph/types/serialisation.js"; const MODE_ALWAYS = 0; const MODE_MUTE = 2; @@ -27,12 +28,12 @@ class FastActionsButton extends BaseAnyInputConnectedNode { readonly logger = rgthree.newLogSession("[FastActionsButton]"); - static "@buttonText" = { type: "string" }; + static "@buttonText" = {type: "string"}; static "@shortcutModifier" = { type: "combo", values: ["ctrl", "alt", "shift"], }; - static "@shortcutKey" = { type: "string" }; + static "@shortcutKey" = {type: "string"}; static collapsible = false; @@ -40,9 +41,9 @@ class FastActionsButton extends BaseAnyInputConnectedNode { override serialize_widgets = true; - readonly buttonWidget: IWidget; + readonly buttonWidget: IButtonWidget; - readonly widgetToData = new Map(); + readonly widgetToData = new Map(); readonly nodeIdtoFunctionCache = new Map(); readonly keypressBound; @@ -50,6 +51,12 @@ class FastActionsButton extends BaseAnyInputConnectedNode { private executingFromShortcut = false; + override properties!: BaseAnyInputConnectedNode["properties"] & { + buttonText: string; + shortcutModifier: string; + shortcutKey: string; + }; + constructor(title?: string) { super(title); this.properties["buttonText"] = "🎬 Action!"; @@ -58,12 +65,12 @@ class FastActionsButton extends BaseAnyInputConnectedNode { this.buttonWidget = this.addWidget( "button", this.properties["buttonText"], - null, + "", () => { this.executeConnectedNodes(); }, - { serialize: false }, - ); + {serialize: false}, + ) as IButtonWidget; this.keypressBound = this.onKeypress.bind(this); this.keyupBound = this.onKeyup.bind(this); @@ -71,7 +78,7 @@ class FastActionsButton extends BaseAnyInputConnectedNode { } /** When we're given data to configure, like from a PNG or JSON. */ - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode): void { super.configure(info); // Since we add the widgets dynamically, we need to wait to set their values // with a short timeout. @@ -79,7 +86,7 @@ class FastActionsButton extends BaseAnyInputConnectedNode { if (info.widgets_values) { for (let [index, value] of info.widgets_values.entries()) { if (index > 0) { - if (value.startsWith("comfy_action:")) { + if (typeof value === "string" && value.startsWith("comfy_action:")) { value = value.replace("comfy_action:", ""); this.addComfyActionWidget(index, value); } @@ -93,7 +100,7 @@ class FastActionsButton extends BaseAnyInputConnectedNode { } override clone() { - const cloned = super.clone(); + const cloned = super.clone()!; cloned.properties["buttonText"] = "🎬 Action!"; cloned.properties["shortcutKey"] = ""; return cloned; @@ -149,14 +156,14 @@ class FastActionsButton extends BaseAnyInputConnectedNode { this.executingFromShortcut = false; } - override onPropertyChanged(property: string, value: any, _prevValue: any): boolean | void { - if (property == "buttonText") { + override onPropertyChanged(property: string, value: unknown, prevValue?: unknown) { + if (property == "buttonText" && typeof value === "string") { this.buttonWidget.name = value; } - if (property == "shortcutKey") { - value = value.trim(); - this.properties["shortcutKey"] = (value && value[0].toLowerCase()) || ""; + if (property == "shortcutKey" && typeof value === "string") { + this.properties["shortcutKey"] = value.trim()[0]?.toLowerCase() ?? ""; } + return true; } override handleLinkedNodesStabilization(linkedNodes: LGraphNode[]) { @@ -212,10 +219,10 @@ class FastActionsButton extends BaseAnyInputConnectedNode { widget = this.addWidget("combo", node.title, "None", "", { values: ["None", "Mute", "Bypass", "Enable", ...exposedActions], }); - (widget as ComfyWidget).serializeValue = async (_node: LGraphNode, _index: number) => { + widget.serializeValue = async (_node: LGraphNode, _index: number) => { return widget?.value; }; - this.widgetToData.set(widget, { node }); + this.widgetToData.set(widget, {node}); changed = true; } } @@ -250,7 +257,7 @@ class FastActionsButton extends BaseAnyInputConnectedNode { continue; } const action = widget.value; - const { comfy, node } = this.widgetToData.get(widget) ?? {}; + const {comfy, node} = this.widgetToData.get(widget) ?? {}; if (comfy) { if (action === "Queue Prompt") { await comfy.queuePrompt(0); @@ -267,6 +274,9 @@ class FastActionsButton extends BaseAnyInputConnectedNode { } // If there's a handleAction, always call it. if ((node as RgthreeBaseNode).handleAction) { + if (typeof action !== 'string') { + throw new Error('Fast Actions Button action should be a string: ' + action); + } await (node as RgthreeBaseNode).handleAction(action); } app.graph.change(); @@ -285,24 +295,24 @@ class FastActionsButton extends BaseAnyInputConnectedNode { "Comfy Action", "None", () => { - if (widget.value.startsWith("MOVE ")) { + if (String(widget.value).startsWith("MOVE ")) { this.widgets.push(this.widgets.splice(this.widgets.indexOf(widget), 1)[0]!); - widget.value = (widget as any)["lastValue_"]; - } else if (widget.value.startsWith("REMOVE ")) { + widget.value = String(widget.rgthree_lastValue); + } else if (String(widget.value).startsWith("REMOVE ")) { this.removeWidget(widget); } - (widget as any)["lastValue_"] = widget.value; + widget.rgthree_lastValue = widget.value; }, { values: ["None", "Queue Prompt", "REMOVE Comfy Action", "MOVE to end"], }, - ); - (widget as any)["lastValue_"] = value; + ) as IComboWidget; + widget.rgthree_lastValue = value; - (widget as ComfyWidget).serializeValue = async (_node: LGraphNode, _index: number) => { + widget.serializeValue = async (_node: LGraphNode, _index: number) => { return `comfy_app:${widget?.value}`; }; - this.widgetToData.set(widget, { comfy: app }); + this.widgetToData.set(widget, {comfy: app}); if (slot != null) { this.widgets.splice(slot, 0, this.widgets.splice(this.widgets.indexOf(widget), 1)[0]!); @@ -310,11 +320,11 @@ class FastActionsButton extends BaseAnyInputConnectedNode { return widget; } - override onSerialize(o: SerializedLGraphNode) { - super.onSerialize && super.onSerialize(o); - for (let [index, value] of (o.widgets_values || []).entries()) { + override onSerialize(serialised: ISerialisedNode) { + super.onSerialize?.(serialised); + for (let [index, value] of (serialised.widgets_values || []).entries()) { if (this.widgets[index]?.name === "Comfy Action") { - o.widgets_values![index] = `comfy_action:${value}`; + serialised.widgets_values![index] = `comfy_action:${value}`; } } } diff --git a/src_web/comfyui/fast_groups_bypasser.ts b/src_web/comfyui/fast_groups_bypasser.ts index 45d87eb..ca82cb3 100644 --- a/src_web/comfyui/fast_groups_bypasser.ts +++ b/src_web/comfyui/fast_groups_bypasser.ts @@ -1,4 +1,5 @@ -import { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js"; +import type { Size } from "@litegraph/litegraph.js"; + import { app } from "scripts/app.js"; import { NodeTypesString } from "./constants.js"; import { BaseFastGroupsModeChanger } from "./fast_groups_muter.js"; @@ -31,7 +32,7 @@ app.registerExtension({ }, loadedGraphNode(node: FastGroupsBypasser) { if (node.type == FastGroupsBypasser.title) { - node.tempSize = [...node.size]; + node.tempSize = [...node.size] as Size; } }, }); diff --git a/src_web/comfyui/fast_groups_muter.ts b/src_web/comfyui/fast_groups_muter.ts index 0201ad0..ebc1662 100644 --- a/src_web/comfyui/fast_groups_muter.ts +++ b/src_web/comfyui/fast_groups_muter.ts @@ -1,17 +1,21 @@ + +import type { + LGraphNode, + LGraph as TLGraph, + LGraphCanvas as TLGraphCanvas, + Vector2, + Size, +} from "@litegraph/litegraph.js"; +import type { ISerialisedNode } from "@litegraph/types/serialisation.js"; +import type { IBooleanWidget } from "@litegraph/types/widgets"; +import type { Point } from "@litegraph/interfaces.js"; + import { app } from "scripts/app.js"; import { RgthreeBaseVirtualNode } from "./base_node.js"; import { NodeTypesString } from "./constants.js"; -import { - type LGraphNode, - type LGraph as TLGraph, - LGraphCanvas as TLGraphCanvas, - Vector2, - SerializedLGraphNode, - IWidget, -} from "typings/litegraph.js"; import { SERVICE as FAST_GROUPS_SERVICE } from "./services/fast_groups_service.js"; import { drawNodeWidget, fitString } from "./utils_canvas.js"; -import { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js"; +import { CanvasMouseEvent } from "@litegraph/types/events.js"; const PROPERTY_SORT = "sort"; const PROPERTY_SORT_CUSTOM_ALPHA = "customSortAlphabet"; @@ -49,6 +53,15 @@ export abstract class BaseFastGroupsModeChanger extends RgthreeBaseVirtualNode { }; static "@customSortAlphabet" = { type: "string" }; + override properties!: RgthreeBaseVirtualNode["properties"] & { + [PROPERTY_MATCH_COLORS]: string; + [PROPERTY_MATCH_TITLE]: string; + [PROPERTY_SHOW_NAV]: boolean; + [PROPERTY_SORT]: string; + [PROPERTY_SORT_CUSTOM_ALPHA]: string; + [PROPERTY_RESTRICTION]: string; + }; + static "@toggleRestriction" = { type: "combo", values: ["default", "max one", "always one"], @@ -69,7 +82,7 @@ export abstract class BaseFastGroupsModeChanger extends RgthreeBaseVirtualNode { return super.onConstructed(); } - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode): void { // Patch a small issue (~14h) where multiple OPT_CONNECTIONS may have been created. // https://github.com/rgthree/rgthree-comfy/issues/206 // TODO: This can probably be removed within a few weeks. @@ -187,9 +200,11 @@ export abstract class BaseFastGroupsModeChanger extends RgthreeBaseVirtualNode { if (!widget) { // When we add a widget, litegraph is going to mess up the size, so we // store it so we can retrieve it in computeSize. Hacky.. - this.tempSize = [...this.size]; - widget = this.addCustomWidget>({ + this.tempSize = [...this.size] as Size; + widget = this.addCustomWidget({ name: "RGTHREE_TOGGLE_AND_NAV", + type: 'toggle', + y: 0, label: "", value: false, disabled: false, @@ -271,10 +286,10 @@ export abstract class BaseFastGroupsModeChanger extends RgthreeBaseVirtualNode { } } }, - serializeValue(serializedNode: SerializedLGraphNode, widgetIndex: number) { + serializeValue(serializedNode: ISerialisedNode, widgetIndex: number) { return this.value; }, - mouse(event: PointerEvent, pos: Vector2, node: LGraphNode) { + mouse(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode) { if (event.type == "pointerdown") { if ( node.properties?.[PROPERTY_SHOW_NAV] !== false && @@ -496,7 +511,7 @@ app.registerExtension({ }, loadedGraphNode(node: LGraphNode) { if (node.type == FastGroupsMuter.title) { - (node as FastGroupsMuter).tempSize = [...node.size]; + (node as FastGroupsMuter).tempSize = [...node.size] as Point; } }, }); diff --git a/src_web/comfyui/feature_group_fast_toggle.ts b/src_web/comfyui/feature_group_fast_toggle.ts index da51a3c..560a736 100644 --- a/src_web/comfyui/feature_group_fast_toggle.ts +++ b/src_web/comfyui/feature_group_fast_toggle.ts @@ -2,10 +2,10 @@ import type { LGraphCanvas as TLGraphCanvas, LGraphGroup as TLGraphGroup, LGraph as TLGraph, - AdjustedMouseEvent, Vector2, -} from "typings/litegraph.js"; +} from "@litegraph/litegraph.js"; import type {AdjustedMouseCustomEvent} from "typings/rgthree.js"; +import type { CanvasMouseEvent } from "@litegraph/types/events.js"; import {app} from "scripts/app.js"; import {rgthree} from "./rgthree.js"; @@ -29,7 +29,7 @@ function getToggles() { /** * Determines if the user clicked on an fast header icon. */ -function clickedOnToggleButton(e: AdjustedMouseEvent, group: TLGraphGroup): string | null { +function clickedOnToggleButton(e: CanvasMouseEvent, group: TLGraphGroup): string | null { const toggles = getToggles(); const pos = group.pos; const size = group.size; @@ -139,7 +139,7 @@ app.registerExtension({ if ( !CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled") || - !rgthree.lastAdjustedMouseEvent + !rgthree.lastCanvasMouseEvent ) { return; } @@ -150,8 +150,8 @@ app.registerExtension({ // Default to hover if not always. if (CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.show") !== "always") { const hoverGroup = graph.getGroupOnPos( - rgthree.lastAdjustedMouseEvent.canvasX, - rgthree.lastAdjustedMouseEvent.canvasY, + rgthree.lastCanvasMouseEvent.canvasX, + rgthree.lastCanvasMouseEvent.canvasY, ); groups = hoverGroup ? [hoverGroup] : []; } else { diff --git a/src_web/comfyui/image_comparer.ts b/src_web/comfyui/image_comparer.ts index 09d0281..8f14d4e 100644 --- a/src_web/comfyui/image_comparer.ts +++ b/src_web/comfyui/image_comparer.ts @@ -1,15 +1,16 @@ +import { + LGraphCanvas, + LGraphNode, + Vector2, +} from "@litegraph/litegraph.js"; +import type { CanvasMouseEvent } from "@litegraph/types/events.js"; +import type { ISerialisedNode } from "@litegraph/types/serialisation.js"; +import type { ComfyNodeConstructor, ComfyObjectInfo } from "typings/comfy.js"; + import { app } from "scripts/app.js"; import { api } from "scripts/api.js"; import { RgthreeBaseServerNode } from "./base_node.js"; import { NodeTypesString } from "./constants.js"; -import { ComfyNodeConstructor, ComfyObjectInfo } from "typings/comfy.js"; -import { - AdjustedMouseEvent, - LGraphCanvas, - LGraphNode, - SerializedLGraphNode, - Vector2, -} from "typings/litegraph.js"; import { addConnectionLayoutSupport } from "./utils.js"; import { RgthreeBaseHitAreas, @@ -17,6 +18,7 @@ import { RgthreeBaseWidgetBounds, } from "./utils_widgets.js"; import { measureText } from "./utils_canvas.js"; +import { Point, Size } from "@litegraph/interfaces.js"; type ComfyImageServerData = { filename: string; type: string; subfolder: string }; type ComfyImageData = { name: string; selected: boolean; url: string; img?: HTMLImageElement }; @@ -103,12 +105,12 @@ export class RgthreeImageComparer extends RgthreeBaseServerNode { } } - override onSerialize(o: SerializedLGraphNode) { - super.onSerialize && super.onSerialize(o); - for (let [index, widget_value] of (o.widgets_values || []).entries()) { + override onSerialize(serialised: ISerialisedNode) { + super.onSerialize && super.onSerialize(serialised); + for (let [index, widget_value] of (serialised.widgets_values || []).entries()) { if (this.widgets[index]?.name === "rgthree_comparer") { - o.widgets_values![index] = ( - this.widgets[index] as RgthreeImageComparerWidget + serialised.widgets_values![index] = ( + this.widgets[index] as unknown as RgthreeImageComparerWidget ).value.images.map((d) => { d = { ...d }; delete d.img; @@ -145,26 +147,27 @@ export class RgthreeImageComparer extends RgthreeBaseServerNode { } } - override onMouseDown(event: MouseEvent, pos: Vector2, graphCanvas: LGraphCanvas): void { - super.onMouseDown?.(event, pos, graphCanvas); + override onMouseDown(event: CanvasMouseEvent, pos: Point, canvas: LGraphCanvas): boolean { + super.onMouseDown?.(event, pos, canvas); this.setIsPointerDown(true); + return false; } - override onMouseEnter(event: MouseEvent, pos: Vector2, graphCanvas: LGraphCanvas): void { - super.onMouseEnter?.(event, pos, graphCanvas); + override onMouseEnter(event: CanvasMouseEvent): void { + super.onMouseEnter?.(event); this.setIsPointerDown(!!app.canvas.pointer_is_down); this.isPointerOver = true; } - override onMouseLeave(event: MouseEvent, pos: Vector2, graphCanvas: LGraphCanvas): void { - super.onMouseLeave?.(event, pos, graphCanvas); + override onMouseLeave(event: CanvasMouseEvent): void { + super.onMouseLeave?.(event); this.setIsPointerDown(false); this.isPointerOver = false; } - override onMouseMove(event: MouseEvent, pos: Vector2, graphCanvas: LGraphCanvas): void { - super.onMouseMove?.(event, pos, graphCanvas); - this.pointerOverPos = [...pos]; + override onMouseMove(event: MouseEvent, pos: Point, canvas: LGraphCanvas): void { + super.onMouseMove?.(event, pos, canvas); + this.pointerOverPos = [...pos] as Point; this.imageIndex = this.pointerOverPos[0] > this.size[0] / 2 ? 1 : 0; } @@ -364,7 +367,7 @@ class RgthreeImageComparerWidget extends RgthreeBaseWidget { const v = []; for (const data of this._value.images) { // Remove the img since it can't serialize. diff --git a/src_web/comfyui/image_inset_crop.ts b/src_web/comfyui/image_inset_crop.ts index fac5ca0..a5a5f78 100644 --- a/src_web/comfyui/image_inset_crop.ts +++ b/src_web/comfyui/image_inset_crop.ts @@ -1,8 +1,10 @@ -import { app } from "scripts/app.js"; -import type { ComfyApp, ComfyNodeConstructor, ComfyObjectInfo } from "typings/comfy.js"; -import { RgthreeBaseServerNode } from "./base_node.js"; -import { LGraph, LGraphNode, SerializedLGraphNode } from "typings/litegraph.js"; -import { NodeTypesString } from "./constants.js"; +import type {LGraph} from "@litegraph/litegraph.js"; +import type {ComfyApp, ComfyNodeConstructor, ComfyObjectInfo} from "typings/comfy.js"; +import type {ISerialisedNode} from "@litegraph/types/serialisation.js"; + +import {app} from "scripts/app.js"; +import {RgthreeBaseServerNode} from "./base_node.js"; +import {NodeTypesString} from "./constants.js"; class ImageInsetCrop extends RgthreeBaseServerNode { static override title = NodeTypesString.IMAGE_INSET_CROP; @@ -26,7 +28,7 @@ class ImageInsetCrop extends RgthreeBaseServerNode { this.setWidgetStep(); } - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode): void { super.configure(info); this.setWidgetStep(); } diff --git a/src_web/comfyui/label.ts b/src_web/comfyui/label.ts index e87b729..605d3bc 100644 --- a/src_web/comfyui/label.ts +++ b/src_web/comfyui/label.ts @@ -1,13 +1,13 @@ -import { app } from "scripts/app.js"; -import { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js"; -import { RgthreeBaseVirtualNode } from "./base_node.js"; -import { NodeTypesString } from "./constants.js"; import type { LGraphCanvas as TLGraphCanvas, LGraphNode, - AdjustedMouseEvent, Vector2, -} from "typings/litegraph.js"; +} from "@litegraph/litegraph.js"; +import type { CanvasMouseEvent } from "@litegraph/types/events.js"; + +import { app } from "scripts/app.js"; +import { RgthreeBaseVirtualNode } from "./base_node.js"; +import { NodeTypesString } from "./constants.js"; import { rgthree } from "./rgthree.js"; /** @@ -32,6 +32,16 @@ export class Label extends RgthreeBaseVirtualNode { static "@padding" = { type: "number" }; static "@borderRadius" = { type: "number" }; + override properties!: RgthreeBaseVirtualNode["properties"] & { + fontSize: number; + fontFamily: string; + fontColor: string; + textAlign: string; + backgroundColor: string; + padding: number; + borderRadius: number; + }; + override resizable = false; constructor(title = Label.title) { @@ -92,7 +102,7 @@ export class Label extends RgthreeBaseVirtualNode { ctx.restore(); } - override onDblClick(event: AdjustedMouseEvent, pos: Vector2, canvas: TLGraphCanvas) { + override onDblClick(event: CanvasMouseEvent, pos: Vector2, canvas: TLGraphCanvas) { // Since everything we can do here is in the properties, let's pop open the properties panel. LGraphCanvas.active_canvas.showShowNodePanel(this); } @@ -153,7 +163,7 @@ export class Label extends RgthreeBaseVirtualNode { */ const oldDrawNode = LGraphCanvas.prototype.drawNode; LGraphCanvas.prototype.drawNode = function (node: LGraphNode, ctx: CanvasRenderingContext2D) { - if (node.constructor === Label) { + if (node.constructor === Label.prototype.constructor) { // These get set very aggressively; maybe an extension is doing it. We'll just clear them out // each time. (node as Label).bgcolor = "transparent"; @@ -175,18 +185,13 @@ LGraphCanvas.prototype.drawNode = function (node: LGraphNode, ctx: CanvasRenderi * click). */ const oldGetNodeOnPos = LGraph.prototype.getNodeOnPos; -LGraph.prototype.getNodeOnPos = function ( - x: number, - y: number, - nodes_list?: LGraphNode[], - margin?: number, -) { +LGraph.prototype.getNodeOnPos = function(x: number, y: number, nodes_list?: LGraphNode[]) { if ( // processMouseDown always passes in the nodes_list nodes_list && rgthree.processingMouseDown && - rgthree.lastAdjustedMouseEvent?.type.includes("down") && - rgthree.lastAdjustedMouseEvent?.which === 1 + rgthree.lastCanvasMouseEvent?.type.includes("down") && + rgthree.lastCanvasMouseEvent?.which === 1 ) { // Using the same logic from LGraphCanvas processMouseDown, let's see if we consider this a // double click. @@ -195,7 +200,7 @@ LGraph.prototype.getNodeOnPos = function ( nodes_list = [...nodes_list].filter((n) => !(n instanceof Label) || !n.flags?.pinned); } } - return oldGetNodeOnPos.apply(this, [x, y, nodes_list, margin]) as T | null; + return oldGetNodeOnPos.apply(this, [x, y, nodes_list]); }; // Register the extension. diff --git a/src_web/comfyui/menu_auto_nest.ts b/src_web/comfyui/menu_auto_nest.ts index 1d650a7..0c65c3f 100644 --- a/src_web/comfyui/menu_auto_nest.ts +++ b/src_web/comfyui/menu_auto_nest.ts @@ -1,10 +1,11 @@ -import { app } from "scripts/app.js"; import type { - ContextMenuItem, LGraphNode, ContextMenu, IContextMenuOptions, -} from "typings/litegraph.js"; + IContextMenuValue, +} from "@litegraph/litegraph.js"; + +import { app } from "scripts/app.js"; import { rgthree } from "./rgthree.js"; import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js"; @@ -22,7 +23,7 @@ app.registerExtension({ const existingContextMenu = LiteGraph.ContextMenu; // @ts-ignore: TypeScript doesn't like this override. - LiteGraph.ContextMenu = function (values: ContextMenuItem[], options: IContextMenuOptions) { + LiteGraph.ContextMenu = function (values: IContextMenuValue[], options: IContextMenuOptions) { const threshold = CONFIG_SERVICE.getConfigValue("features.menu_auto_nest.threshold", 20); const enabled = CONFIG_SERVICE.getConfigValue("features.menu_auto_nest.subdirs", false); @@ -34,7 +35,7 @@ app.registerExtension({ } // If there's a rgthree_originalCallback, then we're nested and don't need to check things // we only expect on the first nesting. - if (!options.parentMenu?.options.rgthree_originalCallback) { + if (!(options.parentMenu?.options as any)?.rgthree_originalCallback) { // On first context menu, we require a callback and a flat list of options as strings. if (!options?.callback) { incompatible = `Skipping context menu auto nesting b/c a callback was expected.`; @@ -53,16 +54,16 @@ app.registerExtension({ return existingContextMenu.apply(this as any, [...arguments] as any); } - const folders: { [key: string]: ContextMenuItem[] } = {}; - const specialOps: ContextMenuItem[] = []; - const folderless: ContextMenuItem[] = []; + const folders: { [key: string]: IContextMenuValue[] } = {}; + const specialOps: IContextMenuValue[] = []; + const folderless: IContextMenuValue[] = []; for (const value of values) { if (!value) { folderless.push(value); continue; } const newValue = typeof value === "string" ? { content: value } : Object.assign({}, value); - newValue.rgthree_originalValue = value.rgthree_originalValue || value; + (newValue as any).rgthree_originalValue = (value as any).rgthree_originalValue || value; const valueContent = newValue.content || ''; const splitBy = valueContent.indexOf("/") > -1 ? "/" : "\\"; const valueSplit = valueContent.split(splitBy); @@ -81,24 +82,24 @@ app.registerExtension({ const foldersCount = Object.values(folders).length; if (foldersCount > 0) { // Propogate the original callback down through the options. - options.rgthree_originalCallback = - options.rgthree_originalCallback || - options.parentMenu?.options.rgthree_originalCallback || + (options as any).rgthree_originalCallback = + (options as any).rgthree_originalCallback || + (options.parentMenu?.options as any)?.rgthree_originalCallback || options.callback; - const oldCallback = options.rgthree_originalCallback; + const oldCallback = (options as any)?.rgthree_originalCallback; options.callback = undefined; const newCallback = ( - item: ContextMenuItem, + item: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, parentMenu: ContextMenu | undefined, node: LGraphNode, ) => { - oldCallback?.(item?.rgthree_originalValue!, options, event, undefined, node); + oldCallback?.((item as any)?.rgthree_originalValue!, options, event, undefined, node); }; const [n, v] = logger.infoParts(`Nested folders found (${foldersCount}).`); console[n]?.(...v); - const newValues: ContextMenuItem[] = []; + const newValues: IContextMenuValue[] = []; for (const [folderName, folderValues] of Object.entries(folders)) { newValues.push({ content: `📁 ${folderName}`, @@ -114,7 +115,7 @@ app.registerExtension({ }, }); } - values = ([] as ContextMenuItem[]).concat( + values = ([] as IContextMenuValue[]).concat( specialOps.map((f) => { if (typeof f === "string") { f = { content: f }; @@ -136,7 +137,7 @@ app.registerExtension({ options.scale = Math.max(app.canvas.ds?.scale || 1, 1); } - const oldCtrResponse = existingContextMenu.call(this as any, values, options); + const oldCtrResponse = existingContextMenu.call(this as any, values, options as any); // For some reason, LiteGraph calls submenus with "this.constructor" which no longer allows // us to continue building deep nesting, as well as skips many other extensions (even // ComfyUI's core extensions like translations) from working on submenus. It also removes diff --git a/src_web/comfyui/menu_copy_image.ts b/src_web/comfyui/menu_copy_image.ts index c1a6e4a..ec1fab8 100644 --- a/src_web/comfyui/menu_copy_image.ts +++ b/src_web/comfyui/menu_copy_image.ts @@ -1,13 +1,14 @@ -import { app } from "scripts/app.js"; -import type { LGraphCanvas, ContextMenuItem } from "typings/litegraph.js"; -import type { ComfyNodeConstructor, ComfyObjectInfo } from "typings/comfy.js"; +import type {IContextMenuValue, LGraphCanvas} from "@litegraph/litegraph.js"; +import type {ComfyNodeConstructor, ComfyObjectInfo} from "typings/comfy.js"; + +import {app} from "scripts/app.js"; const clipboardSupportedPromise = new Promise(async (resolve) => { try { // MDN says to check this, but it doesn't work in Mozilla... however, in secure contexts // (localhost included), it's given by default if the user has it flagged.. so we should be // able to check in the latter ClipboardItem too. - const result = await navigator.permissions.query({ name: "clipboard-write" } as any); + const result = await navigator.permissions.query({name: "clipboard-write"} as any); resolve(result.state === "granted"); return; } catch (e) { @@ -15,7 +16,7 @@ const clipboardSupportedPromise = new Promise(async (resolve) => { if (!navigator.clipboard.write) { throw new Error(); } - new ClipboardItem({ "image/png": new Blob([], { type: "image/png" }) }); + new ClipboardItem({"image/png": new Blob([], {type: "image/png"})}); resolve(true); return; } catch (e) { @@ -36,16 +37,16 @@ app.registerExtension({ const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; nodeType.prototype.getExtraMenuOptions = function ( canvas: LGraphCanvas, - options: ContextMenuItem[], - ) { - getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined; + options: (IContextMenuValue | null)[], + ): (IContextMenuValue | null)[] { + options = getExtraMenuOptions?.call(this, canvas, options) ?? options; // If we already have a copy image somehow, then let's skip ours. if (this.imgs?.length) { let img = this.imgs[this.imageIndex || 0] || this.imgs[this.overIndex || 0] || this.imgs[0]; const foundIdx = options.findIndex((option) => option?.content?.includes("Copy Image")); if (img && foundIdx === -1) { - const menuItem: ContextMenuItem = { + const menuItem: IContextMenuValue = { content: "Copy Image (rgthree)", callback: () => { const canvas = document.createElement("canvas"); @@ -54,7 +55,7 @@ app.registerExtension({ canvas.height = img.naturalHeight; ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight); canvas.toBlob((blob) => { - navigator.clipboard.write([new ClipboardItem({ "image/png": blob! })]); + navigator.clipboard.write([new ClipboardItem({"image/png": blob!})]); }); }, }; @@ -66,6 +67,7 @@ app.registerExtension({ } } } + return options; }; } } diff --git a/src_web/comfyui/menu_queue_node.ts b/src_web/comfyui/menu_queue_node.ts index c3423be..ca576aa 100644 --- a/src_web/comfyui/menu_queue_node.ts +++ b/src_web/comfyui/menu_queue_node.ts @@ -1,16 +1,14 @@ -import { app } from "scripts/app.js"; -import type { - LGraphCanvas as TLGraphCanvas, - ContextMenuItem, - LGraphNode, -} from "typings/litegraph.js"; -import type { ComfyNodeConstructor, ComfyObjectInfo } from "typings/comfy.js"; -import { rgthree } from "./rgthree.js"; -import { getOutputNodes } from "./utils.js"; -import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js"; +import type {IContextMenuValue, LGraphCanvas as TLGraphCanvas} from "@litegraph/litegraph.js"; +import type {ComfyNodeConstructor, ComfyObjectInfo} from "typings/comfy.js"; +import {app} from "scripts/app.js"; +import {rgthree} from "./rgthree.js"; +import {getOutputNodes} from "./utils.js"; +import {SERVICE as CONFIG_SERVICE} from "./services/config_service.js"; -function showQueueNodesMenuIfOutputNodesAreSelected(existingOptions: ContextMenuItem[]) { +function showQueueNodesMenuIfOutputNodesAreSelected( + existingOptions: (IContextMenuValue | null)[], +) { if (CONFIG_SERVICE.getConfigValue("features.menu_queue_selected_nodes") === false) { return; } @@ -30,15 +28,17 @@ function showQueueNodesMenuIfOutputNodesAreSelected(existingOptions: ContextMenu existingOptions.splice(idx, 0, menuItem); } -function showQueueGroupNodesMenuIfGroupIsSelected(existingOptions: ContextMenuItem[]) { +function showQueueGroupNodesMenuIfGroupIsSelected( + existingOptions: (IContextMenuValue | null)[], +) { if (CONFIG_SERVICE.getConfigValue("features.menu_queue_selected_nodes") === false) { return; } const group = - rgthree.lastAdjustedMouseEvent && + rgthree.lastCanvasMouseEvent && app.graph.getGroupOnPos( - rgthree.lastAdjustedMouseEvent.canvasX, - rgthree.lastAdjustedMouseEvent.canvasY, + rgthree.lastCanvasMouseEvent.canvasX, + rgthree.lastCanvasMouseEvent.canvasY, ); const outputNodes = group && getOutputNodes(group._nodes); @@ -68,11 +68,12 @@ app.registerExtension({ const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; nodeType.prototype.getExtraMenuOptions = function ( canvas: TLGraphCanvas, - options: ContextMenuItem[], - ) { - getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined; + options: (IContextMenuValue | null)[], + ): (IContextMenuValue | null)[] { + options = getExtraMenuOptions?.call(this, canvas, options) ?? options; showQueueNodesMenuIfOutputNodesAreSelected(options); showQueueGroupNodesMenuIfGroupIsSelected(options); + return options; }; }, diff --git a/src_web/comfyui/muter.ts b/src_web/comfyui/muter.ts index b093409..8ada542 100644 --- a/src_web/comfyui/muter.ts +++ b/src_web/comfyui/muter.ts @@ -1,7 +1,7 @@ import { app } from "scripts/app.js"; import { BaseNodeModeChanger } from "./base_node_mode_changer.js"; import { NodeTypesString } from "./constants.js"; -import type { LGraphNode } from "typings/litegraph.js"; +import type { LGraphNode } from "@litegraph/litegraph.js"; const MODE_MUTE = 2; const MODE_ALWAYS = 0; diff --git a/src_web/comfyui/node_collector.ts b/src_web/comfyui/node_collector.ts index 24b73df..03e52be 100644 --- a/src_web/comfyui/node_collector.ts +++ b/src_web/comfyui/node_collector.ts @@ -1,19 +1,22 @@ -import { app } from "scripts/app.js"; import type { LLink, LGraph, - ContextMenuItem, LGraphCanvas, - SerializedLGraphNode, LGraphNode as TLGraphNode, IContextMenuOptions, ContextMenu, -} from "typings/litegraph.js"; -import { addConnectionLayoutSupport } from "./utils.js"; -import { wait } from "rgthree/common/shared_utils.js"; -import { ComfyWidgets } from "scripts/widgets.js"; -import { BaseCollectorNode } from "./base_node_collector.js"; -import { NodeTypesString } from "./constants.js"; + IContextMenuValue, + Size, +} from "@litegraph/litegraph.js"; +import type {ISerialisedNode} from "@litegraph/types/serialisation.js"; +import type {Point} from "@litegraph/interfaces"; + +import {app} from "scripts/app.js"; +import {addConnectionLayoutSupport} from "./utils.js"; +import {wait} from "rgthree/common/shared_utils.js"; +import {ComfyWidgets} from "scripts/widgets.js"; +import {BaseCollectorNode} from "./base_node_collector.js"; +import {NodeTypesString} from "./constants.js"; /** * The Collector Node. Takes any number of inputs as connections for nodes and collects them into @@ -36,7 +39,7 @@ class CollectorNode extends BaseCollectorNode { return super.onConstructed(); } - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode) { // Patch a small issue (~14h) where multiple OPT_CONNECTIONS may have been created. // https://github.com/rgthree/rgthree-comfy/issues/206 // TODO: This can probably be removed within a few weeks. @@ -58,7 +61,7 @@ class CombinerNode extends CollectorNode { const note = ComfyWidgets["STRING"]( this, "last_seed", - ["STRING", { multiline: true }], + ["STRING", {multiline: true}], app, ).widget; note.inputEl!.value = @@ -69,11 +72,14 @@ class CombinerNode extends CollectorNode { note.inputEl!.style.fontStyle = "italic"; note.inputEl!.style.opacity = "0.8"; - this.getExtraMenuOptions = (_: LGraphCanvas, options: ContextMenuItem[]) => { + this.getExtraMenuOptions = ( + canvas: LGraphCanvas, + options: (IContextMenuValue | null)[], + ): (IContextMenuValue | null)[] => { options.splice(options.length - 1, 0, { content: "‼️ Update to Node Collector", callback: ( - _value: ContextMenuItem, + _value: IContextMenuValue, _options: IContextMenuOptions, _event: MouseEvent, _parentMenu: ContextMenu | undefined, @@ -82,10 +88,11 @@ class CombinerNode extends CollectorNode { updateCombinerToCollector(this); }, }); + return options; }; } - override configure(info: SerializedLGraphNode) { + override configure(info: ISerialisedNode) { super.configure(info); if (this.title != CombinerNode.title && !this.title.startsWith("‼️")) { this.title = "‼️ " + this.title; @@ -104,9 +111,9 @@ async function updateCombinerToCollector(node: TLGraphNode) { newNode.title = node.title.replace("‼️ ", ""); } // Port the position, size, and properties from the old node. - newNode.pos = [...node.pos]; - newNode.size = [...node.size]; - newNode.properties = { ...node.properties }; + newNode.pos = [...node.pos] as Point; + newNode.size = [...node.size] as Size; + newNode.properties = {...node.properties}; // We now collect the links data, inputs and outputs, of the old node since these will be // lost when we remove it. const links: any[] = []; @@ -115,7 +122,7 @@ async function updateCombinerToCollector(node: TLGraphNode) { const link: LLink = (app.graph as LGraph).links[linkId]!; if (!link) continue; const targetNode = app.graph.getNodeById(link.target_id); - links.push({ node: newNode, slot: index, targetNode, targetSlot: link.target_slot }); + links.push({node: newNode, slot: index, targetNode, targetSlot: link.target_slot}); } } for (const [index, input] of node.inputs.entries()) { diff --git a/src_web/comfyui/node_mode_relay.ts b/src_web/comfyui/node_mode_relay.ts index 1b9c74d..71b9f99 100644 --- a/src_web/comfyui/node_mode_relay.ts +++ b/src_web/comfyui/node_mode_relay.ts @@ -1,14 +1,14 @@ -import { app } from "scripts/app.js"; import type { INodeInputSlot, INodeOutputSlot, LGraphCanvas, + LGraphEventMode, LGraphNode, LLink, - SerializedLGraphNode, Vector2, -} from "typings/litegraph.js"; -import type { NodeMode } from "typings/comfy.js"; +} from "@litegraph/litegraph.js"; + +import { app } from "scripts/app.js"; import { PassThroughFollowing, addConnectionLayoutSupport, @@ -20,6 +20,7 @@ import { BaseCollectorNode } from "./base_node_collector.js"; import { NodeTypesString, stripRgthree } from "./constants.js"; import { fitString } from "./utils_canvas.js"; import { rgthree } from "./rgthree.js"; +import { ISerialisedNode } from "@litegraph/types/serialisation.js"; const MODE_ALWAYS = 0; const MODE_MUTE = 2; @@ -97,7 +98,7 @@ class NodeModeRelay extends BaseCollectorNode { return super.onConstructed(); } - override onModeChange(from: NodeMode, to: NodeMode) { + override onModeChange(from: LGraphEventMode | undefined, to: LGraphEventMode) { super.onModeChange(from, to); // If we aren't connected to anything, then we'll use our mode to relay when it changes. if (this.inputs.length <= 1 && !this.isInputConnected(0) && this.isAnyOutputConnected()) { @@ -108,7 +109,7 @@ class NodeModeRelay extends BaseCollectorNode { } } - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode): void { // Patch a small issue (~14h) where multiple OPT_CONNECTIONS may have been created. // https://github.com/rgthree/rgthree-comfy/issues/206 // TODO: This can probably be removed within a few weeks. @@ -194,7 +195,7 @@ class NodeModeRelay extends BaseCollectorNode { -1, this.inputsPassThroughFollowing, ); - let mode: NodeMode | -99 | null = undefined; + let mode: LGraphEventMode | -99 | undefined = undefined; for (const inputNode of inputNodes) { // If we haven't set our mode to be, then let's set it. Otherwise, mode will stick if it // remains constant, otherwise, if we hit an ALWAYS, then we'll unmute all repeaters and @@ -206,7 +207,7 @@ class NodeModeRelay extends BaseCollectorNode { } else if (inputNode.mode === MODE_ALWAYS || mode === MODE_ALWAYS) { mode = MODE_ALWAYS; } else { - mode = null; + mode = undefined; } } @@ -219,11 +220,11 @@ class NodeModeRelay extends BaseCollectorNode { /** * Sends the mode to the repeater, checking to see if we're modifying our mode. */ - private dispatchModeToRepeater(mode?: NodeMode | -99 | null) { + private dispatchModeToRepeater(mode?: LGraphEventMode | -99 | null) { if (mode != null) { const propertyVal = this.properties?.[MODE_TO_PROPERTY.get(mode) || ""]; - const newMode = OPTION_TO_MODE.get(propertyVal); - mode = (newMode !== null ? newMode : mode) as NodeMode | -99; + const newMode = OPTION_TO_MODE.get(propertyVal as string); + mode = (newMode !== null ? newMode : mode) as LGraphEventMode | -99; if (mode !== null && mode !== MODE_NOTHING) { if (this.outputs?.length) { const outputNodes = getConnectedOutputNodesAndFilterPassThroughs(this); diff --git a/src_web/comfyui/node_mode_repeater.ts b/src_web/comfyui/node_mode_repeater.ts index 8174439..0c61755 100644 --- a/src_web/comfyui/node_mode_repeater.ts +++ b/src_web/comfyui/node_mode_repeater.ts @@ -1,22 +1,22 @@ -import { app } from "scripts/app.js"; -import { BaseCollectorNode } from "./base_node_collector.js"; -import { NodeTypesString, stripRgthree } from "./constants.js"; - import type { INodeInputSlot, INodeOutputSlot, + LGraphEventMode, LGraphGroup, LGraphNode, LLink, - SerializedLGraphNode, -} from "typings/litegraph.js"; +} from "@litegraph/litegraph.js"; + +import { app } from "scripts/app.js"; +import { BaseCollectorNode } from "./base_node_collector.js"; +import { NodeTypesString, stripRgthree } from "./constants.js"; import { PassThroughFollowing, addConnectionLayoutSupport, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodesAndFilterPassThroughs, } from "./utils.js"; -import { NodeMode } from "typings/comfy.js"; +import { ISerialisedNode } from "@litegraph/types/serialisation.js"; class NodeModeRepeater extends BaseCollectorNode { override readonly inputsPassThroughFollowing: PassThroughFollowing = PassThroughFollowing.ALL; @@ -42,7 +42,7 @@ class NodeModeRepeater extends BaseCollectorNode { return super.onConstructed(); } - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode): void { // Patch a small issue (~14h) where multiple OPT_CONNECTIONS may have been created. // https://github.com/rgthree/rgthree-comfy/issues/206 // TODO: This can probably be removed within a few weeks. @@ -159,7 +159,7 @@ class NodeModeRepeater extends BaseCollectorNode { } /** When a mode change, we want all connected nodes to match except for connected relays. */ - override onModeChange(from: NodeMode, to: NodeMode) { + override onModeChange(from: LGraphEventMode | undefined, to: LGraphEventMode) { super.onModeChange(from, to); const linkedNodes = getConnectedInputNodesAndFilterPassThroughs(this).filter( (node) => node.type !== NodeTypesString.NODE_MODE_RELAY, diff --git a/src_web/comfyui/power_lora_loader.ts b/src_web/comfyui/power_lora_loader.ts index 61acbc8..7f16349 100644 --- a/src_web/comfyui/power_lora_loader.ts +++ b/src_web/comfyui/power_lora_loader.ts @@ -1,18 +1,21 @@ -import { app } from "scripts/app.js"; import type { - ContextMenuItem, LGraphNode as TLGraphNode, - IWidget, LGraphCanvas, - SerializedLGraphNode, Vector2, - AdjustedMouseEvent, -} from "typings/litegraph.js"; -import type { ComfyObjectInfo, ComfyNodeConstructor } from "typings/comfy.js"; -import { RgthreeBaseServerNode } from "./base_node.js"; -import { rgthree } from "./rgthree.js"; -import { addConnectionLayoutSupport } from "./utils.js"; -import { NodeTypesString } from "./constants.js"; + IContextMenuValue, + IFoundSlot, +} from "@litegraph/litegraph.js"; +import type {CanvasMouseEvent} from "@litegraph/types/events.js"; +import type {ISerialisedNode} from "@litegraph/types/serialisation.js"; +import type {ICustomWidget} from "@litegraph/types/widgets"; +import type {RgthreeModelInfo} from "typings/rgthree.js"; +import type {ComfyObjectInfo, ComfyNodeConstructor} from "typings/comfy.js"; + +import {app} from "scripts/app.js"; +import {RgthreeBaseServerNode} from "./base_node.js"; +import {rgthree} from "./rgthree.js"; +import {addConnectionLayoutSupport} from "./utils.js"; +import {NodeTypesString} from "./constants.js"; import { drawInfoIcon, drawNumberWidgetPart, @@ -27,12 +30,11 @@ import { RgthreeBetterButtonWidget, RgthreeDividerWidget, } from "./utils_widgets.js"; -import { rgthreeApi } from "rgthree/common/rgthree_api.js"; -import { showLoraChooser } from "./utils_menu.js"; -import { moveArrayItem, removeArrayItem } from "rgthree/common/shared_utils.js"; -import { RgthreeLoraInfoDialog } from "./dialog_info.js"; -import type { RgthreeModelInfo } from "typings/rgthree.js"; -import { LORA_INFO_SERVICE } from "rgthree/common/model_info_service.js"; +import {rgthreeApi} from "rgthree/common/rgthree_api.js"; +import {showLoraChooser} from "./utils_menu.js"; +import {moveArrayItem, removeArrayItem} from "rgthree/common/shared_utils.js"; +import {RgthreeLoraInfoDialog} from "./dialog_info.js"; +import {LORA_INFO_SERVICE} from "rgthree/common/model_info_service.js"; // import { RgthreePowerLoraChooserDialog } from "./dialog_power_lora_chooser.js"; const PROP_LABEL_SHOW_STRENGTHS = "Show Strengths"; @@ -62,7 +64,7 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { private loraWidgetsCounter = 0; /** Keep track of the spacer, new lora widgets will go before it when it exists. */ - private widgetButtonSpacer: IWidget | null = null; + private widgetButtonSpacer: ICustomWidget | null = null; constructor(title = NODE_CLASS.title) { super(title); @@ -78,7 +80,7 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { * added in `onNodeCreated`, letting `super.configure` and do nothing, then create our lora * widgets and, finally, add back in our default widgets. */ - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode): void { while (this.widgets?.length) this.removeWidget(0); this.widgetButtonSpacer = null; super.configure(info); @@ -86,9 +88,9 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { (this as any)._tempWidth = this.size[0]; (this as any)._tempHeight = this.size[1]; for (const widgetValue of info.widgets_values || []) { - if (widgetValue?.lora !== undefined) { + if ((widgetValue as PowerLoraLoaderWidgetValue)?.lora !== undefined) { const widget = this.addNewLoraWidget(); - widget.value = { ...widgetValue }; + widget.value = {...(widgetValue as PowerLoraLoaderWidgetValue)}; } } this.addNonLoraWidgets(); @@ -120,6 +122,7 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { if (this.widgetButtonSpacer) { moveArrayItem(this.widgets, widget, this.widgets.indexOf(this.widgetButtonSpacer)); } + return widget; } @@ -127,25 +130,23 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { private addNonLoraWidgets() { moveArrayItem( this.widgets, - this.addCustomWidget( - new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }), - ), + this.addCustomWidget(new RgthreeDividerWidget({marginTop: 4, marginBottom: 0, thickness: 0})), 0, ); moveArrayItem(this.widgets, this.addCustomWidget(new PowerLoraLoaderHeaderWidget()), 1); this.widgetButtonSpacer = this.addCustomWidget( - new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }), + new RgthreeDividerWidget({marginTop: 4, marginBottom: 0, thickness: 0}), ); this.addCustomWidget( new RgthreeBetterButtonWidget( "➕ Add Lora", - (event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) => { + (event: CanvasMouseEvent, pos: Vector2, node: TLGraphNode) => { rgthreeApi.getLoras().then((loras) => { showLoraChooser( - event as PointerEvent, - (value: ContextMenuItem | string) => { + event as MouseEvent, + (value: IContextMenuValue | string) => { if (typeof value === "string") { if (value.includes("Power Lora Chooser")) { // new RgthreePowerLoraChooserDialog().show(); @@ -206,7 +207,7 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { } // Only care about lora widget clicks. if (lastWidget?.name?.startsWith("lora_")) { - return { widget: lastWidget, output: { type: "LORA WIDGET" } }; + return {widget: lastWidget, output: {type: "LORA WIDGET"}}; } } return slot; @@ -216,7 +217,7 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { * Working with the overridden `getSlotInPosition` above, this method checks if the passed in * option is actually a widget from it and then hijacks the context menu all together. */ - override getSlotMenuOptions(slot: any): ContextMenuItem[] | null { + override getSlotMenuOptions(slot: IFoundSlot) { // Oddly, LiteGraph doesn't call back into our node with a custom menu (even though it let's us // define a custom menu to begin with... wtf?). So, we'll return null so the default is not // triggered and then we'll just show one ourselves because.. yea. @@ -225,7 +226,7 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { const index = this.widgets.indexOf(widget); const canMoveUp = !!this.widgets[index - 1]?.name?.startsWith("lora_"); const canMoveDown = !!this.widgets[index + 1]?.name?.startsWith("lora_"); - const menuItems: ContextMenuItem[] = [ + const menuItems: (IContextMenuValue|null)[] = [ { content: `ℹ️ Show Info`, callback: () => { @@ -260,15 +261,14 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { }, }, ]; - - let canvas = app.canvas as LGraphCanvas; new LiteGraph.ContextMenu( menuItems, - { title: "LORA WIDGET", event: rgthree.lastAdjustedMouseEvent! }, - canvas.getCanvasWindow(), + {title: "LORA WIDGET", event: rgthree.lastCanvasMouseEvent!} ); - return null; + // [🤮] ComfyUI doesn't have a possible return type as falsy, even though the impl skips the + // menu when the return is falsy. Casting as any. + return undefined as any; } return this.defaultGetSlotMenuOptions(slot); } @@ -296,7 +296,7 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { let allOff = true; for (const widget of this.widgets) { if (widget.name?.startsWith("lora_")) { - const on = widget.value?.on; + const on = (widget.value as any)?.on; allOn = allOn && on === true; allOff = allOff && on === false; if (!allOn && !allOff) { @@ -314,8 +314,8 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { const allOn = this.allLorasState(); const toggledTo = !allOn ? true : false; for (const widget of this.widgets) { - if (widget.name?.startsWith("lora_")) { - widget.value.on = toggledTo; + if (widget.name?.startsWith("lora_") && (widget.value as any)?.on != null) { + (widget.value as any).on = toggledTo; } } } @@ -371,13 +371,13 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { * The PowerLoraLoaderHeaderWidget that renders a toggle all switch, as well as some title info * (more necessary for the double model & clip strengths to label them). */ -class PowerLoraLoaderHeaderWidget extends RgthreeBaseWidget<{ type: string }> { +class PowerLoraLoaderHeaderWidget extends RgthreeBaseWidget<{type: string}> { private showModelAndClip: boolean | null = null; - value = { type: "PowerLoraLoaderHeaderWidget" }; + value = {type: "PowerLoraLoaderHeaderWidget"}; protected override hitAreas: RgthreeBaseHitAreas<"toggle"> = { - toggle: { bounds: [0, 0] as Vector2, onDown: this.onToggleDown }, + toggle: {bounds: [0, 0] as Vector2, onDown: this.onToggleDown}, }; constructor(name: string = "PowerLoraLoaderHeaderWidget") { @@ -408,7 +408,7 @@ class PowerLoraLoaderHeaderWidget extends RgthreeBaseWidget<{ type: string }> { const midY = posY + height * 0.5; let posX = 10; ctx.save(); - this.hitAreas.toggle.bounds = drawTogglePart(ctx, { posX, posY, height, value: allLoraState }); + this.hitAreas.toggle.bounds = drawTogglePart(ctx, {posX, posY, height, value: allLoraState}); if (!lowQuality) { posX += this.hitAreas.toggle.bounds[1] + innerMargin; @@ -437,7 +437,7 @@ class PowerLoraLoaderHeaderWidget extends RgthreeBaseWidget<{ type: string }> { /** * Handles a pointer down on the toggle's defined hit area. */ - onToggleDown(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) { + onToggleDown(event: CanvasMouseEvent, pos: Vector2, node: TLGraphNode) { (node as RgthreePowerLoraLoader).toggleAllLoras(); this.cancelMouseDown(); return true; @@ -482,19 +482,19 @@ class PowerLoraLoaderWidget extends RgthreeBaseWidget = { - toggle: { bounds: [0, 0] as Vector2, onDown: this.onToggleDown }, - lora: { bounds: [0, 0] as Vector2, onDown: this.onLoraDown }, + toggle: {bounds: [0, 0] as Vector2, onDown: this.onToggleDown}, + lora: {bounds: [0, 0] as Vector2, onDown: this.onLoraDown}, // info: { bounds: [0, 0] as Vector2, onDown: this.onInfoDown }, - strengthDec: { bounds: [0, 0] as Vector2, onDown: this.onStrengthDecDown }, - strengthVal: { bounds: [0, 0] as Vector2, onUp: this.onStrengthValUp }, - strengthInc: { bounds: [0, 0] as Vector2, onDown: this.onStrengthIncDown }, - strengthAny: { bounds: [0, 0] as Vector2, onMove: this.onStrengthAnyMove }, + strengthDec: {bounds: [0, 0] as Vector2, onDown: this.onStrengthDecDown}, + strengthVal: {bounds: [0, 0] as Vector2, onUp: this.onStrengthValUp}, + strengthInc: {bounds: [0, 0] as Vector2, onDown: this.onStrengthIncDown}, + strengthAny: {bounds: [0, 0] as Vector2, onMove: this.onStrengthAnyMove}, - strengthTwoDec: { bounds: [0, 0] as Vector2, onDown: this.onStrengthTwoDecDown }, - strengthTwoVal: { bounds: [0, 0] as Vector2, onUp: this.onStrengthTwoValUp }, - strengthTwoInc: { bounds: [0, 0] as Vector2, onDown: this.onStrengthTwoIncDown }, - strengthTwoAny: { bounds: [0, 0] as Vector2, onMove: this.onStrengthTwoAnyMove }, + strengthTwoDec: {bounds: [0, 0] as Vector2, onDown: this.onStrengthTwoDecDown}, + strengthTwoVal: {bounds: [0, 0] as Vector2, onUp: this.onStrengthTwoValUp}, + strengthTwoInc: {bounds: [0, 0] as Vector2, onDown: this.onStrengthTwoIncDown}, + strengthTwoAny: {bounds: [0, 0] as Vector2, onMove: this.onStrengthTwoAnyMove}, }; constructor(name: string) { @@ -512,7 +512,7 @@ class PowerLoraLoaderWidget extends RgthreeBaseWidget this.loraInfo?.strengthMax) { @@ -673,8 +673,8 @@ class PowerLoraLoaderWidget extends RgthreeBaseWidget { + const v = {...this.value}; // Never send the second value to the backend if we're not showing it, otherwise, let's just // make sure it's not null. if (!this.showModelAndClip) { @@ -686,18 +686,18 @@ class PowerLoraLoaderWidget extends RgthreeBaseWidget { + onLoraDown(event: CanvasMouseEvent, pos: Vector2, node: TLGraphNode) { + showLoraChooser(event, (value: IContextMenuValue) => { if (typeof value === "string") { this.value.lora = value; this.loraInfo = null; @@ -708,28 +708,28 @@ class PowerLoraLoaderWidget extends RgthreeBaseWidget (this.value[prop] = Number(v)), event); } - override onMouseUp(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode): boolean | void { + override onMouseUp(event: CanvasMouseEvent, pos: Vector2, node: TLGraphNode): boolean | void { super.onMouseUp(event, pos, node); this.haveMouseMovedStrength = false; } @@ -762,7 +762,7 @@ class PowerLoraLoaderWidget extends RgthreeBaseWidget) => { + infoDialog.addEventListener("close", ((e: CustomEvent<{dirty: boolean}>) => { if (e.detail.dirty) { this.getLoraInfo(true); } diff --git a/src_web/comfyui/power_prompt.ts b/src_web/comfyui/power_prompt.ts index 8a45622..9a5c57f 100644 --- a/src_web/comfyui/power_prompt.ts +++ b/src_web/comfyui/power_prompt.ts @@ -1,5 +1,5 @@ import { app } from "scripts/app.js"; -import type { LGraphNode as TLGraphNode } from "typings/litegraph.js"; +import type { LGraphNode as TLGraphNode } from "@litegraph/litegraph.js"; import type { ComfyApp, ComfyObjectInfo, diff --git a/src_web/comfyui/random_unmuter.ts b/src_web/comfyui/random_unmuter.ts index 8c4e401..46de38e 100644 --- a/src_web/comfyui/random_unmuter.ts +++ b/src_web/comfyui/random_unmuter.ts @@ -1,4 +1,4 @@ -import type { LGraphNode } from "typings/litegraph.js"; +import type { LGraphNode } from "@litegraph/litegraph.js"; import type { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js"; import { app } from "scripts/app.js"; diff --git a/src_web/comfyui/reroute.ts b/src_web/comfyui/reroute.ts index a6570c1..8cedc51 100644 --- a/src_web/comfyui/reroute.ts +++ b/src_web/comfyui/reroute.ts @@ -1,4 +1,17 @@ -import { app } from "scripts/app.js"; +import type { + Vector2, + LLink, + LGraphCanvas as TLGraphCanvas, + LGraph as TLGraph, + INodeInputSlot, + INodeOutputSlot, + LGraphNode as TLGraphNode, +} from "@litegraph/litegraph.js"; +import type {LinkDirection} from "@litegraph/types/globalEnums.js"; +import type {ISerialisedNode} from "@litegraph/types/serialisation.js"; +import type {Point, Size} from "@litegraph/interfaces.js"; + +import {app} from "scripts/app.js"; import { getWidgetConfig, mergeIfValid, @@ -6,18 +19,8 @@ import { // @ts-ignore } from "../../extensions/core/widgetInputs.js"; // @ts-ignore -import { rgthreeConfig } from "rgthree/config.js"; -import { rgthree } from "./rgthree.js"; -import type { - Vector2, - LLink, - LGraphCanvas as TLGraphCanvas, - LGraph as TLGraph, - SerializedLGraphNode, - INodeInputSlot, - INodeOutputSlot, - LGraphNode as TLGraphNode, -} from "typings/litegraph.js"; +import {rgthreeConfig} from "rgthree/config.js"; +import {rgthree} from "./rgthree.js"; import { IoDirection, LAYOUT_CLOCKWISE, @@ -30,10 +33,10 @@ import { setConnectionsLayout, waitForCanvas, } from "./utils.js"; -import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js"; -import { wait } from "rgthree/common/shared_utils.js"; -import { RgthreeBaseVirtualNode } from "./base_node.js"; -import { NodeTypesString } from "./constants.js"; +import {SERVICE as KEY_EVENT_SERVICE} from "./services/key_events_services.js"; +import {wait} from "rgthree/common/shared_utils.js"; +import {RgthreeBaseVirtualNode} from "./base_node.js"; +import {NodeTypesString} from "./constants.js"; const CONFIG_REROUTE = rgthreeConfig?.["nodes"]?.["reroute"] || {}; @@ -79,6 +82,7 @@ type FastRerouteEntryCtx = { output?: INodeOutputSlot; slot: number; pos: Vector2; + direction?: LinkDirection; }; type FastRerouteEntry = { @@ -94,7 +98,7 @@ type FastRerouteEntry = { class RerouteService { private isFastLinking = false; private handledNewRerouteKeypress = false; - private connectingData: FastRerouteEntryCtx|null = null; + private connectingData: FastRerouteEntryCtx | null = null; private fastReroutesHistory: FastRerouteEntry[] = []; private handleLinkingKeydownBound = this.handleLinkingKeydown.bind(this); @@ -116,7 +120,7 @@ class RerouteService { // With the new UI released in August 2024, ComfyUI changed LiteGraph's code, removing // connecting_node, connecting_node, connecting_node, and connecting_node properties and instead // using an array of connecting_links. We can try to accomodate both for a while. - const canvasProperty = true ? 'connecting_links' : 'connecting_node'; + const canvasProperty = true ? "connecting_links" : "connecting_node"; (canvas as any)[`_${canvasProperty}`]; const thisService = this; Object.defineProperty(canvas, canvasProperty, { @@ -162,7 +166,10 @@ class RerouteService { private stoppingLinking() { this.isFastLinking = false; this.fastReroutesHistory = []; - KEY_EVENT_SERVICE.removeEventListener("keydown", this.handleLinkingKeydownBound as EventListener); + KEY_EVENT_SERVICE.removeEventListener( + "keydown", + this.handleLinkingKeydownBound as EventListener, + ); KEY_EVENT_SERVICE.removeEventListener("keyup", this.handleLinkingKeyupBound as EventListener); } @@ -197,9 +204,13 @@ class RerouteService { } } - private getConnectingData() : FastRerouteEntryCtx { + private getConnectingData(): FastRerouteEntryCtx { const oldCanvas = app.canvas as any; - if (oldCanvas.connecting_node && oldCanvas.connecting_slot != null && oldCanvas.connecting_pos?.length) { + if ( + oldCanvas.connecting_node && + oldCanvas.connecting_slot != null && + oldCanvas.connecting_pos?.length + ) { return { node: oldCanvas.connecting_node, input: oldCanvas.connecting_input, @@ -214,10 +225,10 @@ class RerouteService { const link = canvas.connecting_links[0]!; return { node: link.node, - input: link.input, - output: link.output, + input: link.input ?? undefined, + output: link.output ?? undefined, slot: link.slot, - pos: [...link.pos], + pos: [...link.pos] as Point, }; } throw new Error("Error, handling linking keydown, but there's no link."); @@ -225,7 +236,11 @@ class RerouteService { private setCanvasConnectingData(ctx: FastRerouteEntryCtx) { const oldCanvas = app.canvas as any; - if (oldCanvas.connecting_node && oldCanvas.connecting_slot != null && oldCanvas.connecting_pos?.length) { + if ( + oldCanvas.connecting_node && + oldCanvas.connecting_slot != null && + oldCanvas.connecting_pos?.length + ) { oldCanvas.connecting_node = ctx.node; oldCanvas.connecting_input = ctx.input; oldCanvas.connecting_output = ctx.output; @@ -242,6 +257,19 @@ class RerouteService { link.slot = ctx.slot; link.pos = ctx.pos; } + // const newCanvas = app.canvas as unknown as TypedLGraphCanvas; + // if (newCanvas.linkConnector.renderLinks?.length) { + // newCanvas.linkConnector.reset(); + // newCanvas.linkConnector.dragNewFromOutput(app.graph as any, ctx.node as any, ctx.output as any); + // } + + // const link = newCanvas.linkConnector.renderLinks[0]! as any; + // link.node = ctx.node; + // link.fromSlot = ctx.output || ctx.input; + // link.fromSlotIndex = ctx.slot; + // link.fromPos = ctx.pos; + // link.fromDirection = ctx.direction || 4; + // } } /** @@ -274,9 +302,9 @@ class RerouteService { let newPos = canvas.convertEventToCanvasOffset({ clientX: Math.round(canvas.last_mouse_position[0] / 10) * 10, clientY: Math.round(canvas.last_mouse_position[1] / 10) * 10, - }); + } as MouseEvent); entry.node.pos = newPos; - canvas.graph.add(entry.node); + canvas.graph!.add(entry.node); canvas.selectNode(entry.node); // Find out which direction we're generally moving. @@ -315,12 +343,16 @@ class RerouteService { data.output = entry.node.outputs[0]!; data.slot = 0; data.pos = entry.node.getConnectionPos(false, 0); + data.direction = + layout[0] === "Top" ? 2 : layout[0] === "Bottom" ? 1 : layout[0] === "Left" ? 4 : 3; } else { entry.node.connect(0, data.node, data.slot); data.node = entry.node; data.input = entry.node.inputs[0]!; data.slot = 0; data.pos = entry.node.getConnectionPos(true, 0); + data.direction = + layout[1] === "Top" ? 2 : layout[1] === "Bottom" ? 1 : layout[1] === "Left" ? 4 : 3; } this.setCanvasConnectingData(data); entry.current = {...this.connectingData}; @@ -385,9 +417,9 @@ class RerouteNode extends RgthreeBaseVirtualNode { /** Shortcuts defined in the config. */ private shortcuts = { - rotate: { keys: CONFIG_KEY_ROTATE, state: false }, - connection_input: { keys: CONFIG_KEY_CXN_INPUT, state: false }, - connection_output: { keys: CONFIG_KEY_CXN_OUTPUT, state: false }, + rotate: {keys: CONFIG_KEY_ROTATE, state: false}, + connection_input: {keys: CONFIG_KEY_CXN_INPUT, state: false}, + connection_output: {keys: CONFIG_KEY_CXN_OUTPUT, state: false}, resize: { keys: CONFIG_KEY_RESIZE, state: false, @@ -410,7 +442,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { } override onConstructed(): boolean { - this.setResizable(this.properties["resizable"] ?? configResizable); + this.setResizable(!!(this.properties["resizable"] ?? configResizable)); this.size = RerouteNode.size; // Starting size. this.addInput("", "*"); this.addOutput("", "*"); @@ -418,7 +450,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { return super.onConstructed(); } - override configure(info: SerializedLGraphNode) { + override configure(info: ISerialisedNode): void { // Patch a small issue (~14h) where multiple OPT_CONNECTIONS may have been created. // https://github.com/rgthree/rgthree-comfy/issues/206 // TODO: This can probably be removed within a few weeks. @@ -430,7 +462,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { } super.configure(info); this.configuring = true; - this.setResizable(this.properties["resizable"] ?? configResizable); + this.setResizable(!!(this.properties["resizable"] ?? configResizable)); this.applyNodeSize(); this.configuring = false; } @@ -441,7 +473,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { } override clone() { - const cloned = super.clone(); + const cloned = super.clone()!; cloned.inputs[0]!.type = "*"; cloned.outputs[0]!.type = "*"; return cloned; @@ -508,13 +540,17 @@ class RerouteNode extends RgthreeBaseVirtualNode { } /** Finds the input slot; since we only ever have one, this is always 0. */ - override findInputSlot(name: string): number { - return 0; + override findInputSlot(name: string, returnObj?: TReturn): number; + override findInputSlot(name: string, returnObj?: TReturn): INodeInputSlot; + override findInputSlot(name: string, returnObj: boolean = false): number | INodeInputSlot { + return returnObj ? this.inputs[0]! : 0; } /** Finds the output slot; since we only ever have one, this is always 0. */ - override findOutputSlot(name: string): number { - return 0; + override findOutputSlot(name: string, returnObj?: TReturn): number; + override findOutputSlot(name: string, returnObj?: TReturn): INodeOutputSlot; + override findOutputSlot(name: unknown, returnObj?: unknown): number | INodeOutputSlot { + return returnObj ? this.outputs[0]! : 0; } override disconnectOutput(slot: string | number, targetNode?: TLGraphNode | undefined): boolean { @@ -530,7 +566,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { if (rgthree.replacingReroute != null && this.inputs[0]?.link) { const graph = app.graph as TLGraph; const link = graph.links[this.inputs[0].link]; - const node = graph.getNodeById(link?.origin_id); + const node = link?.origin_id != null ? graph.getNodeById(link.origin_id) : null; // We'll also be asked to disconnect when the old one is removed, so we only want to stop a // disconnect when the connected node is NOT the one being removed/replaced. if (rgthree.replacingReroute !== node?.id) { @@ -707,15 +743,16 @@ class RerouteNode extends RgthreeBaseVirtualNode { for (const l of node.outputs[0]!.links || []) { const link = app.graph.links[l]; - if (link) { + if (link && color) { link.color = color; } } try { - // For primitive nodes, which look at the widget to dsplay themselves. + // For primitive nodes, which look at the widget to dsplay themselves; we get by with just + // an object with 'name'. if (outputWidgetConfig && outputWidget && outputType) { - node.inputs[0]!.widget = { name: "value" }; + node.inputs[0]!.widget = {name: "value"} as any; setWidgetConfig( node.inputs[0], [outputType ?? displayType, outputWidgetConfig], @@ -739,7 +776,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { const links = inputNode.outputs[inputNodeOutputSlot]!.links; for (const l of links || []) { const link = app.graph.links[l]; - if (link) { + if (link && color) { link.color = color; } } @@ -753,8 +790,8 @@ class RerouteNode extends RgthreeBaseVirtualNode { * When called, sets the node size, and the properties size, and calls out to `stabilizeLayout`. */ override setSize(size: Vector2): void { - const oldSize: Vector2 = [...this.size]; - const newSize: Vector2 = [...size]; + const oldSize = [...this.size] as Size; + const newSize = [...size] as Size; super.setSize(newSize); this.properties["size"] = [...this.size]; this.stabilizeLayout(oldSize, newSize); @@ -768,9 +805,10 @@ class RerouteNode extends RgthreeBaseVirtualNode { if (newSize[0] === 10 || newSize[1] === 10) { const props = this.properties; props["connections_layout"] = props["connections_layout"] || ["Left", "Right"]; - const layout = props["connections_layout"]; props["connections_dir"] = props["connections_dir"] || [-1, -1]; - const dir = props["connections_dir"]; + + const layout = props["connections_layout"] as [string, string]; + const dir = props["connections_dir"] as [number, number]; if (oldSize[0] > 10 && newSize[0] === 10) { dir[0] = LiteGraph.DOWN; @@ -808,10 +846,10 @@ class RerouteNode extends RgthreeBaseVirtualNode { applyNodeSize() { this.properties["size"] = this.properties["size"] || RerouteNode.size; this.properties["size"] = [ - Number(this.properties["size"][0]), - Number(this.properties["size"][1]), + Number((this.properties["size"] as Size)[0]), + Number((this.properties["size"] as Size)[1]), ]; - this.size = this.properties["size"]; + this.size = this.properties["size"] as Size; app.graph.setDirtyCanvas(true, true); } @@ -823,25 +861,21 @@ class RerouteNode extends RgthreeBaseVirtualNode { const h = this.size[1]; this.properties["connections_layout"] = this.properties["connections_layout"] || (this as RerouteNode).defaultConnectionsLayout; - const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(this.properties["connections_layout"][0]); - const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(this.properties["connections_layout"][1]); + + const connections_layout = this.properties["connections_layout"] as [string, string]; + const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[0]); + const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[1]); if (degrees == 90 || degrees === -90) { if (degrees === -90) { - this.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex - 1) % 4) + 4) % 4]; - this.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex - 1) % 4) + 4) % 4]; + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex - 1) % 4) + 4) % 4]!; + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex - 1) % 4) + 4) % 4]!; } else { - this.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex + 1) % 4) + 4) % 4]; - this.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex + 1) % 4) + 4) % 4]; + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 1) % 4) + 4) % 4]!; + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 1) % 4) + 4) % 4]!; } } else if (degrees === 180) { - this.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]; - this.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]; + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]!; + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]!; } this.setSize([h, w]); } @@ -871,7 +905,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { let diffY = Math.round((event.clientY - shortcut.initialMousePos[1]) / 10) * 10; diffX *= shortcut.resizeOnSide[0] === LiteGraph.LEFT ? -1 : 1; diffY *= shortcut.resizeOnSide[1] === LiteGraph.UP ? -1 : 1; - const oldSize: Vector2 = [...this.size]; + const oldSize = [...this.size] as Size; this.setSize([ Math.max(10, shortcut.initialNodeSize[0] + diffX), Math.max(10, shortcut.initialNodeSize[1] + diffY), @@ -896,26 +930,29 @@ class RerouteNode extends RgthreeBaseVirtualNode { private cycleConnection(ioDir: IoDirection) { const props = this.properties; props["connections_layout"] = props["connections_layout"] || ["Left", "Right"]; + const connections_layout = this.properties["connections_layout"] as [string, string]; + const propIdx = ioDir == IoDirection.INPUT ? 0 : 1; const oppositeIdx = propIdx ? 0 : 1; - let currentLayout = props["connections_layout"][propIdx]; - let oppositeLayout = props["connections_layout"][oppositeIdx]; + let currentLayout = connections_layout[propIdx]; + let oppositeLayout = connections_layout[oppositeIdx]; if (this.size[0] === 10 || this.size[1] === 10) { props["connections_dir"] = props["connections_dir"] || [-1, -1]; - let currentDir = props["connections_dir"][propIdx] as number; - // let oppositeDir = props["connections_dir"][oppositeIdx]; + const connections_dir = this.properties["connections_dir"] as [number, number]; + let currentDir = connections_dir[propIdx] as number; + // let oppositeDir = connections_dir[oppositeIdx]; const options: number[] = this.size[0] === 10 ? currentLayout === "Bottom" ? [LiteGraph.DOWN, LiteGraph.RIGHT, LiteGraph.LEFT] : [LiteGraph.UP, LiteGraph.LEFT, LiteGraph.RIGHT] : currentLayout === "Right" - ? [LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.UP] - : [LiteGraph.LEFT, LiteGraph.UP, LiteGraph.DOWN]; + ? [LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.UP] + : [LiteGraph.LEFT, LiteGraph.UP, LiteGraph.DOWN]; let idx = options.indexOf(currentDir); let next = options[idx + 1] ?? options[0]!; - this.properties["connections_dir"][propIdx] = next; + connections_dir[propIdx] = next; return; } @@ -924,7 +961,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { let idx = LAYOUT_CLOCKWISE.indexOf(next); next = LAYOUT_CLOCKWISE[idx + 1] ?? LAYOUT_CLOCKWISE[0]!; } while (next === oppositeLayout); - this.properties["connections_layout"][propIdx] = next; + connections_layout[propIdx] = next; this.setDirtyCanvas(true, true); } @@ -1189,25 +1226,22 @@ addMenuItem(RerouteNode, app, { } else if (value?.startsWith("Rotate 180°")) { node.rotate(180); } else { - const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(node.properties["connections_layout"][0]); - const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(node.properties["connections_layout"][1]); + const connections_layout = node.properties["connections_layout"] as [string, string]; + const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[0]); + const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[1]); if (value?.startsWith("Flip Horizontally")) { - if (["Left", "Right"].includes(node.properties["connections_layout"][0])) { - node.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]; + if (["Left", "Right"].includes(connections_layout[0])) { + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]!; } - if (["Left", "Right"].includes(node.properties["connections_layout"][1])) { - node.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]; + if (["Left", "Right"].includes(connections_layout[1])) { + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]!; } } else if (value?.startsWith("Flip Vertically")) { - if (["Top", "Bottom"].includes(node.properties["connections_layout"][0])) { - node.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]; + if (["Top", "Bottom"].includes(connections_layout[0])) { + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]!; } - if (["Top", "Bottom"].includes(node.properties["connections_layout"][1])) { - node.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]; + if (["Top", "Bottom"].includes(connections_layout[1])) { + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]!; } } } @@ -1218,7 +1252,7 @@ addMenuItem(RerouteNode, app, { name: "Clone New Reroute...", subMenuOptions: ["Before", "After"], callback: async (node, value) => { - const clone = node.clone(); + const clone = node.clone()!; const pos = [...node.pos]; if (value === "Before") { clone.pos = [pos[0]! - 20, pos[1]! - 20]; diff --git a/src_web/comfyui/rgthree.ts b/src_web/comfyui/rgthree.ts index 65a618c..7a21a09 100644 --- a/src_web/comfyui/rgthree.ts +++ b/src_web/comfyui/rgthree.ts @@ -1,14 +1,15 @@ import type { LGraphCanvas as TLGraphCanvas, LGraphNode, - SerializedLGraphNode, - serializedLGraph, - ContextMenuItem, + IContextMenuValue, LGraph as TLGraph, - AdjustedMouseEvent, IContextMenuOptions, -} from "typings/litegraph.js"; +} from "@litegraph/litegraph.js"; +import type {CanvasMouseEvent, CanvasPointerExtensions} from "@litegraph/types/events.js"; +import type {NodeId} from "@litegraph/LGraphNode.js"; import type {ComfyApiFormat, ComfyApiPrompt, ComfyApp} from "typings/comfy.js"; +import type {Bookmark} from "./bookmark.js"; + import {app} from "scripts/app.js"; import {api} from "scripts/api.js"; import {SERVICE as CONFIG_SERVICE} from "./services/config_service.js"; @@ -26,7 +27,6 @@ import { iconStarFilled, logoRgthree, } from "rgthree/common/media/svgs.js"; -import type {Bookmark} from "./bookmark.js"; import {createElement, query, queryOne} from "rgthree/common/utils_dom.js"; export enum LogLevel { @@ -223,7 +223,7 @@ class Rgthree extends EventTarget { private rgthreeCssPromise: Promise; /** Stores a node id that we will use to queu only that output node (with `queueOutputNode`). */ - private queueNodeIds: number[] | null = null; + private queueNodeIds: NodeId[] | null = null; logger = new LogSession("[rgthree]"); @@ -232,11 +232,11 @@ class Rgthree extends EventTarget { processingQueue = false; loadingApiJson = false; - replacingReroute: number | null = null; + replacingReroute: NodeId | null = null; processingMouseDown = false; processingMouseUp = false; processingMouseMove = false; - lastAdjustedMouseEvent: AdjustedMouseEvent | null = null; + lastCanvasMouseEvent: CanvasMouseEvent | null = null; // Comfy/LiteGraph states so nodes and tell what the hell is going on. canvasCurrentlyCopyingToClipboard = false; @@ -275,7 +275,7 @@ class Rgthree extends EventTarget { }); const updateDebugKeyDown = () => { elDebugKeydowns.innerText = Object.keys(KEY_EVENT_SERVICE.downKeys).join(" "); - } + }; KEY_EVENT_SERVICE.addEventListener("keydown", updateDebugKeyDown); KEY_EVENT_SERVICE.addEventListener("keyup", updateDebugKeyDown); } @@ -303,15 +303,11 @@ class Rgthree extends EventTarget { LiteGraph.closeAllContextMenus(); if (e.button == 2) { const canvas = await waitForCanvas(); - new LiteGraph.ContextMenu( - this.getRgthreeContextMenuItems(), - { - title: `
${logoRgthree} rgthree-comfy
`, - left: e.clientX, - top: 5, - }, - canvas.getCanvasWindow(), - ); + new LiteGraph.ContextMenu(this.getRgthreeIContextMenuValues(), { + title: `
${logoRgthree} rgthree-comfy
`, + left: e.clientX, + top: 5, + }); return; } if (e.button == 0) { @@ -376,7 +372,7 @@ class Rgthree extends EventTarget { // Overrides LiteGraphs' processMouseDown to both keep state as well as dispatch a custom event. const processMouseDown = LGraphCanvas.prototype.processMouseDown; - LGraphCanvas.prototype.processMouseDown = function (e: AdjustedMouseEvent) { + LGraphCanvas.prototype.processMouseDown = function (e: PointerEvent) { rgthree.processingMouseDown = true; const returnVal = processMouseDown.apply(this, [...arguments] as any); rgthree.dispatchCustomEvent("on-process-mouse-down", {originalEvent: e}); @@ -388,9 +384,11 @@ class Rgthree extends EventTarget { // to capture the last `canvasX` and `canvasY` properties, which are not the same as LiteGraph's // `canvas.last_mouse_position`, unfortunately. const adjustMouseEvent = LGraphCanvas.prototype.adjustMouseEvent; - LGraphCanvas.prototype.adjustMouseEvent = function (e: PointerEvent) { + LGraphCanvas.prototype.adjustMouseEvent = function ( + e: T & Partial, + ): asserts e is T & CanvasMouseEvent { adjustMouseEvent.apply(this, [...arguments] as any); - rgthree.lastAdjustedMouseEvent = e as AdjustedMouseEvent; + rgthree.lastCanvasMouseEvent = e as CanvasMouseEvent; }; // [🤮] Copying to clipboard clones nodes and then manipulats the linking data manually which @@ -412,12 +410,15 @@ class Rgthree extends EventTarget { LGraphCanvas.onGroupAdd = function (...args: any[]) { const graph = app.graph as TLGraph; onGroupAdd.apply(this, [...args] as any); + // [🤮] Bad typing here.. especially the last arg; it is LGraphNode but can really be anything + // with pos or size... pity. LGraphCanvas.onShowPropertyEditor( - {}, - null, - null, - null, - graph._groups[graph._groups.length - 1], + {} as any, + null as any, + null as any, + null as any, + // + graph._groups[graph._groups.length - 1]! as unknown as LGraphNode, ); }; } @@ -491,7 +492,7 @@ class Rgthree extends EventTarget { LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) { let existingOptions = getCanvasMenuOptions.apply(this, [...args] as any); - const options = []; + const options: (IContextMenuValue | null)[] = []; options.push(null); // Divider options.push(null); // Divider options.push(null); // Divider @@ -499,7 +500,7 @@ class Rgthree extends EventTarget { content: logoRgthree + `rgthree-comfy`, className: "rgthree-contextmenu-item rgthree-contextmenu-main-item-rgthree-comfy", submenu: { - options: that.getRgthreeContextMenuItems(), + options: that.getRgthreeIContextMenuValues(), }, }); options.push(null); // Divider @@ -513,7 +514,9 @@ class Rgthree extends EventTarget { idx = idx || existingOptions.findIndex((o) => o?.content?.startsWith?.("Arrange (")); idx = idx || existingOptions.findIndex((o) => !o) + 1; idx = idx || 3; - existingOptions.splice(idx, 0, ...options); + // [🤮] existingOptions is typed as IContextMenuValue even though it need not be + // a string due to the crazy typing from the original litegraph. oh well. + (existingOptions as (IContextMenuValue | null)[]).splice(idx, 0, ...options); for (let i = existingOptions.length; i > 0; i--) { if (existingOptions[i] === null && existingOptions[i + 1] === null) { existingOptions.splice(i, 1); @@ -528,7 +531,7 @@ class Rgthree extends EventTarget { /** * Returns the standard menu items for an rgthree-comfy context menu. */ - private getRgthreeContextMenuItems(): ContextMenuItem[] { + private getRgthreeIContextMenuValues(): IContextMenuValue[] { const [canvas, graph] = [app.canvas as TLGraphCanvas, app.graph as TLGraph]; const selectedNodes = Object.values(canvas.selected_nodes || {}); let rerouteNodes: LGraphNode[] = []; @@ -553,20 +556,22 @@ class Rgthree extends EventTarget { className: "rgthree-contextmenu-item", has_submenu: true, submenu: { - options: getNodeTypeStrings() as unknown as ContextMenuItem[], + options: getNodeTypeStrings() as unknown as IContextMenuValue[], callback: ( - value: string | ContextMenuItem, + value: string | IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, ) => { const node = LiteGraph.createNode(addRgthree(value as string)); - node.pos = [ - rgthree.lastAdjustedMouseEvent!.canvasX, - rgthree.lastAdjustedMouseEvent!.canvasY, - ]; - canvas.graph.add(node); - canvas.selectNode(node); - app.graph.setDirtyCanvas(true, true); + if (node) { + node.pos = [ + rgthree.lastCanvasMouseEvent!.canvasX, + rgthree.lastCanvasMouseEvent!.canvasY, + ]; + canvas.graph!.add(node); + canvas.selectNode(node); + app.graph.setDirtyCanvas(true, true); + } }, extra: {rgthree_doNotNest: true}, }, @@ -630,7 +635,7 @@ class Rgthree extends EventTarget { * Wraps an `app.queuePrompt` call setting a specific node id that we will inspect and change the * serialized graph right before being sent (below, in our `api.queuePrompt` override). */ - async queueOutputNodes(nodeIds: number[]) { + async queueOutputNodes(nodeIds: NodeId[]) { try { this.queueNodeIds = nodeIds; await app.queuePrompt(); @@ -739,14 +744,16 @@ class Rgthree extends EventTarget { // Hook into a data load, like from an image or JSON drop-in. This is (currently) used to // monitor for bad linking data. const loadGraphData = app.loadGraphData; - app.loadGraphData = function (graph: serializedLGraph) { + // TODO: Pull in real ComfyUI Types. + app.loadGraphData = function (graph: any) { if (rgthree.monitorLinkTimeout) { clearTimeout(rgthree.monitorLinkTimeout); rgthree.monitorLinkTimeout = null; } rgthree.clearAllMessages(); // Try to make a copy to use, because ComfyUI's loadGraphData will modify it. - let graphCopy: serializedLGraph | null; + // TODO: Pull in real ComfyUI Types. + let graphCopy: any | null; try { graphCopy = JSON.parse(JSON.stringify(graph)); } catch (e) { @@ -839,10 +846,12 @@ class Rgthree extends EventTarget { */ getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff( node: LGraphNode, - ): SerializedLGraphNode | null { + // TODO: Pull in real ComfyUI Types. + ): any | null { return ( this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff?.nodes?.find( - (n: SerializedLGraphNode) => n.id === node.id, + // TODO: Pull in real ComfyUI Types. + (n: any) => n.id === node.id, ) ?? null ); } @@ -986,7 +995,7 @@ class Rgthree extends EventTarget { } } -function getBookmarks(): ContextMenuItem[] { +function getBookmarks(): IContextMenuValue[] { const graph: TLGraph = app.graph; // Sorts by Title. diff --git a/src_web/comfyui/seed.ts b/src_web/comfyui/seed.ts index e7a7c0f..c259eee 100644 --- a/src_web/comfyui/seed.ts +++ b/src_web/comfyui/seed.ts @@ -1,25 +1,25 @@ -import { app } from "scripts/app.js"; -import { ComfyWidgets } from "scripts/widgets.js"; import type { - ContextMenuItem, IContextMenuOptions, ContextMenu, LGraphNode as TLGraphNode, IWidget, LGraphCanvas, - SerializedLGraphNode, -} from "typings/litegraph.js"; + IContextMenuValue, +} from "@litegraph/litegraph.js"; import type { ComfyObjectInfo, - ComfyWidget, ComfyNodeConstructor, ComfyApiPrompt, } from "typings/comfy.js"; +import type { SerializedNode } from "typings/index.js"; + +import { app } from "scripts/app.js"; +import { ComfyWidgets } from "scripts/widgets.js"; import { RgthreeBaseServerNode } from "./base_node.js"; import { rgthree } from "./rgthree.js"; import { addConnectionLayoutSupport } from "./utils.js"; import { NodeTypesString } from "./constants.js"; -import { SerializedNode } from "typings/index.js"; +import { ISerialisedNode } from "@litegraph/types/serialisation.js"; const LAST_SEED_BUTTON_LABEL = "♻️ (Use Last Queued Seed)"; @@ -48,7 +48,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { serializedCtx: SeedSerializedCtx = {}; seedWidget!: IWidget; lastSeedButton!: IWidget; - lastSeedValue: ComfyWidget | null = null; + lastSeedValue: IWidget | null = null; randMax = 1125899906842624; // We can have a full range of seeds, including negative. But, for the randomRange we'll @@ -75,7 +75,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { ); } - override configure(info: SerializedLGraphNode): void { + override configure(info: ISerialisedNode): void { super.configure(info); if (this.properties?.["showLastSeed"]) { this.addLastSeedValue(); @@ -106,26 +106,26 @@ class RgthreeSeed extends RgthreeBaseServerNode { // Update random values in case seed comes down with different options. let step = this.seedWidget.options.step || 1; - this.randMax = Math.min(1125899906842624, this.seedWidget.options.max); + this.randMax = Math.min(1125899906842624, this.seedWidget.options.max ?? 0); // We can have a full range of seeds, including negative. But, for the randomRange we'll // only generate positives, since that's what folks assume. - this.randMin = Math.max(0, this.seedWidget.options.min); + this.randMin = Math.max(0, this.seedWidget.options.min ?? 0); this.randomRange = (this.randMax - Math.max(0, this.randMin)) / (step / 10); this.addWidget( "button", "🎲 Randomize Each Time", - null, + '', () => { this.seedWidget.value = SPECIAL_SEED_RANDOM; }, { serialize: false }, - ) as ComfyWidget; + ); this.addWidget( "button", "🎲 New Fixed Random", - null, + '', () => { this.seedWidget.value = Math.floor(Math.random() * this.randomRange) * (step / 10) + this.randMin; @@ -136,7 +136,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { this.lastSeedButton = this.addWidget( "button", LAST_SEED_BUTTON_LABEL, - null, + '', () => { this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value; this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL; @@ -147,12 +147,12 @@ class RgthreeSeed extends RgthreeBaseServerNode { this.lastSeedButton.disabled = true; } - override getExtraMenuOptions(canvas: LGraphCanvas, options: ContextMenuItem[]): void { + override getExtraMenuOptions(canvas: LGraphCanvas, options: IContextMenuValue[]) { super.getExtraMenuOptions?.apply(this, [...arguments] as any); options.splice(options.length - 1, 0, { content: "Show/Hide Last Seed Value", callback: ( - _value: ContextMenuItem, + _value: IContextMenuValue, _options: IContextMenuOptions, _event: MouseEvent, _parentMenu: ContextMenu | undefined, @@ -166,6 +166,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { } }, }); + return options; } addLastSeedValue() { @@ -175,7 +176,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { "last_seed", ["STRING", { multiline: true }], app, - ).widget; + ).widget as unknown as IWidget; this.lastSeedValue!.inputEl!.readOnly = true; this.lastSeedValue!.inputEl!.style.fontSize = "0.75rem"; this.lastSeedValue!.inputEl!.style.textAlign = "center"; @@ -185,7 +186,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { removeLastSeedValue() { if (!this.lastSeedValue) return; this.lastSeedValue!.inputEl!.remove(); - this.widgets.splice(this.widgets.indexOf(this.lastSeedValue as IWidget), 1); + this.widgets.splice(this.widgets.indexOf(this.lastSeedValue), 1); this.lastSeedValue = null; this.computeSize(); } @@ -248,7 +249,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { * There are no sideffects to calling this method. */ private getSeedToUse() { - const inputSeed: number = this.seedWidget.value; + const inputSeed = Number(this.seedWidget.value); let seedToUse: number | null = null; // If our input seed was a special seed, then handle it. diff --git a/src_web/comfyui/services/context_service.ts b/src_web/comfyui/services/context_service.ts index 0a0eeff..bb31dea 100644 --- a/src_web/comfyui/services/context_service.ts +++ b/src_web/comfyui/services/context_service.ts @@ -3,7 +3,7 @@ import type {DynamicContextNodeBase} from "../dynamic_context_base.js"; import {app} from "scripts/app.js"; import {NodeTypesString} from "../constants.js"; import {getConnectedOutputNodesAndFilterPassThroughs} from "../utils.js"; -import {INodeInputSlot, INodeOutputSlot, INodeSlot, LGraphNode} from "typings/litegraph.js"; +import {INodeInputSlot, INodeOutputSlot, INodeSlot, LGraphNode} from "@litegraph/litegraph.js"; export let SERVICE: ContextService; diff --git a/src_web/comfyui/services/fast_groups_service.ts b/src_web/comfyui/services/fast_groups_service.ts index 57892cd..fe92f00 100644 --- a/src_web/comfyui/services/fast_groups_service.ts +++ b/src_web/comfyui/services/fast_groups_service.ts @@ -1,11 +1,12 @@ -import { app } from "scripts/app.js"; import type { BaseFastGroupsModeChanger } from "../fast_groups_muter.js"; -import { - type LGraph as TLGraph, - type LGraphCanvas as TLGraphCanvas, +import type { + LGraph as TLGraph, + LGraphCanvas as TLGraphCanvas, LGraphGroup, Vector4, -} from "typings/litegraph.js"; +} from "@litegraph/litegraph.js"; + +import { app } from "scripts/app.js"; /** * A service that keeps global state that can be shared by multiple FastGroupsMuter or @@ -96,7 +97,7 @@ class FastGroupsService { if (!this.cachedNodeBoundings) { this.cachedNodeBoundings = {}; for (const node of app.graph._nodes) { - this.cachedNodeBoundings[node.id] = node.getBounding(); + this.cachedNodeBoundings[Number(node.id)] = node.getBounding() as Vector4; } setTimeout(() => { this.cachedNodeBoundings = null; @@ -111,11 +112,11 @@ class FastGroupsService { */ recomputeInsideNodesForGroup(group: LGraphGroup) { const cachedBoundings = this.getBoundingsForAllNodes(); - const nodes = group.graph._nodes; + const nodes = group.graph!._nodes; group._nodes.length = 0; for (const node of nodes) { - const node_bounding = cachedBoundings[node.id]; + const node_bounding = cachedBoundings[Number(node.id)]; if (!node_bounding || !LiteGraph.overlapBounding(group._bounding, node_bounding)) { continue; } @@ -134,6 +135,7 @@ class FastGroupsService { if ( // Don't recalculate nodes if we're moving a group (added by ComfyUI in app.js) + // TODO: This doesn't look available anymore... ? !canvas.selected_group_moving && (!this.groupsUnsorted.length || now - this.msLastUnsorted > this.msThreshold) ) { diff --git a/src_web/comfyui/testing/comfyui_env.ts b/src_web/comfyui/testing/comfyui_env.ts index 7bbf4f1..f89afa2 100644 --- a/src_web/comfyui/testing/comfyui_env.ts +++ b/src_web/comfyui/testing/comfyui_env.ts @@ -1,7 +1,7 @@ import { app } from "scripts/app.js"; import { NodeTypesString } from "../constants.js"; import { wait } from "rgthree/common/shared_utils.js"; -import type { LGraphNode } from "typings/litegraph.js"; +import type { LGraphNode } from "@litegraph/litegraph.js"; type addNodeOptions = { placement?: string; @@ -19,7 +19,7 @@ export class ComfyUITestEnvironment { async addNode(nodeString: string, options: addNodeOptions = {}) { const [canvas, graph] = [app.canvas, app.graph]; - const node = LiteGraph.createNode(nodeString); + const node = LiteGraph.createNode(nodeString)!; let x = 0; let y = 30; if (this.lastNode) { @@ -35,7 +35,7 @@ export class ComfyUITestEnvironment { y = this.maxY + 50; } } - canvas.graph.add(node); + canvas.graph!.add(node); node.pos = [x, y]; canvas.selectNode(node); app.graph.setDirtyCanvas(true, true); @@ -50,6 +50,7 @@ export class ComfyUITestEnvironment { app.graph.clear(); const nodeConfig = await this.addNode(NodeTypesString.KSAMPLER_CONFIG); const displayAny = await this.addNode(NodeTypesString.DISPLAY_ANY); + nodeConfig.widgets = nodeConfig.widgets || []; nodeConfig.widgets[0]!.value = Math.round(Math.random() * 100); nodeConfig.connect(0, displayAny, 0); await this.queuePrompt(); diff --git a/src_web/comfyui/tests/context_dynamic_tests.ts b/src_web/comfyui/tests/context_dynamic_tests.ts index 90d882e..e317a1b 100644 --- a/src_web/comfyui/tests/context_dynamic_tests.ts +++ b/src_web/comfyui/tests/context_dynamic_tests.ts @@ -1,12 +1,9 @@ import type { LiteGraph as TLiteGraph, - LGraphCanvas as TLGraphCanvas, - LGraph as TLGraph, LGraphNode as TLGraphNode, - Vector2, LGraphNode, -} from "typings/litegraph.js"; -import {rgthree} from "../rgthree.js"; +} from "@litegraph/litegraph.js"; + import {NodeTypesString} from "../constants.js"; import {wait} from "rgthree/common/shared_utils.js"; import {describe, should, beforeEach, expect, describeRun} from "../testing/runner.js"; @@ -107,6 +104,7 @@ function vertifyInputsStructure(node: LGraphNode, expectedLength: number) { await beforeEach(async () => { nodeConfig2 = await env.addNode(NodeTypesString.KSAMPLER_CONFIG, {placement: "start"}); + nodeConfig2.widgets = nodeConfig2.widgets || []; nodeConfig2.widgets[0]!.value = 111; nodeConfig2.widgets[2]!.value = 11.1; nodeCtx2 = await env.addNode(NodeTypesString.DYNAMIC_CONTEXT, {placement: "right"}); diff --git a/src_web/comfyui/utils.ts b/src_web/comfyui/utils.ts index cf3a5bd..a64366b 100644 --- a/src_web/comfyui/utils.ts +++ b/src_web/comfyui/utils.ts @@ -2,7 +2,6 @@ import type { ComfyApp, ComfyNodeConstructor, ComfyObjectInfo } from "typings/co import type { Vector2, LGraphCanvas, - ContextMenuItem, LLink, LGraph, IContextMenuOptions, @@ -11,12 +10,18 @@ import type { INodeSlot, INodeInputSlot, INodeOutputSlot, -} from "typings/litegraph.js"; + IContextMenuValue, + ISlotType, +} from "@litegraph/litegraph.js"; import type { Constructor } from "typings/index.js"; + import { app } from "scripts/app.js"; import { api } from "scripts/api.js"; import { Resolver, getResolver, wait } from "rgthree/common/shared_utils.js"; import { RgthreeHelpDialog } from "rgthree/common/dialog.js"; +import { NodeProperty } from "@litegraph/LGraphNode"; +import { LinkDirection } from "@litegraph/types/globalEnums"; +import { Point } from "@litegraph/interfaces"; /** * Override the api.getNodeDefs call to add a hook for refreshing node defs. @@ -59,7 +64,7 @@ export const LAYOUT_CLOCKWISE = ["Top", "Right", "Bottom", "Left"]; interface MenuConfig { name: string | ((node: LGraphNode) => string); property?: string; - prepareValue?: (value: string, node: LGraphNode) => any; + prepareValue?: (value: NodeProperty | undefined, node: LGraphNode) => any; callback?: (node: LGraphNode, value?: string) => void; subMenuOptions?: (string | null)[] | ((node: LGraphNode) => (string | null)[]); } @@ -73,7 +78,7 @@ export function addMenuItem( const oldGetExtraMenuOptions = node.prototype.getExtraMenuOptions; node.prototype.getExtraMenuOptions = function ( canvas: LGraphCanvas, - menuOptions: ContextMenuItem[], + menuOptions: IContextMenuValue[], ) { oldGetExtraMenuOptions && oldGetExtraMenuOptions.apply(this, [canvas, menuOptions]); addMenuItemOnExtraMenuOptions(this, config, menuOptions, after); @@ -125,7 +130,7 @@ export function waitForGraph() { export function addMenuItemOnExtraMenuOptions( node: LGraphNode, config: MenuConfig, - menuOptions: ContextMenuItem[], + menuOptions: (IContextMenuValue | null)[], after = "Shape", ) { let idx = menuOptions @@ -154,7 +159,7 @@ export function addMenuItemOnExtraMenuOptions( has_submenu: !!subMenuOptions?.length, isRgthree: true, // Mark it, so we can find it. callback: ( - value: ContextMenuItem, + value: IContextMenuValue, _options: IContextMenuOptions, event: MouseEvent, parentMenu: ContextMenu | undefined, @@ -167,7 +172,7 @@ export function addMenuItemOnExtraMenuOptions( event, parentMenu, callback: ( - subValue: ContextMenuItem, + subValue: IContextMenuValue, _options: IContextMenuOptions, _event: MouseEvent, _parentMenu: ContextMenu | undefined, @@ -193,7 +198,7 @@ export function addMenuItemOnExtraMenuOptions( } config.callback && config.callback(node, value?.content); }, - } as ContextMenuItem); + } as IContextMenuValue); } export function addConnectionLayoutSupport( @@ -210,7 +215,7 @@ export function addConnectionLayoutSupport( property: "connections_layout", subMenuOptions: options.map((option) => option[0] + (option[1] ? " -> " + option[1] : "")), prepareValue: (value, node) => { - const values = value.split(" -> "); + const values = String(value).split(" -> "); if (!values[1] && !node.outputs?.length) { values[1] = LAYOUT_LABEL_OPPOSITES[values[0]!]!; } @@ -276,7 +281,7 @@ export function getConnectionPosForLayout( node.properties = node.properties || {}; const layout = node.properties["connections_layout"] || (node as any).defaultConnectionsLayout || ["Left", "Right"]; - const collapseConnections = node.properties["collapse_connections"] || false; + const collapseConnections = (node.properties["collapse_connections"] as boolean) || false; const offset = (node.constructor as any).layout_slot_offset ?? LiteGraph.NODE_SLOT_HEIGHT * 0.5; let side = isInput ? layout[0] : layout[1]; const otherSide = isInput ? layout[1] : layout[0]; @@ -317,8 +322,9 @@ export function getConnectionPosForLayout( cxn.dir = data[0]; // If we are only 10px tall or wide, then look at connections_dir for the direction. - if ((node.size[0] == 10 || node.size[1] == 10) && node.properties["connections_dir"]) { - cxn.dir = node.properties["connections_dir"][isInput ? 0 : 1]!; + const connections_dir = node.properties["connections_dir"] as [LinkDirection, LinkDirection]; + if ((node.size[0] == 10 || node.size[1] == 10) && connections_dir) { + cxn.dir = connections_dir[isInput ? 0 : 1]!; } if (side === "Left") { @@ -397,7 +403,7 @@ function toggleConnectionLabel(cxn: any, hide = true) { return cxn; } -export function addHelpMenuItem(node: LGraphNode, content: string, menuOptions: ContextMenuItem[]) { +export function addHelpMenuItem(node: LGraphNode, content: string, menuOptions: (IContextMenuValue | null)[]) { addMenuItemOnExtraMenuOptions( node, { @@ -675,12 +681,12 @@ export async function replaceNode( const existingCtor = existingNode.constructor as typeof LGraphNode; const newNode = - typeof typeOrNewNode === "string" ? LiteGraph.createNode(typeOrNewNode) : typeOrNewNode; + typeof typeOrNewNode === "string" ? LiteGraph.createNode(typeOrNewNode)! : typeOrNewNode; // Port title (maybe) the position, size, and properties from the old node. if (existingNode.title != existingCtor.title) { newNode.title = existingNode.title; } - newNode.pos = [...existingNode.pos]; + newNode.pos = [...existingNode.pos] as Point; newNode.properties = { ...existingNode.properties }; const oldComputeSize = [...existingNode.computeSize()]; // oldSize to use. If we match the smallest size (computeSize) then don't record and we'll use @@ -875,7 +881,7 @@ export async function matchLocalSlotsToServer( // doesn't exist). if ( nextNode && - (nextNode.constructor as ComfyNodeConstructor)?.type!.includes("Reroute") + (nextNode.constructor as any)?.type!.includes("Reroute") ) { (nextNode as any).stabilize && (nextNode as any).stabilize(); } @@ -916,7 +922,7 @@ export function isValidConnection(ioA?: INodeSlot | null, ioB?: INodeSlot | null * lists (without users needing to go through and re-create all their nodes one by one). */ const oldIsValidConnection = LiteGraph.isValidConnection; -LiteGraph.isValidConnection = function (typeA: string | string[], typeB: string | string[]) { +LiteGraph.isValidConnection = function (typeA: ISlotType, typeB: ISlotType): boolean { let isValid = oldIsValidConnection.call(LiteGraph, typeA, typeB); if (!isValid) { typeA = String(typeA); diff --git a/src_web/comfyui/utils_canvas.ts b/src_web/comfyui/utils_canvas.ts index 3569522..d787646 100644 --- a/src_web/comfyui/utils_canvas.ts +++ b/src_web/comfyui/utils_canvas.ts @@ -1,5 +1,6 @@ +import type { LGraphCanvas as TLGraphCanvas, Vector2 } from "@litegraph/litegraph.js"; + import { app } from "scripts/app.js"; -import type { LGraphCanvas as TLGraphCanvas, Vector2 } from "../typings/litegraph.js"; function binarySearch(max: number, getValue: (n: number) => number, match: number) { let min = 0; diff --git a/src_web/comfyui/utils_inputs_outputs.ts b/src_web/comfyui/utils_inputs_outputs.ts index ae007d5..d69d791 100644 --- a/src_web/comfyui/utils_inputs_outputs.ts +++ b/src_web/comfyui/utils_inputs_outputs.ts @@ -1,4 +1,4 @@ -import type { LGraphNode } from "typings/litegraph.js"; +import type { LGraphNode } from "@litegraph/litegraph.js"; /** Removes all inputs from the end. */ export function removeUnusedInputsFromEnd(node: LGraphNode, minNumber = 1, nameMatch?: RegExp) { diff --git a/src_web/comfyui/utils_menu.ts b/src_web/comfyui/utils_menu.ts index d819944..73f38a0 100644 --- a/src_web/comfyui/utils_menu.ts +++ b/src_web/comfyui/utils_menu.ts @@ -1,6 +1,3 @@ -import { app } from "scripts/app.js"; -import { rgthreeApi } from "rgthree/common/rgthree_api.js"; - import type { LGraphCanvas as TLGraphCanvas, IWidget, @@ -8,7 +5,10 @@ import type { ContextMenuEventListener, ContextMenu, IContextMenuItem, -} from "../typings/litegraph.js"; +} from "@litegraph/litegraph.js"; + +import { app } from "scripts/app.js"; +import { rgthreeApi } from "rgthree/common/rgthree_api.js"; const PASS_THROUGH = function (item: T) { return item as T; @@ -18,7 +18,7 @@ const PASS_THROUGH = function (item: T) { * Shows a lora chooser context menu. */ export async function showLoraChooser( - event: PointerEvent, + event: PointerEvent | MouseEvent, callback: ContextMenuEventListener, parentMenu?: ContextMenu | null, loras?: string[], @@ -29,7 +29,7 @@ export async function showLoraChooser( } new LiteGraph.ContextMenu(loras, { event: event, - parentMenu, + parentMenu: parentMenu != null ? parentMenu : undefined, title: "Choose a lora", scale: Math.max(1, canvas.ds?.scale ?? 1), className: "dark", @@ -44,7 +44,7 @@ export async function showLoraChooser( * it will be filtered out (rather than use a separate filter method). */ export function showNodesChooser( - event: PointerEvent, + event: PointerEvent | MouseEvent, mapFn: (n: LGraphNode) => T | null, callback: ContextMenuEventListener, parentMenu?: ContextMenu, diff --git a/src_web/comfyui/utils_widgets.ts b/src_web/comfyui/utils_widgets.ts index 12de9f3..61c0b9b 100644 --- a/src_web/comfyui/utils_widgets.ts +++ b/src_web/comfyui/utils_widgets.ts @@ -1,13 +1,14 @@ -import { app } from "scripts/app.js"; import type { - IWidget, LGraphNode, LGraphCanvas as TLGraphCanvas, Vector2, - AdjustedMouseEvent, Vector4, -} from "../typings/litegraph.js"; -import { drawNodeWidget, drawRoundedRectangle, fitString, isLowQuality } from "./utils_canvas.js"; +} from "@litegraph/litegraph.js"; +import type {CanvasMouseEvent} from "@litegraph/types/events.js"; +import type {IBaseWidget, IWidgetOptions} from "@litegraph/types/widgets.js"; + +import {app} from "scripts/app.js"; +import {drawNodeWidget, drawRoundedRectangle, fitString, isLowQuality} from "./utils_canvas.js"; /** * Draws a label on teft, and a value on the right, ellipsizing when out of space. @@ -19,7 +20,7 @@ export function drawLabelAndValue( width: number, posY: number, height: number, - options?: { offsetLeft: number }, + options?: {offsetLeft: number}, ) { const outerMargin = 15; const innerMargin = 10; @@ -43,23 +44,23 @@ export function drawLabelAndValue( export type RgthreeBaseWidgetBounds = { /** The bounds, either [x, width] assuming the full height, or [x, y, width, height] if height. */ bounds: Vector2 | Vector4; - onDown?(event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode): boolean | void; + onDown?(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode): boolean | void; onDown?( - event: AdjustedMouseEvent, + event: CanvasMouseEvent, pos: Vector2, node: LGraphNode, bounds: RgthreeBaseWidgetBounds, ): boolean | void; - onUp?(event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode): boolean | void; + onUp?(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode): boolean | void; onUp?( - event: AdjustedMouseEvent, + event: CanvasMouseEvent, pos: Vector2, node: LGraphNode, bounds: RgthreeBaseWidgetBounds, ): boolean | void; - onMove?(event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode): boolean | void; + onMove?(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode): boolean | void; onMove?( - event: AdjustedMouseEvent, + event: CanvasMouseEvent, pos: Vector2, node: LGraphNode, bounds: RgthreeBaseWidgetBounds, @@ -71,16 +72,21 @@ export type RgthreeBaseHitAreas = { [K in Keys]: RgthreeBaseWidgetBounds; }; +type NotArray = T extends Array ? never : T; + /** * A base widget that handles mouse events more properly. */ -export abstract class RgthreeBaseWidget implements IWidget { +export abstract class RgthreeBaseWidget implements IBaseWidget { // We don't want our value to be an array as a widget will be serialized as an "input" for the API // which uses an array value to represent a link. To keep things simpler, we'll avoid using an // array at all. - abstract value: T extends Array ? never : T; + abstract value: NotArray; name: string; + type: "custom" = "custom"; + options: IWidgetOptions = {}; + y: number = 0; last_y: number = 0; protected mouseDowned: Vector2 | null = null; @@ -94,6 +100,10 @@ export abstract class RgthreeBaseWidget implements IWidget { this.name = name; } + serializeValue(node: LGraphNode, index: number): Promise | V { + return this.value; + } + private clickWasWithinBounds(pos: Vector2, bounds: Vector2 | Vector4) { let xStart = bounds[0]; let xEnd = xStart + (bounds.length > 2 ? bounds[2]! : bounds[1]!); @@ -101,14 +111,14 @@ export abstract class RgthreeBaseWidget implements IWidget { if (bounds.length === 2) { return clickedX; } - return clickedX && pos[1] >= bounds[1] && pos[1] <= bounds[1] + bounds[3]; + return clickedX && pos[1] >= bounds[1] && pos[1] <= bounds[1] + bounds[3]!; } - mouse(event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode) { + mouse(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode) { const canvas = app.canvas as TLGraphCanvas; if (event.type == "pointerdown") { - this.mouseDowned = [...pos]; + this.mouseDowned = [...pos] as Vector2; this.isMouseDownedAndOver = true; this.downedHitAreasForMove.length = 0; // Loop over out bounds data and call any specifics. @@ -172,7 +182,7 @@ export abstract class RgthreeBaseWidget implements IWidget { } /** An event that fires when the pointer is pressed down (once). */ - onMouseDown(event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode): boolean | void { + onMouseDown(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode): boolean | void { return; } @@ -180,7 +190,7 @@ export abstract class RgthreeBaseWidget implements IWidget { * An event that fires when the pointer is let go. Only fires if this was the widget that was * originally pressed down. */ - onMouseUp(event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode): boolean | void { + onMouseUp(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode): boolean | void { return; } @@ -189,7 +199,7 @@ export abstract class RgthreeBaseWidget implements IWidget { * of the widget. Check `isMouseDownedAndOver` to determine if the mouse is currently over the * widget or not. */ - onMouseMove(event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode): boolean | void { + onMouseMove(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode): boolean | void { return; } } @@ -199,11 +209,11 @@ export abstract class RgthreeBaseWidget implements IWidget { */ export class RgthreeBetterButtonWidget extends RgthreeBaseWidget { value: string = ""; - mouseUpCallback: (event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode) => boolean | void; + mouseUpCallback: (event: CanvasMouseEvent, pos: Vector2, node: LGraphNode) => boolean | void; constructor( name: string, - mouseUpCallback: (event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode) => boolean | void, + mouseUpCallback: (event: CanvasMouseEvent, pos: Vector2, node: LGraphNode) => boolean | void, ) { super(name); this.mouseUpCallback = mouseUpCallback; @@ -213,7 +223,7 @@ export class RgthreeBetterButtonWidget extends RgthreeBaseWidget { drawWidgetButton({ctx, node, width, height, y}, this.name, this.isMouseDownedAndOver); } - override onMouseUp(event: AdjustedMouseEvent, pos: Vector2, node: LGraphNode) { + override onMouseUp(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode) { return this.mouseUpCallback(event, pos, node); } } @@ -221,24 +231,24 @@ export class RgthreeBetterButtonWidget extends RgthreeBaseWidget { /** * A better implementation of the LiteGraph text widget, including auto ellipsis. */ -export class RgthreeBetterTextWidget implements IWidget { - name: string; +export class RgthreeBetterTextWidget extends RgthreeBaseWidget { value: string; constructor(name: string, value: string) { + super(name); this.name = name; this.value = value; } draw(ctx: CanvasRenderingContext2D, node: LGraphNode, width: number, y: number, height: number) { - const widgetData = drawNodeWidget(ctx, { width, height, posY: y }); + const widgetData = drawNodeWidget(ctx, {width, height, posY: y}); if (!widgetData.lowQuality) { drawLabelAndValue(ctx, this.name, this.value, width, y, height); } } - mouse(event: MouseEvent, pos: Vector2, node: LGraphNode) { + override mouse(event: CanvasMouseEvent, pos: Vector2, node: LGraphNode): boolean { const canvas = app.canvas as TLGraphCanvas; if (event.type == "pointerdown") { canvas.prompt("Label", this.value, (v: string) => (this.value = v), event); @@ -260,13 +270,13 @@ type RgthreeDividerWidgetOptions = { thickness: number; }; + /** * A divider widget; can also be used as a spacer if fed a 0 thickness. */ -export class RgthreeDividerWidget implements IWidget { - options = { serialize: false }; - value = null; - name = "divider"; +export class RgthreeDividerWidget extends RgthreeBaseWidget { + override options = {serialize: false}; + value = ''; private readonly widgetOptions: RgthreeDividerWidgetOptions = { marginTop: 7, @@ -278,6 +288,7 @@ export class RgthreeDividerWidget implements IWidget { }; constructor(widgetOptions?: Partial) { + super("divider"); Object.assign(this.widgetOptions, widgetOptions || {}); } @@ -310,22 +321,21 @@ export type RgthreeLabelWidgetOptions = { /** A label to put on the right side. */ actionLabel?: "__PLUS_ICON__" | string; - actionCallback?: (event: PointerEvent) => void; + actionCallback?: (event: PointerEvent | CanvasMouseEvent) => void; }; /** * A simple label widget, drawn with no background. */ -export class RgthreeLabelWidget implements IWidget { - options = { serialize: false }; - value = null; - name: string; +export class RgthreeLabelWidget extends RgthreeBaseWidget { + override options = {serialize: false}; + value = ''; private readonly widgetOptions: RgthreeLabelWidgetOptions = {}; private posY: number = 0; constructor(name: string, widgetOptions?: RgthreeLabelWidgetOptions) { - this.name = name; + super(name); Object.assign(this.widgetOptions, widgetOptions); } @@ -374,7 +384,7 @@ export class RgthreeLabelWidget implements IWidget { ctx.restore(); } - mouse(event: PointerEvent, nodePos: Vector2, node: LGraphNode) { + override mouse(event: CanvasMouseEvent, nodePos: Vector2, node: LGraphNode): boolean { if ( event.type !== "pointerdown" || isLowQuality() || @@ -395,37 +405,52 @@ export class RgthreeLabelWidget implements IWidget { } /** An invisible widget. */ -export class RgthreeInvisibleWidget implements IWidget { - name: string; - type: string; - value: T; - serializeValue: IWidget['serializeValue'] = undefined; +export class RgthreeInvisibleWidget extends RgthreeBaseWidget { + value: NotArray; + private serializeValueFn?: (node: LGraphNode, index: number) => Promise | T; - constructor(name: string, type: string, value: T, serializeValueFn: ()=> T) { - this.name = name; - this.type = type; + constructor( + name: string, + type: string, + value: NotArray, + serializeValueFn?: (node: LGraphNode, index: number) => Promise | T, + ) { + super(name); + // this.type = type; this.value = value; - if (serializeValueFn) { - this.serializeValue = serializeValueFn - } + this.serializeValueFn = serializeValueFn; } - draw() { return; } - computeSize(width: number) : Vector2 { return [0, 0]; } -} + draw() { + return; + } + computeSize(width: number): Vector2 { + return [0, 0]; + } + + override serializeValue(node: LGraphNode, index: number): T | Promise { + return this.serializeValueFn != null + ? this.serializeValueFn(node, index) + : super.serializeValue(node, index); + } +} type DrawContext = { - ctx: CanvasRenderingContext2D, - node: LGraphNode, - width: number, - y: number, - height: number, -} + ctx: CanvasRenderingContext2D; + node: LGraphNode; + width: number; + y: number; + height: number; +}; /** * Draws a better button. */ -export function drawWidgetButton(drawCtx: DrawContext, text: string, isMouseDownedAndOver: boolean = false) { +export function drawWidgetButton( + drawCtx: DrawContext, + text: string, + isMouseDownedAndOver: boolean = false, +) { // First, add a shadow if we're not down or lowquality. if (!isLowQuality() && !isMouseDownedAndOver) { drawRoundedRectangle(drawCtx.ctx, { @@ -458,4 +483,4 @@ export function drawWidgetButton(drawCtx: DrawContext, text: string, isMouseDown drawCtx.y + drawCtx.height / 2 + (isMouseDownedAndOver ? 1 : 0), ); } -} \ No newline at end of file +} diff --git a/src_web/common/comfyui_shim_pnginfo.ts b/src_web/common/comfyui_shim_pnginfo.ts index 5e86b48..e3d4639 100644 --- a/src_web/common/comfyui_shim_pnginfo.ts +++ b/src_web/common/comfyui_shim_pnginfo.ts @@ -250,15 +250,15 @@ export async function importA1111(graph: lazyComfyAny, parameters: lazyComfyAny) let positive = parameters.substr(0, p2).trim() let negative = parameters.substring(p2 + 18, p).trim() - const ckptNode = LiteGraph.createNode('CheckpointLoaderSimple') - const clipSkipNode = LiteGraph.createNode('CLIPSetLastLayer') - const positiveNode = LiteGraph.createNode('CLIPTextEncode') - const negativeNode = LiteGraph.createNode('CLIPTextEncode') - const samplerNode = LiteGraph.createNode('KSampler') - const imageNode = LiteGraph.createNode('EmptyLatentImage') - const vaeNode = LiteGraph.createNode('VAEDecode') - const vaeLoaderNode = LiteGraph.createNode('VAELoader') - const saveNode = LiteGraph.createNode('SaveImage') + const ckptNode = LiteGraph.createNode('CheckpointLoaderSimple')! + const clipSkipNode = LiteGraph.createNode('CLIPSetLastLayer')! + const positiveNode = LiteGraph.createNode('CLIPTextEncode')! + const negativeNode = LiteGraph.createNode('CLIPTextEncode')! + const samplerNode = LiteGraph.createNode('KSampler')! + const imageNode = LiteGraph.createNode('EmptyLatentImage')! + const vaeNode = LiteGraph.createNode('VAEDecode')! + const vaeLoaderNode = LiteGraph.createNode('VAELoader')! + const saveNode = LiteGraph.createNode('SaveImage')! let hrSamplerNode = null as lazyComfyAny let hrSteps = null @@ -416,7 +416,7 @@ export async function importA1111(graph: lazyComfyAny, parameters: lazyComfyAny) let latentNode if (hrMethod.startsWith('Latent')) { - latentNode = upscaleNode = LiteGraph.createNode('LatentUpscale') + latentNode = upscaleNode = LiteGraph.createNode('LatentUpscale')! graph.add(upscaleNode) samplerNode.connect(0, upscaleNode, 0) @@ -427,29 +427,28 @@ export async function importA1111(graph: lazyComfyAny, parameters: lazyComfyAny) } setWidgetValue(upscaleNode, 'upscale_method', hrMethod, true) } else { - const decode = LiteGraph.createNode('VAEDecodeTiled') + const decode = LiteGraph.createNode('VAEDecodeTiled')! graph.add(decode) samplerNode.connect(0, decode, 0) vaeLoaderNode.connect(0, decode, 1) - const upscaleLoaderNode = - LiteGraph.createNode('UpscaleModelLoader') + const upscaleLoaderNode = LiteGraph.createNode('UpscaleModelLoader')! graph.add(upscaleLoaderNode) setWidgetValue(upscaleLoaderNode, 'model_name', hrMethod, true) const modelUpscaleNode = LiteGraph.createNode( 'ImageUpscaleWithModel' - ) + )! graph.add(modelUpscaleNode) decode.connect(0, modelUpscaleNode, 1) upscaleLoaderNode.connect(0, modelUpscaleNode, 0) - upscaleNode = LiteGraph.createNode('ImageScale') + upscaleNode = LiteGraph.createNode('ImageScale')! graph.add(upscaleNode) modelUpscaleNode.connect(0, upscaleNode, 0) const vaeEncodeNode = (latentNode = - LiteGraph.createNode('VAEEncodeTiled')) + LiteGraph.createNode('VAEEncodeTiled')!)! graph.add(vaeEncodeNode) upscaleNode.connect(0, vaeEncodeNode, 0) vaeLoaderNode.connect(0, vaeEncodeNode, 1) @@ -458,7 +457,7 @@ export async function importA1111(graph: lazyComfyAny, parameters: lazyComfyAny) setWidgetValue(upscaleNode, 'width', ceil64(uw)) setWidgetValue(upscaleNode, 'height', ceil64(uh)) - hrSamplerNode = LiteGraph.createNode('KSampler') + hrSamplerNode = LiteGraph.createNode('KSampler')! graph.add(hrSamplerNode) ckptNode.connect(0, hrSamplerNode, 0) positiveNode.connect(0, hrSamplerNode, 1) diff --git a/src_web/common/dialog.ts b/src_web/common/dialog.ts index bc93a06..59ccb40 100644 --- a/src_web/common/dialog.ts +++ b/src_web/common/dialog.ts @@ -1,4 +1,4 @@ -import type { LGraphNode, LGraphNodeConstructor } from "typings/litegraph.js"; +import type { LGraphNode, LGraphNodeConstructor } from "@litegraph/litegraph.js"; import { createElement as $el, getClosestOrSelf, setAttributes } from "./utils_dom.js"; type RgthreeDialogButton = { diff --git a/src_web/common/link_fixer.ts b/src_web/common/link_fixer.ts index 54491da..7b3b0aa 100644 --- a/src_web/common/link_fixer.ts +++ b/src_web/common/link_fixer.ts @@ -1,17 +1,23 @@ import type { BadLinksData, SerializedGraph, SerializedLink, SerializedNode } from "typings/index.js"; -import type { LGraph, LGraphNode, LLink, serializedLGraph } from "typings/litegraph.js"; +import type { LGraph, LGraphNode, LLink} from "@litegraph/litegraph.js"; +import type { NodeId } from "@litegraph/LGraphNode"; + +// TODO: Fix comyui types. +type serializedLGraph = any; +type serializedLGraphNode = any; enum IoDirection { INPUT, OUTPUT, } -function getNodeById(graph: SerializedGraph | LGraph | serializedLGraph, id: number) { + +function getNodeById(graph: SerializedGraph | LGraph | serializedLGraph, id: NodeId) { if ((graph as LGraph).getNodeById) { return (graph as LGraph).getNodeById(id); } graph = graph as SerializedGraph; - return graph.nodes.find((n) => n.id === id)!; + return graph.nodes.find((node: serializedLGraphNode) => Number(node.id) === id)!; } function extendLink(link: SerializedLink) { diff --git a/src_web/common/prompt_service.ts b/src_web/common/prompt_service.ts index 93aeb82..e2870a6 100644 --- a/src_web/common/prompt_service.ts +++ b/src_web/common/prompt_service.ts @@ -10,7 +10,7 @@ import type { ComfyApiPrompt, } from "typings/comfy.js"; import { api } from "scripts/api.js"; -import type { LGraph as TLGraph, LGraphCanvas as TLGraphCanvas } from "typings/litegraph.js"; +import type { LGraph as TLGraph, LGraphCanvas as TLGraphCanvas } from "@litegraph/litegraph.js"; import { Resolver, getResolver } from "./shared_utils.js"; /** diff --git a/src_web/common/shared_utils.ts b/src_web/common/shared_utils.ts index f611b48..005145b 100644 --- a/src_web/common/shared_utils.ts +++ b/src_web/common/shared_utils.ts @@ -144,7 +144,7 @@ export function setObjectValue(obj: any, objKey: string, value: any, createMissi /** * Moves an item in an array (by item or its index) to another index. */ -export function moveArrayItem(arr: T[], itemOrFrom: T | number, to: number) { +export function moveArrayItem(arr: any[], itemOrFrom: any, to: number) { const from = typeof itemOrFrom === "number" ? itemOrFrom : arr.indexOf(itemOrFrom); arr.splice(to, 0, arr.splice(from, 1)[0]!); } diff --git a/src_web/scripts_comfy/widgets.ts b/src_web/scripts_comfy/widgets.ts index ee8e9c6..3ef835b 100644 --- a/src_web/scripts_comfy/widgets.ts +++ b/src_web/scripts_comfy/widgets.ts @@ -1,4 +1,4 @@ -import type { LGraphNode } from "typings/litegraph.js"; +import type { LGraphNode } from "@litegraph/litegraph.js"; import type { ComfyApp, ComfyWidget } from "../typings/comfy.js"; type ComfyWidgetFn = ( diff --git a/src_web/typings/comfy.d.ts b/src_web/typings/comfy.d.ts index 3e84035..f1e398f 100644 --- a/src_web/typings/comfy.d.ts +++ b/src_web/typings/comfy.d.ts @@ -1,20 +1,6 @@ -import type { LGraphGroup as TLGraphGroup, LGraphNode as TLGraphNode, IWidget, SerializedLGraphNode, LGraph as TLGraph, LGraphCanvas as TLGraphCanvas, LiteGraph as TLiteGraph } from "./litegraph.js"; +import type { LGraphGroup as TLGraphGroup, LGraphNode as TLGraphNode, IWidget, SerializedLGraphNode, LGraph as TLGraph, LGraphCanvas as TLGraphCanvas, LiteGraph as TLiteGraph, INodeSlot } from "@litegraph/litegraph.js"; import type {Constructor, SerializedGraph} from './index.js'; -declare global { - const LiteGraph: typeof TLiteGraph; - const LGraph: typeof TLGraph; - const LGraphNode: typeof TLGraphNode; - const LGraphCanvas: typeof TLGraphCanvas; - const LGraphGroup: typeof TLGraphGroup; - interface Window { - // Used in the common/comfyui_shim to determine if we're in the app or not. - comfyAPI: { - // So much more stuffed in here, add as needed. - [key: string]: any; - } - } -} export type getPngMetadata = (file: File | Blob) => { workflow?: string; prompt?: string }; export type getWebpMetadata = (file: File | Blob) => { @@ -58,7 +44,7 @@ export interface ComfyWidget extends IWidget { } export interface ComfyGraphNode extends TLGraphNode { - getExtraMenuOptions: (node: TLGraphNode, options: ContextMenuItem[]) => void; + // getExtraMenuOptions: (node: TLGraphNode, options: ContextMenuItem[]) => void; onExecuted(message: any): void; } @@ -73,7 +59,7 @@ export interface ComfyNodeConstructor extends Constructor { static comfyClass: string; } -export type NodeMode = 0|1|2|3|4|undefined; +// export type NodeMode = 0|1|2|3|4|undefined; export interface ComfyExtension { diff --git a/src_web/typings/litegraph.d.ts b/src_web/typings/litegraph.d.ts index fd2abf1..43b21ac 100644 --- a/src_web/typings/litegraph.d.ts +++ b/src_web/typings/litegraph.d.ts @@ -1,1748 +1,87 @@ -// Type definitions for litegraph.js 0.7.0 -// Project: litegraph.js -// Definitions by: NateScarlet +/** + * This used to clean up the old LiteGraph types but now that we're importing the actual types + * from https://github.com/Comfy-Org/litegraph.js we just use this to declare the globally available + * code when inside ComfyUI; as well as any augmentations we do want/need that aren't in the actual + * LiteGraph types, for whatever reason. + * + * Notes: + * - Augmentations marked @rgthree are added specifically for rgthree-comfy. + * - Augmentations marked @ComfyUI_frontend are from ComfyUI_frontend and yet to be imported. + * https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/src/types/litegraph-augmentation.d.ts + */ +import type { + LGraphGroup as TLGraphGroup, + LGraphNode as TLGraphNode, + LGraph as TLGraph, + LGraphCanvas as TLGraphCanvas, + LiteGraph as TLiteGraph, +} from "@litegraph/litegraph.js"; -export type Vector2 = [number, number]; -export type Vector4 = [number, number, number, number]; -export type widgetTypes = - | "number" - | "slider" - | "combo" - | "text" - | "toggle" - | "button"; -export type SlotShape = - | typeof LiteGraph.BOX_SHAPE - | typeof LiteGraph.CIRCLE_SHAPE - | typeof LiteGraph.ARROW_SHAPE - | typeof LiteGraph.SQUARE_SHAPE - | number; // For custom shapes +declare global { + const LiteGraph: typeof TLiteGraph; + const LGraph: typeof TLGraph; + const LGraphNode: typeof TLGraphNode; + const LGraphCanvas: typeof TLGraphCanvas; + const LGraphGroup: typeof TLGraphGroup; + interface Window { + // Used in the common/comfyui_shim to determine if we're in the app or not. + comfyAPI: { + // So much more stuffed in here, add as needed. + [key: string]: any; + }; + } +} -/** https://github.com/jagenjo/litegraph.js/tree/master/guides#node-slots */ -export interface INodeSlot { - name: string; - type: string | -1; - label?: string; - dir?: - | typeof LiteGraph.UP - | typeof LiteGraph.RIGHT - | typeof LiteGraph.DOWN - | typeof LiteGraph.LEFT; - color_on?: string; - color_off?: string; - shape?: SlotShape; - locked?: boolean; - nameLocked?: boolean; - pos?: Vector2; +declare module "@litegraph/litegraph.js" { + interface INodeSlot { // @rgthree hidden?: boolean; - // @rgthree + + // @rgthree - Used to "disable" an input/output. Used in PowerPrompt to disallow connecting + // an output if there's no optional corresponding input (since, that would just break). disabled?: boolean; - // @rgthree - Found this checked in getSlotMenuOptions default. - removable?: boolean; + // @rgthree - A status we put on some nodes so we can draw things around it. - rgthree_status?: 'WARN' | 'ERROR'; -} + rgthree_status?: "WARN" | "ERROR"; + } -export interface INodeInputSlot extends INodeSlot { - link: LLink["id"] | null; - // @rgthree - add comfy widget info - widget?: { - name: string; - } -} - -export interface INodeOutputSlot extends INodeSlot { - links: LLink["id"][] | null; -} - -export type WidgetCallback = ( - this: T, - value: T["value"], - graphCanvas: LGraphCanvas, - node: LGraphNode, - pos: Vector2, - event?: MouseEvent -) => void; - -// @rgthree -export type WidgetComboCallback = ( - this: T, - value: T["value"][0], - graphCanvas: LGraphCanvas, - node: LGraphNode, - pos: Vector2, - event?: MouseEvent -) => void; - -// @rgthree -export type IWidgetOptions = { - y?: number; // ? - property?: string; - serialize?: boolean; // ComfyUI in app.js - forceInput?: boolean; // ComfyUI in app.js - defaultInput?: boolean; // ComfyUI in app.js -} - -// @rgthree -export type IWidgetToggleOptions = IWidgetOptions & { - on?: string; - off?: string; -} - -// @rgthree -export type IWidgetNumberOptions = IWidgetOptions & { - precision?: number; - max?: number; - min?: number; -} - -// @rgthree -export type IWidgetSliderOptions = IWidgetNumberOptions & { - slider_color?: string; - marker_color?: string; -} - - -// @rgthree -export type IWidgetComboOptions = IWidgetOptions & { - values?: string[] | ((widget: IComboWidget, node: LGraphNode) => string[]); -} - -export interface IWidget { - name: string | null; - // @rgthree - label?: string | null; - value: TValue; - options?: TOptions; - // @rgthree - extend to string for custom - type?: widgetTypes | string; - y?: number; - property?: string; - last_y?: number; - clicked?: boolean; - marker?: boolean; - disabled?: boolean; - callback?: WidgetCallback; - /** Called by `LGraphCanvas.drawNodeWidgets` */ - draw?( - ctx: CanvasRenderingContext2D, - node: LGraphNode, - width: number, - posY: number, - height: number - ): void; - /** - * Called by `LGraphCanvas.processNodeWidgets` - * https://github.com/jagenjo/litegraph.js/issues/76 - */ - mouse?( - event: MouseEvent, - pos: Vector2, - node: LGraphNode - ): boolean; - /** Called by `LGraphNode.computeSize` */ - computeSize?(width: number): [number, number]; - // @rgthree - make optional, since it is in the code. - serializeValue?(serializedNode: SerializedLGraphNode, widgetIndex: number): TValue; - // @rgthree - Checked in LGraphCanvas.prototype.processNodeWidgets, and figured I'd use it too. - width?: number; -} -export interface IButtonWidget extends IWidget { - type: "button"; -} -// @rgthree: adding options -export interface IToggleWidget extends IWidget { - type: "toggle"; -} -// @rgthree: adding options -export interface ISliderWidget extends IWidget { - type: "slider"; -} -// @rgthree: adding options -export interface INumberWidget extends IWidget { - type: "number"; -} -// @rgthree: adding options -export interface IComboWidget extends IWidget { - value: T[0]; - type: "combo"; - callback?: WidgetComboCallback; -} - -export interface ITextWidget extends IWidget { - type: "text"; -} - -export interface IContextMenuItem { - // @rgthree - Make optional because, I guess it is? - content?: string; - value?: any; - callback?: ContextMenuEventListener; - /** Used as innerHTML for extra child element */ - title?: string; - disabled?: boolean; - has_submenu?: boolean; - submenu?: { - options: ContextMenuItem[]; - } & IContextMenuOptions; - className?: string; - // @rgthree - Added for menu_auto_nest - rgthree_originalValue?: IContextMenuItem; - // @rgthree - this was missing and passed through for getSlotMenuOptions default. - slot?: {input?: INodeInputSlot, output?: INodeOutputSlot}; -} -export interface IContextMenuOptions { - callback?: ContextMenuEventListener; - ignore_item_callbacks?: Boolean; - event?: MouseEvent | CustomEvent | AdjustedMouseEvent; - parentMenu?: ContextMenu|null; - autoopen?: boolean; - title?: string; - extra?: any; - // @rgthree - scale?: number; - // @rgthree - left?: number; - // @rgthree - top?: number; - // @rgthree - className?: string; - // @rgthree - Added for menu_auto_nest - rgthree_originalCallback?: ContextMenuEventListener; - // @rgthree - No idea since it's not documented, but we'll use it to pass data, like rgthree_doNotNest - extra?: any -} - -export type ContextMenuItem = IContextMenuItem | null; -export type ContextMenuEventListener = ( - value: ContextMenuItem, - options: IContextMenuOptions, - event: MouseEvent, - parentMenu: ContextMenu | undefined, - node: LGraphNode -) => boolean | void; - -export const LiteGraph: { - VERSION: number; - - CANVAS_GRID_SIZE: number; - - NODE_TITLE_HEIGHT: number; - NODE_TITLE_TEXT_Y: number; - NODE_SLOT_HEIGHT: number; - NODE_WIDGET_HEIGHT: number; - NODE_WIDTH: number; - NODE_MIN_WIDTH: number; - NODE_COLLAPSED_RADIUS: number; - NODE_COLLAPSED_WIDTH: number; - NODE_TITLE_COLOR: string; - NODE_TEXT_SIZE: number; - NODE_TEXT_COLOR: string; - NODE_SUBTEXT_SIZE: number; - NODE_DEFAULT_COLOR: string; - NODE_DEFAULT_BGCOLOR: string; - NODE_DEFAULT_BOXCOLOR: string; - NODE_DEFAULT_SHAPE: string; - // @rgthree - didn't exist. - NODE_BOX_OUTLINE_COLOR: string; - DEFAULT_SHADOW_COLOR: string; - DEFAULT_GROUP_FONT: number; - // @rgthree - Seems to have been missing. - NODE_BOX_OUTLINE_COLOR: string; - - WIDGET_BGCOLOR: string; - WIDGET_OUTLINE_COLOR: string; - WIDGET_TEXT_COLOR: string; - WIDGET_SECONDARY_TEXT_COLOR: string; - - LINK_COLOR: string; - EVENT_LINK_COLOR: string; - CONNECTING_LINK_COLOR: string; - - MAX_NUMBER_OF_NODES: number; //avoid infinite loops - DEFAULT_POSITION: Vector2; //default node position - VALID_SHAPES: ["default", "box", "round", "card"]; //,"circle" - - //shapes are used for nodes but also for slots - BOX_SHAPE: 1; - ROUND_SHAPE: 2; - CIRCLE_SHAPE: 3; - CARD_SHAPE: 4; - ARROW_SHAPE: 5; - GRID_SHAPE: 6; - - //enums - INPUT: 1; - OUTPUT: 2; - - EVENT: -1; //for outputs - ACTION: -1; //for inputs - - ALWAYS: 0; - ON_EVENT: 1; - NEVER: 2; - ON_TRIGGER: 3; - - UP: 1; - DOWN: 2; - LEFT: 3; - RIGHT: 4; - CENTER: 5; - - STRAIGHT_LINK: 0; - LINEAR_LINK: 1; - SPLINE_LINK: 2; - - NORMAL_TITLE: 0; - NO_TITLE: 1; - TRANSPARENT_TITLE: 2; - AUTOHIDE_TITLE: 3; - - node_images_path: string; - - // @rgthree. These just weren't there. Note, LiteGraph initializes these as an array, but - // ComfyUI overrides these to a string-keye'd object... ??? - slot_types_default_out: {[key: string]: string[]}; - slot_types_default_in: {[key: string]: string[]}; - - debug: boolean; - catch_exceptions: boolean; - throw_errors: boolean; - /** if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits */ - allow_scripts: boolean; - /** node types by string */ - registered_node_types: Record; - /** used for dropping files in the canvas */ - node_types_by_file_extension: Record; - /** node types by class name */ - Nodes: Record; - - /** used to add extra features to the search box */ - searchbox_extras: Record< - string, - { - data: { outputs: string[][]; title: string }; - desc: string; - type: string; - } - >; - - //@rgthree - isValidConnection(type: string|string[], type: string|string[]):boolean; - overlapBounding(a: Vector4, b: Vector4) : boolean; - - createNode(type: string): T; - /** Register a node class so it can be listed when the user wants to create a new one */ - registerNodeType(type: string, base: { new (title?: string): T }): void; - /** removes a node type from the system */ - unregisterNodeType(type: string): void; - /** Removes all previously registered node's types. */ - clearRegisteredTypes(): void; - /** - * Create a new node type by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. - * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. - * @param name node name with namespace (p.e.: 'math/sum') - * @param func - * @param param_types an array containing the type of every parameter, otherwise parameters will accept any type - * @param return_type string with the return type, otherwise it will be generic - * @param properties properties to be configurable - */ - wrapFunctionAsNode( - name: string, - func: (...args: any[]) => any, - param_types?: string[], - return_type?: string, - properties?: object - ): void; - - /** - * Adds this method to all node types, existing and to be created - * (You can add it to LGraphNode.prototype but then existing node types wont have it) - */ - addNodeMethod(name: string, func: (...args: any[]) => any): void; - - /** - * Create a node of a given type with a name. The node is not attached to any graph yet. - * @param type full name of the node class. p.e. "math/sin" - * @param name a name to distinguish from other nodes - * @param options to set options - */ - createNode( - type: string, - title: string, - options: object - ): T; - - /** - * Returns a registered node type with a given name - * @param type full name of the node class. p.e. "math/sin" - */ - getNodeType(type: string): LGraphNodeConstructor; - - /** - * Returns a list of node types matching one category - * @method getNodeTypesInCategory - * @param {String} category category name - * @param {String} filter only nodes with ctor.filter equal can be shown - * @return {Array} array with all the node classes - */ - getNodeTypesInCategory( - category: string, - filter: string - ): LGraphNodeConstructor[]; - - /** - * Returns a list with all the node type categories - * @method getNodeTypesCategories - * @param {String} filter only nodes with ctor.filter equal can be shown - * @return {Array} array with all the names of the categories - */ - getNodeTypesCategories(filter: string): string[]; - - /** debug purposes: reloads all the js scripts that matches a wildcard */ - reloadNodes(folder_wildcard: string): void; - - getTime(): number; - LLink: typeof LLink; - LGraph: typeof LGraph; - DragAndScale: typeof DragAndScale; - compareObjects(a: object, b: object): boolean; - distance(a: Vector2, b: Vector2): number; - colorToString(c: string): string; - isInsideRectangle( - x: number, - y: number, - left: number, - top: number, - width: number, - height: number - ): boolean; - growBounding(bounding: Vector4, x: number, y: number): Vector4; - isInsideBounding(p: Vector2, bb: Vector4): boolean; - hex2num(hex: string): [number, number, number]; - num2hex(triplet: [number, number, number]): string; - ContextMenu: typeof ContextMenu; - extendClass(target: A, origin: B): A & B; - getParameterNames(func: string): string[]; - // @rgthree - closeAllContextMenus(ref_window?: Window): void; -}; - -export type serializedLGraph< - TNode = ReturnType, - // https://github.com/jagenjo/litegraph.js/issues/74 - TLink = [number, number, number, number, number, string], - TGroup = ReturnType -> = { - last_node_id: LGraph["last_node_id"]; - last_link_id: LGraph["last_link_id"]; - nodes: TNode[]; - links: TLink[]; - groups: TGroup[]; - config: LGraph["config"]; - version: typeof LiteGraph.VERSION; -}; - -export declare class LGraph { - static supported_types: string[]; - static STATUS_STOPPED: 1; - static STATUS_RUNNING: 2; - - constructor(o?: object); - - filter: string; - catch_errors: boolean; - /** custom data */ - config: object; - elapsed_time: number; - fixedtime: number; - fixedtime_lapse: number; - globaltime: number; - inputs: any; - iteration: number; - last_link_id: number; - last_node_id: number; - last_update_time: number; - links: Record; - list_of_graphcanvas: LGraphCanvas[]; - outputs: any; - runningtime: number; - starttime: number; - status: typeof LGraph.STATUS_RUNNING | typeof LGraph.STATUS_STOPPED; - - // @rgthree, remove private; it's not really private b/c it's javascript. - _nodes: LGraphNode[]; - // @rgthree, remove private; it's not really private b/c it's javascript. - _groups: LGraphGroup[]; - private _nodes_by_id: Record; - /** nodes that are executable sorted in execution order */ - private _nodes_executable: - | (LGraphNode & { onExecute: NonNullable }[]) - | null; - /** nodes that contain onExecute */ - private _nodes_in_order: LGraphNode[]; - private _version: number; - - getSupportedTypes(): string[]; - /** Removes all nodes from this graph */ - clear(): void; - /** Attach Canvas to this graph */ - attachCanvas(graphCanvas: LGraphCanvas): void; - /** Detach Canvas to this graph */ - detachCanvas(graphCanvas: LGraphCanvas): void; - /** - * Starts running this graph every interval milliseconds. - * @param interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate - */ - start(interval?: number): void; - /** Stops the execution loop of the graph */ - stop(): void; - /** - * Run N steps (cycles) of the graph - * @param num number of steps to run, default is 1 - */ - runStep(num?: number, do_not_catch_errors?: boolean): void; - /** - * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than - * nodes with only inputs. - */ - updateExecutionOrder(): void; - /** This is more internal, it computes the executable nodes in order and returns it */ - computeExecutionOrder(only_onExecute: boolean, set_level: any): T; - /** - * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. - * It doesn't include the node itself - * @return an array with all the LGraphNodes that affect this node, in order of execution - */ - getAncestors(node: LGraphNode): LGraphNode[]; - /** - * Positions every node in a more readable manner - */ - arrange(margin?: number,layout?: string): void; - /** - * Returns the amount of time the graph has been running in milliseconds - * @return number of milliseconds the graph has been running - */ - getTime(): number; - - /** - * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant - * @return number of milliseconds the graph has been running - */ - getFixedTime(): number; - - /** - * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct - * if the nodes are using graphical actions - * @return number of milliseconds it took the last cycle - */ - getElapsedTime(): number; - /** - * Sends an event to all the nodes, useful to trigger stuff - * @param eventName the name of the event (function to be called) - * @param params parameters in array format - */ - sendEventToAllNodes(eventName: string, params: any[], mode?: any): void; - - sendActionToCanvas(action: any, params: any[]): void; - /** - * Adds a new node instance to this graph - * @param node the instance of the node - */ - add(node: LGraphNode, skip_compute_order?: boolean): void; - /** - * Called when a new node is added - * @param node the instance of the node - */ - onNodeAdded(node: LGraphNode): void; - /** Removes a node from the graph */ - remove(node: LGraphNode): void; - /** - * Returns a node by its id. - * @rgthree - make id options/nullable. - */ - getNodeById(id?: number|null): LGraphNode | undefined; - /** - * Returns a list of nodes that matches a class - * @param classObject the class itself (not an string) - * @return a list with all the nodes of this type - */ - findNodesByClass( - classObject: LGraphNodeConstructor - ): T[]; - /** - * Returns a list of nodes that matches a type - * @param type the name of the node type - * @return a list with all the nodes of this type - */ - findNodesByType(type: string): T[]; - /** - * Returns the first node that matches a name in its title - * @param title the name of the node to search - * @return the node or null - */ - findNodeByTitle(title: string): T | null; - /** - * Returns a list of nodes that matches a name - * @param title the name of the node to search - * @return a list with all the nodes with this name - */ - findNodesByTitle(title: string): T[]; - /** - * Returns the top-most node in this position of the canvas - * @param x the x coordinate in canvas space - * @param y the y coordinate in canvas space - * @param nodes_list a list with all the nodes to search from, by default is all the nodes in the graph - * @return the node at this position or null - */ - getNodeOnPos( - x: number, - y: number, - node_list?: LGraphNode[], - margin?: number - ): T | null; - /** - * Returns the top-most group in that position - * @param x the x coordinate in canvas space - * @param y the y coordinate in canvas space - * @return the group or null - */ - getGroupOnPos(x: number, y: number): LGraphGroup | null; - - onAction(action: any, param: any): void; - trigger(action: any, param: any): void; - /** Tell this graph it has a global graph input of this type */ - addInput(name: string, type: string, value?: any): void; - /** Assign a data to the global graph input */ - setInputData(name: string, data: any): void; - /** Returns the current value of a global graph input */ - getInputData(name: string): T; - /** Changes the name of a global graph input */ - renameInput(old_name: string, name: string): false | undefined; - /** Changes the type of a global graph input */ - changeInputType(name: string, type: string): false | undefined; - /** Removes a global graph input */ - removeInput(name: string): boolean; - /** Creates a global graph output */ - addOutput(name: string, type: string, value: any): void; - /** Assign a data to the global output */ - setOutputData(name: string, value: string): void; - /** Returns the current value of a global graph output */ - getOutputData(name: string): T; - - /** Renames a global graph output */ - renameOutput(old_name: string, name: string): false | undefined; - /** Changes the type of a global graph output */ - changeOutputType(name: string, type: string): false | undefined; - /** Removes a global graph output */ - removeOutput(name: string): boolean; - triggerInput(name: string, value: any): void; - setCallback(name: string, func: (...args: any[]) => any): void; - beforeChange(info?: LGraphNode): void; - afterChange(info?: LGraphNode): void; - connectionChange(node: LGraphNode): void; - /** returns if the graph is in live mode */ - isLive(): boolean; - /** clears the triggered slot animation in all links (stop visual animation) */ - clearTriggeredSlots(): void; - /* Called when something visually changed (not the graph!) */ - change(): void; - setDirtyCanvas(fg: boolean, bg?: boolean): void; - /** Destroys a link */ - removeLink(link_id: number): void; - /** Creates a Object containing all the info about this graph, it can be serialized */ - serialize(): T; - /** - * Configure a graph from a JSON string - * @param data configure a graph from a JSON string - * @returns if there was any error parsing - */ - configure(data: object, keep_old?: boolean): boolean | undefined; - load(url: string): void; -} - -export type SerializedLLink = [number, string, number, number, number, number]; -export declare class LLink { - id: number; - type: string; - origin_id: number; - origin_slot: number; - target_id: number; - target_slot: number; - constructor( - id: number, - type: string, - origin_id: number, - origin_slot: number, - target_id: number, - target_slot: number - ); - configure(o: LLink | SerializedLLink): void; - serialize(): SerializedLLink; - // @rgthree - color?: string; -} - -export type SerializedLGraphNode = { - id: T["id"]; - type: T["type"]; - pos: T["pos"]; - size: T["size"]; - flags: T["flags"]; - mode: T["mode"]; - inputs: T["inputs"]; - outputs: T["outputs"]; - title: T["title"]; - properties: T["properties"]; - widgets_values?: IWidget["value"][]; -}; - -/** https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#lgraphnode */ -export declare class LGraphNode { - - // @rgthree added - findInputSlotByType(type: string, returnObj?: boolean, preferFreeSlot?: boolean, doNotUseOccupied?: boolean): number - findOutputSlotByType(type: string, returnObj?: boolean, preferFreeSlot?: boolean, doNotUseOccupied?: boolean): number - onShowCustomPanelInfo(panel: HTMLElement): void; - onDblClick?(event: AdjustedMouseEvent, pos: Vector2, canvas: LGraphCanvas): void; - inResizeCorner(x: number, y:number) : boolean; - onWidgetChanged?(widgetName: string, widgetValue: any, oldWidgetValue: any, widget: IWidget): void; - - // end @rgthree added - - static title_color?: string; - static title: string; - static type: null | string; - static widgets_up: boolean; - constructor(title?: string); - - title: string; - // @rgthree - made undefined since ComfyNode does it (even through LiteGraph does not..) - type?: null | string; - size: Vector2; - graph: null | LGraph; - graph_version: number; - pos: Vector2; - is_selected: boolean; - mouseOver: boolean; - // @rgthree - missing. - block_delete: boolean; - - id: number; - - widgets: IWidget[]; - //inputs available: array of inputs - inputs: INodeInputSlot[]; - outputs: INodeOutputSlot[]; - connections: any[]; - - // @rgthree - _collapsed_width?: number; - - //local data - properties: Record; - properties_info: any[]; - - flags: Partial<{ - collapsed: boolean - // @rgthree - allow_interaction: boolean; - // @rgthree - pinned: boolean; - }>; - - color: string; - bgcolor: string; - boxcolor: string; - shape: - | typeof LiteGraph.BOX_SHAPE - | typeof LiteGraph.ROUND_SHAPE - | typeof LiteGraph.CIRCLE_SHAPE - | typeof LiteGraph.CARD_SHAPE - | typeof LiteGraph.ARROW_SHAPE; - - serialize_widgets: boolean; - skip_list: boolean; - - /** Used in `LGraphCanvas.onMenuNodeMode` */ - mode?: - | typeof LiteGraph.ON_EVENT - | typeof LiteGraph.ON_TRIGGER - | typeof LiteGraph.NEVER - | typeof LiteGraph.ALWAYS - | 4; // Comfy App "Bypass" - - /** If set to true widgets do not start after the slots */ - widgets_up: boolean; - /** widgets start at y distance from the top of the node */ - widgets_start_y: number; - /** if you render outside the node, it will be clipped */ - clip_area: boolean; - /** if set to false it wont be resizable with the mouse */ - resizable: boolean; - /** slots are distributed horizontally */ - horizontal: boolean; - /** if true, the node will show the bgcolor as 'red' */ - has_errors?: boolean; - - // @rgthree - setSize(size: Vector2): void; - onResize?(size: Vector2): void; - onInputClick(slot: number, event: MouseEvent): void; - onOutputClick(slot: number, event: MouseEvent): void; - getConnectionPos(isInput: boolean, slotNumber: number, out: Vector2): Vector2; - - /** configure a node from an object containing the serialized info */ - configure(info: SerializedLGraphNode): void; - /** serialize the content */ - serialize(): SerializedLGraphNode; - /** Creates a clone of this node */ - clone(): this; - /** serialize and stringify */ - toString(): string; - /** get the title string */ - getTitle(): string; - /** sets the value of a property */ - setProperty(name: string, value: any): void; - /** sets the output data */ - setOutputData(slot: number, data: any): void; - /** sets the output data */ - setOutputDataType(slot: number, type: string): void; - /** - * Retrieves the input data (data traveling through the connection) from one slot - * @param slot - * @param force_update if set to true it will force the connected node of this slot to output data into this link - * @return data or if it is not connected returns undefined - */ - getInputData(slot: number, force_update?: boolean): T; - /** - * Retrieves the input data type (in case this supports multiple input types) - * @param slot - * @return datatype in string format - */ - getInputDataType(slot: number): string; - /** - * Retrieves the input data from one slot using its name instead of slot number - * @param slot_name - * @param force_update if set to true it will force the connected node of this slot to output data into this link - * @return data or if it is not connected returns null - */ - getInputDataByName(slot_name: string, force_update?: boolean): T; - /** tells you if there is a connection in one input slot */ - isInputConnected(slot: number): boolean; - /** tells you info about an input connection (which node, type, etc) */ - getInputInfo( - slot: number - ): { link: number; name: string; type: string | 0 } | null; - /** returns the node connected in the input slot */ - getInputNode(slot: number): LGraphNode | null; - /** returns the value of an input with this name, otherwise checks if there is a property with that name */ - getInputOrProperty(name: string): T; - /** tells you the last output data that went in that slot */ - getOutputData(slot: number): T | null; - /** tells you info about an output connection (which node, type, etc) */ - getOutputInfo( - slot: number - ): { name: string; type: string; links: number[] } | null; - /** tells you if there is a connection in one output slot */ - isOutputConnected(slot: number): boolean; - /** tells you if there is any connection in the output slots */ - isAnyOutputConnected(): boolean; - /** retrieves all the nodes connected to this output slot */ - getOutputNodes(slot: number): LGraphNode[]; - /** Triggers an event in this node, this will trigger any output with the same name */ - trigger(action: string, param: any): void; - /** - * Triggers an slot event in this node - * @param slot the index of the output slot - * @param param - * @param link_id in case you want to trigger and specific output link in a slot - */ - triggerSlot(slot: number, param: any, link_id?: number): void; - /** - * clears the trigger slot animation - * @param slot the index of the output slot - * @param link_id in case you want to trigger and specific output link in a slot - */ - clearTriggeredSlot(slot: number, link_id?: number): void; - /** - * add a new property to this node - * @param name - * @param default_value - * @param type string defining the output type ("vec3","number",...) - * @param extra_info this can be used to have special properties of the property (like values, etc) - */ - addProperty( - name: string, - default_value: any, - type: string, - extra_info?: object - ): T; - /** - * add a new output slot to use in this node - * @param name - * @param type string defining the output type ("vec3","number",...) - * @param extra_info this can be used to have special properties of an output (label, special color, position, etc) - */ - addOutput( - name: string, - type: string | -1, - extra_info?: Partial - ): INodeOutputSlot; - /** - * add a new output slot to use in this node - * @param array of triplets like [[name,type,extra_info],[...]] - */ - addOutputs( - array: [string, string | -1, Partial | undefined][] - ): void; - /** remove an existing output slot */ - removeOutput(slot: number): void; - /** - * add a new input slot to use in this node - * @param name - * @param type string defining the input type ("vec3","number",...), it its a generic one use 0 - * @param extra_info this can be used to have special properties of an input (label, color, position, etc) - */ - addInput( - name: string, - type: string | -1, - extra_info?: Partial - ): INodeInputSlot; - /** - * add several new input slots in this node - * @param array of triplets like [[name,type,extra_info],[...]] - */ - addInputs( - array: [string, string | -1, Partial | undefined][] - ): void; - /** remove an existing input slot */ - removeInput(slot: number): void; - /** - * add an special connection to this node (used for special kinds of graphs) - * @param name - * @param type string defining the input type ("vec3","number",...) - * @param pos position of the connection inside the node - * @param direction if is input or output - */ - addConnection( - name: string, - type: string, - pos: Vector2, - direction: string - ): { - name: string; - type: string; - pos: Vector2; - direction: string; - links: null; - }; - setValue(v: any): void; - /** computes the size of a node according to its inputs and output slots */ - computeSize(out?: Vector2): [number, number]; - /** - * https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#node-widgets - * @return created widget - */ - addWidget( - type: T["type"], - name: string, - value: T["value"], - // @rgthree - callback?: T["callback"] | string,//WidgetCallback | string, - options?: T["options"] - ): T; - - addCustomWidget(customWidget: T): T; - - /** - * returns the bounding of the object, used for rendering purposes - * @return [x, y, width, height] - */ - getBounding(): Vector4; - /** checks if a point is inside the shape of a node */ - isPointInside( - x: number, - y: number, - margin?: number, - skipTitle?: boolean - ): boolean; - /** checks if a point is inside a node slot, and returns info about which slot */ - getSlotInPosition( - x: number, - y: number - ): { - input?: INodeInputSlot; - output?: INodeOutputSlot; - slot: number; - link_pos: Vector2; - }; - /** - * returns the input slot with a given name (used for dynamic slots), -1 if not found - * @param name the name of the slot - * @return the slot (-1 if not found) - */ - findInputSlot(name: string): number; - /** - * returns the output slot with a given name (used for dynamic slots), -1 if not found - * @param name the name of the slot - * @return the slot (-1 if not found) - */ - findOutputSlot(name: string): number; - /** - * connect this node output to the input of another node - * @param slot (could be the number of the slot or the string with the name of the slot) - * @param targetNode the target node - * @param targetSlot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) - * @return {Object} the link_info is created, otherwise null - */ - connect( - slot: number | string, - targetNode: LGraphNode, - targetSlot: number | string - ): T | null; - - connectByTypeOutput( - slot: number | string, - sourceNode: LGraphNode, - sourceSlotType: string, - optsIn: string - ): T | null; - - connectByType( - slot: number | string, - sourceNode: LGraphNode, - sourceSlotType: string, - optsIn: string - ): T | null; - - - /** - * disconnect one output to an specific node - * @param slot (could be the number of the slot or the string with the name of the slot) - * @param target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] - * @return if it was disconnected successfully - */ - disconnectOutput(slot: number | string, targetNode?: LGraphNode): boolean; - /** - * disconnect one input - * @param slot (could be the number of the slot or the string with the name of the slot) - * @return if it was disconnected successfully - */ - disconnectInput(slot: number | string): boolean; - /** - * returns the center of a connection point in canvas coords - * @param is_input true if if a input slot, false if it is an output - * @param slot (could be the number of the slot or the string with the name of the slot) - * @param out a place to store the output, to free garbage - * @return the position - **/ - getConnectionPos( - is_input: boolean, - slot: number | string, - out?: Vector2 - ): Vector2; - /** Force align to grid */ - alignToGrid(): void; - /** Console output */ - trace(msg: string): void; - /** Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ - setDirtyCanvas(fg: boolean, bg?: boolean): void; - loadImage(url: string): void; - /** Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ - captureInput(v: any): void; - /** Collapse the node to make it smaller on the canvas */ - collapse(force: boolean): void; - /** Forces the node to do not move or realign on Z */ - pin(v?: boolean): void; - localToScreen(x: number, y: number, graphCanvas: LGraphCanvas): Vector2; - - // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-appearance - onDrawBackground?( - ctx: CanvasRenderingContext2D, - // @rgthree fixed - canvas: LGraphCanvas - ): void; - onDrawForeground?( - ctx: CanvasRenderingContext2D, - // @rgthree fixed - canvas: LGraphCanvas - ): void; - - // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-behaviour - onMouseDown?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseMove?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseUp?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseEnter?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseLeave?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onKey?(event: KeyboardEvent, pos: Vector2, graphCanvas: LGraphCanvas): void; - // @rgthree - onKeyDown?(event: KeyboardEvent): void; - // @rgthree - onKeyUp?(event: KeyboardEvent): void; - - onExecuted(message: any): void; - /** Called by `LGraphNode.createNode` */ - onNodeCreated?(): void; - /** Called by `LGraphCanvas.selectNodes` */ - onSelected?(): void; - /** Called by `LGraphCanvas.deselectNode` */ - onDeselected?(): void; - /** Called by `LGraph.runStep` `LGraphNode.getInputData` */ - onExecute?(): void; - /** Called by `LGraph.serialize` */ - onSerialize?(o: SerializedLGraphNode): void; - /** Called by `LGraph.configure` */ - onConfigure?(o: SerializedLGraphNode): void; - /** - * when added to graph (warning: this is called BEFORE the node is configured when loading) - * Called by `LGraph.add` - */ - onAdded?(graph: LGraph): void; - /** - * when removed from graph - * Called by `LGraph.remove` `LGraph.clear` - */ - onRemoved?(): void; - /** - * if returns false the incoming connection will be canceled - * Called by `LGraph.connect` - * @param inputIndex target input slot number - * @param outputType type of output slot - * @param outputSlot output slot object - * @param outputNode node containing the output - * @param outputIndex index of output slot - */ - onConnectInput?( - inputIndex: number, - outputType: INodeOutputSlot["type"], - outputSlot: INodeOutputSlot, - outputNode: LGraphNode, - outputIndex: number - ): boolean; - /** - * if returns false the incoming connection will be canceled - * Called by `LGraph.connect` - * @param outputIndex target output slot number - * @param inputType type of input slot - * @param inputSlot input slot object - * @param inputNode node containing the input - * @param inputIndex index of input slot - */ - onConnectOutput?( - outputIndex: number, - inputType: INodeInputSlot["type"], - inputSlot: INodeInputSlot, - inputNode: LGraphNode, - inputIndex: number - ): boolean; - - /** - * Called just before connection (or disconnect - if input is linked). - * A convenient place to switch to another input, or create new one. - * This allow for ability to automatically add slots if needed - * @param inputIndex - * @return selected input slot index, can differ from parameter value - */ - onBeforeConnectInput?( - inputIndex: number - ): number; - - /** a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info or output_info ) */ - onConnectionsChange( - type: number, - slotIndex: number, - isConnected: boolean, - link: LLink, - // @rgthree - Make it INodeSlot instead of union - ioSlot: INodeSlot - ): void; - - /** - * if returns false, will abort the `LGraphNode.setProperty` - * Called when a property is changed - * @param property - * @param value - * @param prevValue - */ - onPropertyChanged?(property: string, value: any, prevValue: any): void | boolean; - - /** Called by `LGraphCanvas.processContextMenu` */ - getMenuOptions?(graphCanvas: LGraphCanvas): ContextMenuItem[]; - // @rgthree. This is fixed because the INodeSlot is wrong and, also, null can be returned to not trigger a menu. - // getSlotMenuOptions?(slot: INodeSlot): ContextMenuItem[]; - getSlotMenuOptions(slot: {input?: INodeInputSlot, output?: INodeOutputSlot}): ContextMenuItem[] | null; - - getExtraMenuOptions?(canvas: LGraphCanvas, options: ContextMenuItem[]): void; - - // @rgthree - Called in LiteGraph.core when the properties panel is constructed. - onShowCustomPanelInfo(panel: HTMLElement): void; -} - -export type LGraphNodeConstructor = { - new (): T; - - // @rgthree - title_mode?: - typeof LiteGraph.NORMAL_TITLE | - typeof LiteGraph.TRANSPARENT_TITLE | - typeof LiteGraph.AUTOHIDE_TITLE | - typeof LiteGraph.NO_TITLE; - title: string; - category: string; - type: string; - comfyClass?: string; -}; - -export type SerializedLGraphGroup = { - title: LGraphGroup["title"]; - bounding: LGraphGroup["_bounding"]; - color: LGraphGroup["color"]; - font: LGraphGroup["font"]; -}; -export declare class LGraphGroup { - title: string; - // @rgthree - mark unprivate - _bounding: Vector4; - // @rgthree - updated to make optional because it seems to be so. - color?: string|null; - font: string; - // @rgthree - _nodes: LGraphNode[]; - // @rgthree - _pos: Vector2; - // @rgthree - _size: Vector2; - // @rgthree - graph: LGraph; - // @rgthree - apparently it is available? - size: Vector2; - // @rgthree - apparently it is available? - pos: Vector2; - - - configure(o: SerializedLGraphGroup): void; - serialize(): SerializedLGraphGroup; - move(deltaX: number, deltaY: number, ignoreNodes?: boolean): void; - recomputeInsideNodes(): void; - isPointInside: LGraphNode["isPointInside"]; - setDirtyCanvas: LGraphNode["setDirtyCanvas"]; -} - -export declare class DragAndScale { - constructor(element?: HTMLElement, skipEvents?: boolean); - offset: [number, number]; - scale: number; - max_scale: number; - min_scale: number; - onredraw: Function | null; - enabled: boolean; - last_mouse: Vector2; - element: HTMLElement | null; - visible_area: Vector4; - bindEvents(element: HTMLElement): void; - computeVisibleArea(): void; - onMouse(e: MouseEvent): void; - toCanvasContext(ctx: CanvasRenderingContext2D): void; - convertOffsetToCanvas(pos: Vector2): Vector2; - convertCanvasToOffset(pos: Vector2): Vector2; - mouseDrag(x: number, y: number): void; - changeScale(value: number, zooming_center?: Vector2): void; - changeDeltaScale(value: number, zooming_center?: Vector2): void; - reset(): void; -} - -// @rgthree. -interface CanvasDivDialog extends HTMLDivElement { - close: () => void; - modified: () => void; - is_modified: boolean; -} - -/** - * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. - * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked - * - * @param canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) - * @param graph - * @param options { skip_rendering, autoresize } - */ -export declare class LGraphCanvas { - static node_colors: Record< - string, - { - color: string; - bgcolor: string; - groupcolor: string; - } - >; - static link_type_colors: Record; - static gradients: object; - static search_limit: number; - - static getFileExtension(url: string): string; - static decodeHTML(str: string): string; - - static onMenuCollapseAll(): void; - static onMenuNodeEdit(): void; - static onShowPropertyEditor( - item: any, - options: any, - e: any, - menu: any, - node: any - ): void; - /** Create menu for `Add Group` */ - static onGroupAdd: ContextMenuEventListener; - /** Create menu for `Add Node` */ - static onMenuAdd: ContextMenuEventListener; - static showMenuNodeOptionalInputs: ContextMenuEventListener; - static showMenuNodeOptionalOutputs: ContextMenuEventListener; - static onShowMenuNodeProperties: ContextMenuEventListener; - static onResizeNode: ContextMenuEventListener; - static onMenuNodeCollapse: ContextMenuEventListener; - static onMenuNodePin: ContextMenuEventListener; - static onMenuNodeMode: ContextMenuEventListener; - static onMenuNodeColors: ContextMenuEventListener; - static onMenuNodeShapes: ContextMenuEventListener; - static onMenuNodeRemove: ContextMenuEventListener; - static onMenuNodeClone: ContextMenuEventListener; - - // @rgthree - static onShowPropertyEditor: ContextMenuEventListener; - - constructor( - canvas: HTMLCanvasElement | string, - graph?: LGraph, - options?: { - skip_render?: boolean; - autoresize?: boolean; - } - ); - - // @rgthree. This was "HTMLCanvasElement" but that is just wrong... it's LGraphCanvas - static active_canvas: LGraphCanvas; - - // @rgthree - pointer_is_down: boolean; - - allow_dragcanvas: boolean; - allow_dragnodes: boolean; - /** allow to control widgets, buttons, collapse, etc */ - allow_interaction: boolean; - /** allows to change a connection with having to redo it again */ - allow_reconnect_links: boolean; - /** allow selecting multi nodes without pressing extra keys */ - multi_select: boolean; - /** No effect */ - allow_searchbox: boolean; - always_render_background: boolean; - autoresize?: boolean; - background_image: string; - bgcanvas: HTMLCanvasElement; - bgctx: CanvasRenderingContext2D; - canvas: HTMLCanvasElement; - canvas_mouse: Vector2; - // @rgthree - Looks like this is to replace canvas_mouse. - graph_mouse: Vector2; - clear_background: boolean; - // connecting_node: LGraphNode | null; - // // @rgthree - for overriding. - // _connecting_node: LGraphNode | null; - // @rgthree - // connecting_input: INodeInputSlot | null; - // // @rgthree - // connecting_output: INodeOutputSlot | null; - // // @rgthree - // connecting_slot: number; - // // @rgthree - // connecting_pos: Vector2 | null; - // @rgthree - for some reason, the new comfyUI update renamed connecting_node to connecting_links (maybe) - connecting_links: { - node: LGraphNode, input?: INodeInputSlot, output?: INodeOutputSlot, pos: Vector2, slot: number - }[] | null; - _connecting_links: this['connecting_links'] | null; - - connections_width: number; - ctx: CanvasRenderingContext2D; - current_node: LGraphNode | null; - default_connection_color: { - input_off: string; - input_on: string; - output_off: string; - output_on: string; - }; - default_link_color: string; - dirty_area: Vector4 | null; - dirty_bgcanvas?: boolean; - dirty_canvas?: boolean; - drag_mode: boolean; - dragging_canvas: boolean; - dragging_rectangle: Vector4 | null; - // @rgthree; mark undefined. - // It doesn't look like this should ever be undefined.. but something changed in Comfy and folks - // reported https://github.com/rgthree/rgthree-comfy/issues/71. I couldn't reproduce, but we can - // handle it. - ds?: DragAndScale; - /** used for transition */ - editor_alpha: number; - filter: any; - fps: number; - frame: number; - graph: LGraph; - highlighted_links: Record; - highquality_render: boolean; - inner_text_font: string; - is_rendering: boolean; - last_draw_time: number; - last_mouse: Vector2; - /** - * Possible duplicated with `last_mouse` - * https://github.com/jagenjo/litegraph.js/issues/70 - */ - last_mouse_position: Vector2; - /** Timestamp of last mouse click, defaults to 0 */ - last_mouseclick: number; - links_render_mode: - | typeof LiteGraph.STRAIGHT_LINK - | typeof LiteGraph.LINEAR_LINK - | typeof LiteGraph.SPLINE_LINK; - live_mode: boolean; - node_capturing_input: LGraphNode | null; - node_dragged: LGraphNode | null; - node_in_panel: LGraphNode | null; - node_over: LGraphNode | null; - node_title_color: string; - node_widget: [LGraphNode, IWidget] | null; - /** Called by `LGraphCanvas.drawBackCanvas` */ - onDrawBackground: - | ((ctx: CanvasRenderingContext2D, visibleArea: Vector4) => void) - | null; - /** Called by `LGraphCanvas.drawFrontCanvas` */ - onDrawForeground: - | ((ctx: CanvasRenderingContext2D, visibleArea: Vector4) => void) - | null; - onDrawOverlay: ((ctx: CanvasRenderingContext2D) => void) | null; - /** Called by `LGraphCanvas.processMouseDown` */ - onMouse: ((event: MouseEvent) => boolean) | null; - /** Called by `LGraphCanvas.drawFrontCanvas` and `LGraphCanvas.drawLinkTooltip` */ - onDrawLinkTooltip: ((ctx: CanvasRenderingContext2D, link: LLink, _this: this) => void) | null; - /** Called by `LGraphCanvas.selectNodes` */ - onNodeMoved: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.processNodeSelected` */ - onNodeSelected: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.deselectNode` */ - onNodeDeselected: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.processNodeDblClicked` */ - onShowNodePanel: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.processNodeDblClicked` */ - onNodeDblClicked: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.selectNodes` */ - onSelectionChange: ((nodes: Record) => void) | null; - /** Called by `LGraphCanvas.showSearchBox` */ - onSearchBox: - | (( - helper: Element, - value: string, - graphCanvas: LGraphCanvas - ) => string[]) - | null; - onSearchBoxSelection: - | ((name: string, event: MouseEvent, graphCanvas: LGraphCanvas) => void) - | null; - pause_rendering: boolean; - render_canvas_border: boolean; - render_collapsed_slots: boolean; - render_connection_arrows: boolean; - render_connections_border: boolean; - render_connections_shadows: boolean; - render_curved_connections: boolean; - render_execution_order: boolean; - render_only_selected: boolean; - render_shadows: boolean; - render_title_colored: boolean; - round_radius: number; - selected_group: null | LGraphGroup; - selected_group_resizing: boolean; - selected_nodes: Record; - show_info: boolean; - title_text_font: string; - /** set to true to render title bar with gradients */ - use_gradients: boolean; - visible_area: DragAndScale["visible_area"]; - visible_links: LLink[]; - visible_nodes: LGraphNode[]; - zoom_modify_alpha: boolean; - - /** clears all the data inside */ - clear(): void; - /** assigns a graph, you can reassign graphs to the same canvas */ - setGraph(graph: LGraph, skipClear?: boolean): void; - /** opens a graph contained inside a node in the current graph */ - openSubgraph(graph: LGraph): void; - /** closes a subgraph contained inside a node */ - closeSubgraph(): void; - /** assigns a canvas */ - setCanvas(canvas: HTMLCanvasElement, skipEvents?: boolean): void; - /** binds mouse, keyboard, touch and drag events to the canvas */ - bindEvents(): void; - /** unbinds mouse events from the canvas */ - unbindEvents(): void; - - /** - * this function allows to render the canvas using WebGL instead of Canvas2D - * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL - **/ - enableWebGL(): void; - - /** - * marks as dirty the canvas, this way it will be rendered again - * @param fg if the foreground canvas is dirty (the one containing the nodes) - * @param bg if the background canvas is dirty (the one containing the wires) - */ - setDirty(fg: boolean, bg?: boolean): void; - - /** - * Used to attach the canvas in a popup - * @return the window where the canvas is attached (the DOM root node) - */ - getCanvasWindow(): Window; - /** starts rendering the content of the canvas when needed */ - startRendering(): void; - /** stops rendering the content of the canvas (to save resources) */ - stopRendering(): void; - - processMouseDown(e: MouseEvent): boolean | undefined; - processMouseMove(e: MouseEvent): boolean | undefined; - processMouseUp(e: MouseEvent): boolean | undefined; - processMouseWheel(e: MouseEvent): boolean | undefined; - - /** returns true if a position (in graph space) is on top of a node little corner box */ - isOverNodeBox(node: LGraphNode, canvasX: number, canvasY: number): boolean; - /** returns true if a position (in graph space) is on top of a node input slot */ - isOverNodeInput( - node: LGraphNode, - canvasX: number, - canvasY: number, - slotPos: Vector2 - ): boolean; - - /** process a key event */ - processKey(e: KeyboardEvent): boolean | undefined; - - // @rgthree - added param - copyToClipboard(nodes: LGraphNode[]|{[key:number]:LGraphNode}): void; - pasteFromClipboard(): void; - processDrop(e: DragEvent): void; - checkDropItem(e: DragEvent): void; - processNodeDblClicked(n: LGraphNode): void; - processNodeSelected(n: LGraphNode, e: MouseEvent): void; - processNodeDeselected(node: LGraphNode): void; - - /** selects a given node (or adds it to the current selection) */ - selectNode(node: LGraphNode, add?: boolean): void; - /** selects several nodes (or adds them to the current selection) */ - selectNodes(nodes?: LGraphNode[], add?: boolean): void; - /** removes a node from the current selection */ - deselectNode(node: LGraphNode): void; - /** removes all nodes from the current selection */ - deselectAllNodes(): void; - /** deletes all nodes in the current selection from the graph */ - deleteSelectedNodes(): void; - - /** centers the camera on a given node */ - // @rgthree - narrow parameter - centerOnNode(node: {pos: Vector2, size: Vector2}): void; - /** changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom */ - setZoom(value: number, center: Vector2): void; - /** brings a node to front (above all other nodes) */ - bringToFront(node: LGraphNode): void; - /** sends a node to the back (below all other nodes) */ - sendToBack(node: LGraphNode): void; - /** checks which nodes are visible (inside the camera area) */ - computeVisibleNodes(nodes: LGraphNode[]): LGraphNode[]; - /** renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) */ - draw(forceFG?: boolean, forceBG?: boolean): void; - /** draws the front canvas (the one containing all the nodes) */ - drawFrontCanvas(): void; - /** draws some useful stats in the corner of the canvas */ - renderInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void; - /** draws the back canvas (the one containing the background and the connections) */ - drawBackCanvas(): void; - /** draws the given node inside the canvas */ - drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void; - /** draws graphic for node's slot */ - drawSlotGraphic(ctx: CanvasRenderingContext2D, pos: number[], shape: SlotShape, horizontal: boolean): void; - /** draws the shape of the given node in the canvas */ - drawNodeShape( - node: LGraphNode, - ctx: CanvasRenderingContext2D, - size: [number, number], - fgColor: string, - bgColor: string, - selected: boolean, - mouseOver: boolean - ): void; - /** draws every connection visible in the canvas */ - drawConnections(ctx: CanvasRenderingContext2D): void; - /** - * draws a link between two points - * @param a start pos - * @param b end pos - * @param link the link object with all the link info - * @param skipBorder ignore the shadow of the link - * @param flow show flow animation (for events) - * @param color the color for the link - * @param startDir the direction enum - * @param endDir the direction enum - * @param numSublines number of sublines (useful to represent vec3 or rgb) - **/ - renderLink( - a: Vector2, - b: Vector2, - link: object, - skipBorder: boolean, - flow: boolean, - color?: string, - startDir?: number, - endDir?: number, - numSublines?: number - ): void; - - computeConnectionPoint( - a: Vector2, - b: Vector2, - t: number, - startDir?: number, - endDir?: number - ): void; - - drawExecutionOrder(ctx: CanvasRenderingContext2D): void; - /** draws the widgets stored inside a node */ - drawNodeWidgets( - node: LGraphNode, - posY: number, - ctx: CanvasRenderingContext2D, - activeWidget: object - ): void; - /** process an event on widgets */ - processNodeWidgets( - node: LGraphNode, - pos: Vector2, - event: Event, - activeWidget: object - ): void; - /** draws every group area in the background */ - drawGroups(canvas: any, ctx: CanvasRenderingContext2D): void; - adjustNodesSize(): void; - /** resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode */ - resize(width?: number, height?: number): void; - /** - * switches to live mode (node shapes are not rendered, only the content) - * this feature was designed when graphs where meant to create user interfaces - **/ - switchLiveMode(transition?: boolean): void; - onNodeSelectionChange(): void; - touchHandler(event: TouchEvent): void; - - showLinkMenu(link: LLink, e: any): false; - prompt( - title: string, - value: any, - callback: Function, - event: any - ): HTMLDivElement; - showSearchBox(event?: MouseEvent): void; - showEditPropertyValue(node: LGraphNode, property: any, options: any): void; - createDialog( - html: string, - options?: { position?: Vector2; event?: MouseEvent } - // @rgthree - Fix return type from void (added above) - ): CanvasDivDialog; - - - - convertOffsetToCanvas: DragAndScale["convertOffsetToCanvas"]; - convertCanvasToOffset: DragAndScale["convertCanvasToOffset"]; - /** converts event coordinates from canvas2D to graph coordinates */ - // @rgthree - change MouseEvent to less restrictive {clientX: number, clientY: number} that - // implementation uses. - convertEventToCanvasOffset(e: {clientX: number, clientY: number}): Vector2; - /** adds some useful properties to a mouse event, like the position in graph coordinates */ - adjustMouseEvent(e: MouseEvent): void; - - getCanvasMenuOptions(): ContextMenuItem[]; - getNodeMenuOptions(node: LGraphNode): ContextMenuItem[]; - getGroupMenuOptions(): ContextMenuItem[]; - /** Called by `getCanvasMenuOptions`, replace default options */ - getMenuOptions?(): ContextMenuItem[]; - /** Called by `getCanvasMenuOptions`, append to default options */ - getExtraMenuOptions?(): ContextMenuItem[]; - /** Called when mouse right click */ - processContextMenu(node: LGraphNode, event: Event): void; + interface LGraphNode { + // @ComfyUI_frontend + onExecuted?(output: any): void; + } + interface LGraphCanvas { // @rgthree - Adding this for ComfyUI, since they add this in their own overload in app.js selected_group_moving?: boolean; - // @rgthree - showShowNodePanel(node: LGraphNode): void; + // @rgthree - Allows LGraphGroup to be centered (it could just be `{size: Point, pos: Point}`). + centerOnNode(node: LGraphNode | LGraphGroup); + } } -// @rgthree - The adjusted pointer event after calling adjustMouseEvent -export interface AdjustedMouseEvent extends PointerEvent { - deltaX: number; - deltaY: number; - canvasX: number; - canvasY: number; +declare module "@litegraph/types/widgets" { + interface IBaseWidget { + // @ComfyUI_frontend - Kinda, made this a method instead of a member. + serializeValue?(node: LGraphNode, index: number): Promise | unknown; + + // @ComfyUI_frontend + inputEl?: HTMLInputElement; + + // @rgthree - A status we put on some nodes so we can draw things around it. + rgthree_lastValue?: any; + } } -declare class ContextMenu { - static trigger( - element: HTMLElement, - event_name: string, - params: any, - origin: any - ): void; - static isCursorOverElement(event: MouseEvent, element: HTMLElement): void; - static closeAllContextMenus(window: Window): void; - constructor(values: ContextMenuItem[]|string[], options?: IContextMenuOptions, window?: Window); - options: IContextMenuOptions; - parentMenu?: ContextMenu; - lock: boolean; - current_submenu?: ContextMenu; - addItem( - name: string, - value: ContextMenuItem, - options?: IContextMenuOptions - ): void; - close(e?: MouseEvent, ignore_parent_menu?: boolean): void; - getTopMenu(): void; - getFirstEvent(): void; +declare module "@litegraph/interfaces" { + // @ComfyUI_frontend + interface IFoundSlot { + widget?: IBaseWidget; + } } -declare global { - interface Math { - clamp(v: number, min: number, max: number): number; - } +declare module "@litegraph/LiteGraphGlobal" { + interface LiteGraphGlobal { + // @rgthree - Fix: Window is actually optional in the code. + closeAllContextMenus(ref_window?: Window): void; + } } diff --git a/src_web/typings/rgthree.d.ts b/src_web/typings/rgthree.d.ts index bf11fa2..e812237 100644 --- a/src_web/typings/rgthree.d.ts +++ b/src_web/typings/rgthree.d.ts @@ -1,8 +1,9 @@ -import type { AdjustedMouseEvent, LGraphNode, Vector2 } from "./litegraph.js"; +import type { LGraphNode, Vector2 } from "@litegraph/litegraph.js"; +import type { CanvasMouseEvent } from "@litegraph/types/events.js"; import type {Constructor} from "./index.js"; import type {RgthreeBaseVirtualNode} from '../comfyui/base_node.js' -export type AdjustedMouseCustomEvent = CustomEvent<{ originalEvent: AdjustedMouseEvent }>; +export type AdjustedMouseCustomEvent = CustomEvent<{ originalEvent: CanvasMouseEvent }>; export interface RgthreeBaseNodeConstructor extends Constructor { diff --git a/tsconfig.json b/tsconfig.json index 47e3cca..9b39ae4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ // ], "baseUrl": "./", "paths": { + "@litegraph/*": ["node_modules/@comfyorg/litegraph/dist/*"], "typings/*": ["src_web/typings/*"], "rgthree/common/*": ["src_web/common/*"], "node_modules": ["node_modules/*"], diff --git a/web/comfyui/base_any_input_connected_node.js b/web/comfyui/base_any_input_connected_node.js index 4187b9b..0e8ebd1 100644 --- a/web/comfyui/base_any_input_connected_node.js +++ b/web/comfyui/base_any_input_connected_node.js @@ -191,16 +191,16 @@ export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode { } } const oldLGraphNodeConnectByType = LGraphNode.prototype.connectByType; -LGraphNode.prototype.connectByType = function connectByType(slot, sourceNode, sourceSlotType, optsIn) { - if (sourceNode.inputs) { - for (const [index, input] of sourceNode.inputs.entries()) { +LGraphNode.prototype.connectByType = function connectByType(slot, targetNode, targetSlotType, optsIn) { + if (targetNode.inputs) { + for (const [index, input] of targetNode.inputs.entries()) { if (!input.link && input.type === "*") { - this.connect(slot, sourceNode, index); + this.connect(slot, targetNode, index); return null; } } } return ((oldLGraphNodeConnectByType && - oldLGraphNodeConnectByType.call(this, slot, sourceNode, sourceSlotType, optsIn)) || + oldLGraphNodeConnectByType.call(this, slot, targetNode, targetSlotType, optsIn)) || null); }; diff --git a/web/comfyui/base_node.js b/web/comfyui/base_node.js index 4b71df0..9fe5f2e 100644 --- a/web/comfyui/base_node.js +++ b/web/comfyui/base_node.js @@ -29,7 +29,7 @@ export class RgthreeBaseNode extends LGraphNode { } this.checkAndRunOnConstructed(); }); - defineProperty(this, 'mode', { + defineProperty(this, "mode", { get: () => { return this.rgthree_mode; }, @@ -80,7 +80,7 @@ export class RgthreeBaseNode extends LGraphNode { } clone() { const cloned = super.clone(); - if (cloned.properties && !!window.structuredClone) { + if ((cloned === null || cloned === void 0 ? void 0 : cloned.properties) && !!window.structuredClone) { cloned.properties = structuredClone(cloned.properties); } return cloned; @@ -91,6 +91,9 @@ export class RgthreeBaseNode extends LGraphNode { action; } removeWidget(widgetOrSlot) { + if (!this.widgets) { + return; + } if (typeof widgetOrSlot === "number") { this.widgets.splice(widgetOrSlot, 1); } @@ -105,7 +108,7 @@ export class RgthreeBaseNode extends LGraphNode { var _a, _b; const menu_info = []; if ((_b = (_a = slot === null || slot === void 0 ? void 0 : slot.output) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length) { - menu_info.push({ content: "Disconnect Links", slot: slot }); + menu_info.push({ content: "Disconnect Links", slot }); } let inputOrOutput = slot.input || slot.output; if (inputOrOutput) { @@ -161,6 +164,7 @@ export class RgthreeBaseNode extends LGraphNode { if (help) { addHelpMenuItem(this, help, options); } + return options; } } RgthreeBaseNode.exposedActions = []; diff --git a/web/comfyui/base_power_prompt.js b/web/comfyui/base_power_prompt.js index 9a5f064..0e1fccf 100644 --- a/web/comfyui/base_power_prompt.js +++ b/web/comfyui/base_power_prompt.js @@ -59,10 +59,11 @@ export class PowerPrompt { }; const onPropertyChanged = this.node.onPropertyChanged; this.node.onPropertyChanged = (property, value, prevValue) => { - onPropertyChanged && onPropertyChanged.call(this, property, value, prevValue); + const v = onPropertyChanged && onPropertyChanged.call(this.node, property, value, prevValue); if (property === "combos_filter") { this.refreshCombos(this.nodeData); } + return v !== null && v !== void 0 ? v : true; }; for (let i = this.node.widgets.length - 1; i >= 0; i--) { if (this.shouldRemoveServerWidget(this.node.widgets[i])) { diff --git a/web/comfyui/bookmark.js b/web/comfyui/bookmark.js index 4935a57..c7eeacd 100644 --- a/web/comfyui/bookmark.js +++ b/web/comfyui/bookmark.js @@ -70,6 +70,7 @@ export class Bookmark extends RgthreeBaseVirtualNode { input.value = Object.keys(KEY_EVENT_SERVICE.downKeys).join(" + "); }); } + return false; } canvasToBookmark() { var _a, _b; diff --git a/web/comfyui/bypasser.js b/web/comfyui/bypasser.js index 3f97178..96e47e2 100644 --- a/web/comfyui/bypasser.js +++ b/web/comfyui/bypasser.js @@ -13,17 +13,17 @@ class BypasserNode extends BaseNodeModeChanger { } async handleAction(action) { if (action === "Bypass all") { - for (const widget of this.widgets) { + for (const widget of this.widgets || []) { this.forceWidgetOff(widget, true); } } else if (action === "Enable all") { - for (const widget of this.widgets) { + for (const widget of this.widgets || []) { this.forceWidgetOn(widget, true); } } else if (action === "Toggle all") { - for (const widget of this.widgets) { + for (const widget of this.widgets || []) { this.forceWidgetToggle(widget, true); } } diff --git a/web/comfyui/context.js b/web/comfyui/context.js index 3df222e..e6185da 100644 --- a/web/comfyui/context.js +++ b/web/comfyui/context.js @@ -67,37 +67,36 @@ export class BaseContextNode extends RgthreeBaseServerNode { this.___collapsed_width = 30 + (title ? 10 + ctx.measureText(title).width : 0); ctx.font = oldFont; } - connectByType(slot, sourceNode, sourceSlotType, optsIn) { - let canConnect = super.connectByType && - super.connectByType.call(this, slot, sourceNode, sourceSlotType, optsIn); + connectByType(slot, targetNode, targetSlotType, optsIn) { + var _a; + let canConnect = (_a = super.connectByType) === null || _a === void 0 ? void 0 : _a.call(this, slot, targetNode, targetSlotType, optsIn); if (!super.connectByType) { - canConnect = LGraphNode.prototype.connectByType.call(this, slot, sourceNode, sourceSlotType, optsIn); + canConnect = LGraphNode.prototype.connectByType.call(this, slot, targetNode, targetSlotType, optsIn); } if (!canConnect && slot === 0) { const ctrlKey = KEY_EVENT_SERVICE.ctrlKey; - for (const [index, input] of (sourceNode.inputs || []).entries()) { + for (const [index, input] of (targetNode.inputs || []).entries()) { if (input.link && !ctrlKey) { continue; } - const thisOutputSlot = findMatchingIndexByTypeOrName(sourceNode, input, this.outputs); + const thisOutputSlot = findMatchingIndexByTypeOrName(targetNode, input, this.outputs); if (thisOutputSlot > -1) { - this.connect(thisOutputSlot, sourceNode, index); + this.connect(thisOutputSlot, targetNode, index); } } } return null; } connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn) { - var _a; - let canConnect = super.connectByTypeOutput && - super.connectByTypeOutput.call(this, slot, sourceNode, sourceSlotType, optsIn); + var _a, _b; + let canConnect = (_a = super.connectByTypeOutput) === null || _a === void 0 ? void 0 : _a.call(this, slot, sourceNode, sourceSlotType, optsIn); if (!super.connectByType) { canConnect = LGraphNode.prototype.connectByTypeOutput.call(this, slot, sourceNode, sourceSlotType, optsIn); } if (!canConnect && slot === 0) { const ctrlKey = KEY_EVENT_SERVICE.ctrlKey; for (const [index, output] of (sourceNode.outputs || []).entries()) { - if (((_a = output.links) === null || _a === void 0 ? void 0 : _a.length) && !ctrlKey) { + if (((_b = output.links) === null || _b === void 0 ? void 0 : _b.length) && !ctrlKey) { continue; } const thisInputSlot = findMatchingIndexByTypeOrName(sourceNode, output, this.inputs); diff --git a/web/comfyui/dynamic_context_switch.js b/web/comfyui/dynamic_context_switch.js index 9e5ebcd..a59bd98 100644 --- a/web/comfyui/dynamic_context_switch.js +++ b/web/comfyui/dynamic_context_switch.js @@ -21,9 +21,9 @@ class DynamicContextSwitchNode extends DynamicContextNodeBase { handleUpstreamMutation(mutation) { this.scheduleHardRefresh(); } - onConnectionsChange(type, slotIndex, isConnected, link, ioSlot) { + onConnectionsChange(type, slotIndex, isConnected, link, inputOrOutput) { var _a; - (_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, link, ioSlot); + (_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, link, inputOrOutput); if (this.configuring) { return; } diff --git a/web/comfyui/fast_actions_button.js b/web/comfyui/fast_actions_button.js index 5d2319b..dc8e697 100644 --- a/web/comfyui/fast_actions_button.js +++ b/web/comfyui/fast_actions_button.js @@ -19,7 +19,7 @@ class FastActionsButton extends BaseAnyInputConnectedNode { this.properties["buttonText"] = "🎬 Action!"; this.properties["shortcutModifier"] = "alt"; this.properties["shortcutKey"] = ""; - this.buttonWidget = this.addWidget("button", this.properties["buttonText"], null, () => { + this.buttonWidget = this.addWidget("button", this.properties["buttonText"], "", () => { this.executeConnectedNodes(); }, { serialize: false }); this.keypressBound = this.onKeypress.bind(this); @@ -32,7 +32,7 @@ class FastActionsButton extends BaseAnyInputConnectedNode { if (info.widgets_values) { for (let [index, value] of info.widgets_values.entries()) { if (index > 0) { - if (value.startsWith("comfy_action:")) { + if (typeof value === "string" && value.startsWith("comfy_action:")) { value = value.replace("comfy_action:", ""); this.addComfyActionWidget(index, value); } @@ -92,14 +92,15 @@ class FastActionsButton extends BaseAnyInputConnectedNode { } this.executingFromShortcut = false; } - onPropertyChanged(property, value, _prevValue) { - if (property == "buttonText") { + onPropertyChanged(property, value, prevValue) { + var _a, _b; + if (property == "buttonText" && typeof value === "string") { this.buttonWidget.name = value; } - if (property == "shortcutKey") { - value = value.trim(); - this.properties["shortcutKey"] = (value && value[0].toLowerCase()) || ""; + if (property == "shortcutKey" && typeof value === "string") { + this.properties["shortcutKey"] = (_b = (_a = value.trim()[0]) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : ""; } + return true; } handleLinkedNodesStabilization(linkedNodes) { var _a, _b, _c, _d, _e, _f, _g, _h; @@ -200,6 +201,9 @@ class FastActionsButton extends BaseAnyInputConnectedNode { node.mode = MODE_ALWAYS; } if (node.handleAction) { + if (typeof action !== 'string') { + throw new Error('Fast Actions Button action should be a string: ' + action); + } await node.handleAction(action); } app.graph.change(); @@ -210,18 +214,18 @@ class FastActionsButton extends BaseAnyInputConnectedNode { } addComfyActionWidget(slot, value) { let widget = this.addWidget("combo", "Comfy Action", "None", () => { - if (widget.value.startsWith("MOVE ")) { + if (String(widget.value).startsWith("MOVE ")) { this.widgets.push(this.widgets.splice(this.widgets.indexOf(widget), 1)[0]); - widget.value = widget["lastValue_"]; + widget.value = String(widget.rgthree_lastValue); } - else if (widget.value.startsWith("REMOVE ")) { + else if (String(widget.value).startsWith("REMOVE ")) { this.removeWidget(widget); } - widget["lastValue_"] = widget.value; + widget.rgthree_lastValue = widget.value; }, { values: ["None", "Queue Prompt", "REMOVE Comfy Action", "MOVE to end"], }); - widget["lastValue_"] = value; + widget.rgthree_lastValue = value; widget.serializeValue = async (_node, _index) => { return `comfy_app:${widget === null || widget === void 0 ? void 0 : widget.value}`; }; @@ -231,12 +235,12 @@ class FastActionsButton extends BaseAnyInputConnectedNode { } return widget; } - onSerialize(o) { - var _a; - super.onSerialize && super.onSerialize(o); - for (let [index, value] of (o.widgets_values || []).entries()) { - if (((_a = this.widgets[index]) === null || _a === void 0 ? void 0 : _a.name) === "Comfy Action") { - o.widgets_values[index] = `comfy_action:${value}`; + onSerialize(serialised) { + var _a, _b; + (_a = super.onSerialize) === null || _a === void 0 ? void 0 : _a.call(this, serialised); + for (let [index, value] of (serialised.widgets_values || []).entries()) { + if (((_b = this.widgets[index]) === null || _b === void 0 ? void 0 : _b.name) === "Comfy Action") { + serialised.widgets_values[index] = `comfy_action:${value}`; } } } diff --git a/web/comfyui/fast_groups_muter.js b/web/comfyui/fast_groups_muter.js index a000001..aa2bc05 100644 --- a/web/comfyui/fast_groups_muter.js +++ b/web/comfyui/fast_groups_muter.js @@ -135,6 +135,8 @@ export class BaseFastGroupsModeChanger extends RgthreeBaseVirtualNode { this.tempSize = [...this.size]; widget = this.addCustomWidget({ name: "RGTHREE_TOGGLE_AND_NAV", + type: 'toggle', + y: 0, label: "", value: false, disabled: false, diff --git a/web/comfyui/feature_group_fast_toggle.js b/web/comfyui/feature_group_fast_toggle.js index 35b9630..27deb54 100644 --- a/web/comfyui/feature_group_fast_toggle.js +++ b/web/comfyui/feature_group_fast_toggle.js @@ -86,13 +86,13 @@ app.registerExtension({ LGraphCanvas.prototype.drawGroups = function (canvasEl, ctx) { drawGroups.apply(this, [...arguments]); if (!CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled") || - !rgthree.lastAdjustedMouseEvent) { + !rgthree.lastCanvasMouseEvent) { return; } const graph = app.graph; let groups; if (CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.show") !== "always") { - const hoverGroup = graph.getGroupOnPos(rgthree.lastAdjustedMouseEvent.canvasX, rgthree.lastAdjustedMouseEvent.canvasY); + const hoverGroup = graph.getGroupOnPos(rgthree.lastCanvasMouseEvent.canvasX, rgthree.lastCanvasMouseEvent.canvasY); groups = hoverGroup ? [hoverGroup] : []; } else { diff --git a/web/comfyui/image_comparer.js b/web/comfyui/image_comparer.js index 832734c..7123b26 100644 --- a/web/comfyui/image_comparer.js +++ b/web/comfyui/image_comparer.js @@ -56,12 +56,12 @@ export class RgthreeImageComparer extends RgthreeBaseServerNode { this.canvasWidget.value = { images: imagesToChoose }; } } - onSerialize(o) { + onSerialize(serialised) { var _a; - super.onSerialize && super.onSerialize(o); - for (let [index, widget_value] of (o.widgets_values || []).entries()) { + super.onSerialize && super.onSerialize(serialised); + for (let [index, widget_value] of (serialised.widgets_values || []).entries()) { if (((_a = this.widgets[index]) === null || _a === void 0 ? void 0 : _a.name) === "rgthree_comparer") { - o.widgets_values[index] = this.widgets[index].value.images.map((d) => { + serialised.widgets_values[index] = this.widgets[index].value.images.map((d) => { d = { ...d }; delete d.img; return d; @@ -87,26 +87,27 @@ export class RgthreeImageComparer extends RgthreeBaseServerNode { }); } } - onMouseDown(event, pos, graphCanvas) { + onMouseDown(event, pos, canvas) { var _a; - (_a = super.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, graphCanvas); + (_a = super.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, canvas); this.setIsPointerDown(true); + return false; } - onMouseEnter(event, pos, graphCanvas) { + onMouseEnter(event) { var _a; - (_a = super.onMouseEnter) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, graphCanvas); + (_a = super.onMouseEnter) === null || _a === void 0 ? void 0 : _a.call(this, event); this.setIsPointerDown(!!app.canvas.pointer_is_down); this.isPointerOver = true; } - onMouseLeave(event, pos, graphCanvas) { + onMouseLeave(event) { var _a; - (_a = super.onMouseLeave) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, graphCanvas); + (_a = super.onMouseLeave) === null || _a === void 0 ? void 0 : _a.call(this, event); this.setIsPointerDown(false); this.isPointerOver = false; } - onMouseMove(event, pos, graphCanvas) { + onMouseMove(event, pos, canvas) { var _a; - (_a = super.onMouseMove) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, graphCanvas); + (_a = super.onMouseMove) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, canvas); this.pointerOverPos = [...pos]; this.imageIndex = this.pointerOverPos[0] > this.size[0] / 2 ? 1 : 0; } @@ -343,7 +344,7 @@ class RgthreeImageComparerWidget extends RgthreeBaseWidget { computeSize(width) { return [width, 20]; } - serializeValue(serializedNode, widgetIndex) { + serializeValue(node, index) { const v = []; for (const data of this._value.images) { const d = { ...data }; diff --git a/web/comfyui/label.js b/web/comfyui/label.js index 14e1faf..1d7f11d 100644 --- a/web/comfyui/label.js +++ b/web/comfyui/label.js @@ -120,7 +120,7 @@ Label["@padding"] = { type: "number" }; Label["@borderRadius"] = { type: "number" }; const oldDrawNode = LGraphCanvas.prototype.drawNode; LGraphCanvas.prototype.drawNode = function (node, ctx) { - if (node.constructor === Label) { + if (node.constructor === Label.prototype.constructor) { node.bgcolor = "transparent"; node.color = "transparent"; const v = oldDrawNode.apply(this, arguments); @@ -131,18 +131,18 @@ LGraphCanvas.prototype.drawNode = function (node, ctx) { return v; }; const oldGetNodeOnPos = LGraph.prototype.getNodeOnPos; -LGraph.prototype.getNodeOnPos = function (x, y, nodes_list, margin) { +LGraph.prototype.getNodeOnPos = function (x, y, nodes_list) { var _a, _b; if (nodes_list && rgthree.processingMouseDown && - ((_a = rgthree.lastAdjustedMouseEvent) === null || _a === void 0 ? void 0 : _a.type.includes("down")) && - ((_b = rgthree.lastAdjustedMouseEvent) === null || _b === void 0 ? void 0 : _b.which) === 1) { + ((_a = rgthree.lastCanvasMouseEvent) === null || _a === void 0 ? void 0 : _a.type.includes("down")) && + ((_b = rgthree.lastCanvasMouseEvent) === null || _b === void 0 ? void 0 : _b.which) === 1) { let isDoubleClick = LiteGraph.getTime() - LGraphCanvas.active_canvas.last_mouseclick < 300; if (!isDoubleClick) { nodes_list = [...nodes_list].filter((n) => { var _a; return !(n instanceof Label) || !((_a = n.flags) === null || _a === void 0 ? void 0 : _a.pinned); }); } } - return oldGetNodeOnPos.apply(this, [x, y, nodes_list, margin]); + return oldGetNodeOnPos.apply(this, [x, y, nodes_list]); }; app.registerExtension({ name: "rgthree.Label", diff --git a/web/comfyui/menu_auto_nest.js b/web/comfyui/menu_auto_nest.js index 0fa78eb..e9288e9 100644 --- a/web/comfyui/menu_auto_nest.js +++ b/web/comfyui/menu_auto_nest.js @@ -8,7 +8,7 @@ app.registerExtension({ const logger = rgthree.newLogSession("[ContextMenuAutoNest]"); const existingContextMenu = LiteGraph.ContextMenu; LiteGraph.ContextMenu = function (values, options) { - var _a, _b, _c, _d, _e, _f; + var _a, _b, _c, _d, _e, _f, _g, _h; const threshold = CONFIG_SERVICE.getConfigValue("features.menu_auto_nest.threshold", 20); const enabled = CONFIG_SERVICE.getConfigValue("features.menu_auto_nest.subdirs", false); let incompatible = !enabled || !!((_a = options === null || options === void 0 ? void 0 : options.extra) === null || _a === void 0 ? void 0 : _a.rgthree_doNotNest); @@ -16,7 +16,7 @@ app.registerExtension({ if (values.length <= threshold) { incompatible = `Skipping context menu auto nesting b/c threshold is not met (${threshold})`; } - if (!((_b = options.parentMenu) === null || _b === void 0 ? void 0 : _b.options.rgthree_originalCallback)) { + if (!((_c = (_b = options.parentMenu) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.rgthree_originalCallback)) { if (!(options === null || options === void 0 ? void 0 : options.callback)) { incompatible = `Skipping context menu auto nesting b/c a callback was expected.`; } @@ -28,7 +28,7 @@ app.registerExtension({ if (incompatible) { if (enabled) { const [n, v] = logger.infoParts("Skipping context menu auto nesting for incompatible menu."); - (_c = console[n]) === null || _c === void 0 ? void 0 : _c.call(console, ...v); + (_d = console[n]) === null || _d === void 0 ? void 0 : _d.call(console, ...v); } return existingContextMenu.apply(this, [...arguments]); } @@ -62,15 +62,15 @@ app.registerExtension({ if (foldersCount > 0) { options.rgthree_originalCallback = options.rgthree_originalCallback || - ((_d = options.parentMenu) === null || _d === void 0 ? void 0 : _d.options.rgthree_originalCallback) || + ((_f = (_e = options.parentMenu) === null || _e === void 0 ? void 0 : _e.options) === null || _f === void 0 ? void 0 : _f.rgthree_originalCallback) || options.callback; - const oldCallback = options.rgthree_originalCallback; + const oldCallback = options === null || options === void 0 ? void 0 : options.rgthree_originalCallback; options.callback = undefined; const newCallback = (item, options, event, parentMenu, node) => { oldCallback === null || oldCallback === void 0 ? void 0 : oldCallback(item === null || item === void 0 ? void 0 : item.rgthree_originalValue, options, event, undefined, node); }; const [n, v] = logger.infoParts(`Nested folders found (${foldersCount}).`); - (_e = console[n]) === null || _e === void 0 ? void 0 : _e.call(console, ...v); + (_g = console[n]) === null || _g === void 0 ? void 0 : _g.call(console, ...v); const newValues = []; for (const [folderName, folderValues] of Object.entries(folders)) { newValues.push({ @@ -101,7 +101,7 @@ app.registerExtension({ })); } if (options.scale == null) { - options.scale = Math.max(((_f = app.canvas.ds) === null || _f === void 0 ? void 0 : _f.scale) || 1, 1); + options.scale = Math.max(((_h = app.canvas.ds) === null || _h === void 0 ? void 0 : _h.scale) || 1, 1); } const oldCtrResponse = existingContextMenu.call(this, values, options); if (oldCtrResponse === null || oldCtrResponse === void 0 ? void 0 : oldCtrResponse.constructor) { diff --git a/web/comfyui/menu_copy_image.js b/web/comfyui/menu_copy_image.js index 4a0be8f..84f2b6f 100644 --- a/web/comfyui/menu_copy_image.js +++ b/web/comfyui/menu_copy_image.js @@ -26,9 +26,9 @@ app.registerExtension({ if (await clipboardSupportedPromise) { const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; nodeType.prototype.getExtraMenuOptions = function (canvas, options) { - var _a; - getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined; - if ((_a = this.imgs) === null || _a === void 0 ? void 0 : _a.length) { + var _a, _b; + options = (_a = getExtraMenuOptions === null || getExtraMenuOptions === void 0 ? void 0 : getExtraMenuOptions.call(this, canvas, options)) !== null && _a !== void 0 ? _a : options; + if ((_b = this.imgs) === null || _b === void 0 ? void 0 : _b.length) { let img = this.imgs[this.imageIndex || 0] || this.imgs[this.overIndex || 0] || this.imgs[0]; const foundIdx = options.findIndex((option) => { var _a; return (_a = option === null || option === void 0 ? void 0 : option.content) === null || _a === void 0 ? void 0 : _a.includes("Copy Image"); }); if (img && foundIdx === -1) { @@ -54,6 +54,7 @@ app.registerExtension({ } } } + return options; }; } } diff --git a/web/comfyui/menu_queue_node.js b/web/comfyui/menu_queue_node.js index b1a7a2a..9b0cfff 100644 --- a/web/comfyui/menu_queue_node.js +++ b/web/comfyui/menu_queue_node.js @@ -24,8 +24,8 @@ function showQueueGroupNodesMenuIfGroupIsSelected(existingOptions) { if (CONFIG_SERVICE.getConfigValue("features.menu_queue_selected_nodes") === false) { return; } - const group = rgthree.lastAdjustedMouseEvent && - app.graph.getGroupOnPos(rgthree.lastAdjustedMouseEvent.canvasX, rgthree.lastAdjustedMouseEvent.canvasY); + const group = rgthree.lastCanvasMouseEvent && + app.graph.getGroupOnPos(rgthree.lastCanvasMouseEvent.canvasX, rgthree.lastCanvasMouseEvent.canvasY); const outputNodes = group && getOutputNodes(group._nodes); const menuItem = { content: `Queue Group Output Nodes (rgthree)  `, @@ -46,9 +46,11 @@ app.registerExtension({ async beforeRegisterNodeDef(nodeType, nodeData) { const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; nodeType.prototype.getExtraMenuOptions = function (canvas, options) { - getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined; + var _a; + options = (_a = getExtraMenuOptions === null || getExtraMenuOptions === void 0 ? void 0 : getExtraMenuOptions.call(this, canvas, options)) !== null && _a !== void 0 ? _a : options; showQueueNodesMenuIfOutputNodesAreSelected(options); showQueueGroupNodesMenuIfGroupIsSelected(options); + return options; }; }, async setup() { diff --git a/web/comfyui/node_collector.js b/web/comfyui/node_collector.js index c4f6e0f..61e7cdb 100644 --- a/web/comfyui/node_collector.js +++ b/web/comfyui/node_collector.js @@ -35,13 +35,14 @@ class CombinerNode extends CollectorNode { note.inputEl.style.fontWeight = "bold"; note.inputEl.style.fontStyle = "italic"; note.inputEl.style.opacity = "0.8"; - this.getExtraMenuOptions = (_, options) => { + this.getExtraMenuOptions = (canvas, options) => { options.splice(options.length - 1, 0, { content: "‼️ Update to Node Collector", callback: (_value, _options, _event, _parentMenu, _node) => { updateCombinerToCollector(this); }, }); + return options; }; } configure(info) { diff --git a/web/comfyui/node_mode_relay.js b/web/comfyui/node_mode_relay.js index 886485d..e541b95 100644 --- a/web/comfyui/node_mode_relay.js +++ b/web/comfyui/node_mode_relay.js @@ -123,7 +123,7 @@ class NodeModeRelay extends BaseCollectorNode { mode = MODE_ALWAYS; } else { - mode = null; + mode = undefined; } } this.dispatchModeToRepeater(mode); diff --git a/web/comfyui/power_lora_loader.js b/web/comfyui/power_lora_loader.js index 0730b1e..6c30dc2 100644 --- a/web/comfyui/power_lora_loader.js +++ b/web/comfyui/power_lora_loader.js @@ -149,9 +149,8 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { }, }, ]; - let canvas = app.canvas; - new LiteGraph.ContextMenu(menuItems, { title: "LORA WIDGET", event: rgthree.lastAdjustedMouseEvent }, canvas.getCanvasWindow()); - return null; + new LiteGraph.ContextMenu(menuItems, { title: "LORA WIDGET", event: rgthree.lastCanvasMouseEvent }); + return undefined; } return this.defaultGetSlotMenuOptions(slot); } @@ -179,11 +178,11 @@ class RgthreePowerLoraLoader extends RgthreeBaseServerNode { return allOn && ((_d = this.widgets) === null || _d === void 0 ? void 0 : _d.length) ? true : false; } toggleAllLoras() { - var _b; + var _b, _c; const allOn = this.allLorasState(); const toggledTo = !allOn ? true : false; for (const widget of this.widgets) { - if ((_b = widget.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_")) { + if (((_b = widget.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_")) && ((_c = widget.value) === null || _c === void 0 ? void 0 : _c.on) != null) { widget.value.on = toggledTo; } } @@ -374,8 +373,8 @@ class PowerLoraLoaderWidget extends RgthreeBaseWidget { ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR; let rposX = node.size[0] - margin - innerMargin - innerMargin; const strengthValue = this.showModelAndClip - ? (_c = this.value.strengthTwo) !== null && _c !== void 0 ? _c : 1 - : (_d = this.value.strength) !== null && _d !== void 0 ? _d : 1; + ? ((_c = this.value.strengthTwo) !== null && _c !== void 0 ? _c : 1) + : ((_d = this.value.strength) !== null && _d !== void 0 ? _d : 1); let textColor = undefined; if (((_e = this.loraInfo) === null || _e === void 0 ? void 0 : _e.strengthMax) != null && strengthValue > ((_f = this.loraInfo) === null || _f === void 0 ? void 0 : _f.strengthMax)) { textColor = "#c66"; @@ -445,7 +444,7 @@ class PowerLoraLoaderWidget extends RgthreeBaseWidget { ctx.globalAlpha = app.canvas.editor_alpha; ctx.restore(); } - serializeValue(serializedNode, widgetIndex) { + serializeValue(node, index) { var _b; const v = { ...this.value }; if (!this.showModelAndClip) { diff --git a/web/comfyui/reroute.js b/web/comfyui/reroute.js index 82c1ab8..422fb46 100644 --- a/web/comfyui/reroute.js +++ b/web/comfyui/reroute.js @@ -49,7 +49,7 @@ class RerouteService { } async onCanvasSetUpListenerForLinking() { const canvas = await waitForCanvas(); - const canvasProperty = true ? 'connecting_links' : 'connecting_node'; + const canvasProperty = true ? "connecting_links" : "connecting_node"; canvas[`_${canvasProperty}`]; const thisService = this; Object.defineProperty(canvas, canvasProperty, { @@ -98,9 +98,11 @@ class RerouteService { } } getConnectingData() { - var _a, _b; + var _a, _b, _c, _d; const oldCanvas = app.canvas; - if (oldCanvas.connecting_node && oldCanvas.connecting_slot != null && ((_a = oldCanvas.connecting_pos) === null || _a === void 0 ? void 0 : _a.length)) { + if (oldCanvas.connecting_node && + oldCanvas.connecting_slot != null && + ((_a = oldCanvas.connecting_pos) === null || _a === void 0 ? void 0 : _a.length)) { return { node: oldCanvas.connecting_node, input: oldCanvas.connecting_input, @@ -114,8 +116,8 @@ class RerouteService { const link = canvas.connecting_links[0]; return { node: link.node, - input: link.input, - output: link.output, + input: (_c = link.input) !== null && _c !== void 0 ? _c : undefined, + output: (_d = link.output) !== null && _d !== void 0 ? _d : undefined, slot: link.slot, pos: [...link.pos], }; @@ -125,7 +127,9 @@ class RerouteService { setCanvasConnectingData(ctx) { var _a, _b; const oldCanvas = app.canvas; - if (oldCanvas.connecting_node && oldCanvas.connecting_slot != null && ((_a = oldCanvas.connecting_pos) === null || _a === void 0 ? void 0 : _a.length)) { + if (oldCanvas.connecting_node && + oldCanvas.connecting_slot != null && + ((_a = oldCanvas.connecting_pos) === null || _a === void 0 ? void 0 : _a.length)) { oldCanvas.connecting_node = ctx.node; oldCanvas.connecting_input = ctx.input; oldCanvas.connecting_output = ctx.output; @@ -200,6 +204,8 @@ class RerouteService { data.output = entry.node.outputs[0]; data.slot = 0; data.pos = entry.node.getConnectionPos(false, 0); + data.direction = + layout[0] === "Top" ? 2 : layout[0] === "Bottom" ? 1 : layout[0] === "Left" ? 4 : 3; } else { entry.node.connect(0, data.node, data.slot); @@ -207,6 +213,8 @@ class RerouteService { data.input = entry.node.inputs[0]; data.slot = 0; data.pos = entry.node.getConnectionPos(true, 0); + data.direction = + layout[1] === "Top" ? 2 : layout[1] === "Bottom" ? 1 : layout[1] === "Left" ? 4 : 3; } this.setCanvasConnectingData(data); entry.current = { ...this.connectingData }; @@ -265,7 +273,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { } onConstructed() { var _a; - this.setResizable((_a = this.properties["resizable"]) !== null && _a !== void 0 ? _a : configResizable); + this.setResizable(!!((_a = this.properties["resizable"]) !== null && _a !== void 0 ? _a : configResizable)); this.size = RerouteNode.size; this.addInput("", "*"); this.addOutput("", "*"); @@ -282,7 +290,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { } super.configure(info); this.configuring = true; - this.setResizable((_c = this.properties["resizable"]) !== null && _c !== void 0 ? _c : configResizable); + this.setResizable(!!((_c = this.properties["resizable"]) !== null && _c !== void 0 ? _c : configResizable)); this.applyNodeSize(); this.configuring = false; } @@ -333,11 +341,11 @@ class RerouteNode extends RgthreeBaseVirtualNode { ctx.restore(); } } - findInputSlot(name) { - return 0; + findInputSlot(name, returnObj = false) { + return returnObj ? this.inputs[0] : 0; } - findOutputSlot(name) { - return 0; + findOutputSlot(name, returnObj) { + return returnObj ? this.outputs[0] : 0; } disconnectOutput(slot, targetNode) { return super.disconnectOutput(slot, targetNode); @@ -347,7 +355,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { if (rgthree.replacingReroute != null && ((_a = this.inputs[0]) === null || _a === void 0 ? void 0 : _a.link)) { const graph = app.graph; const link = graph.links[this.inputs[0].link]; - const node = graph.getNodeById(link === null || link === void 0 ? void 0 : link.origin_id); + const node = (link === null || link === void 0 ? void 0 : link.origin_id) != null ? graph.getNodeById(link.origin_id) : null; if (rgthree.replacingReroute !== (node === null || node === void 0 ? void 0 : node.id)) { return false; } @@ -490,7 +498,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { (_h = (_g = node).applyNodeSize) === null || _h === void 0 ? void 0 : _h.call(_g); for (const l of node.outputs[0].links || []) { const link = app.graph.links[l]; - if (link) { + if (link && color) { link.color = color; } } @@ -516,7 +524,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { const links = inputNode.outputs[inputNodeOutputSlot].links; for (const l of links || []) { const link = app.graph.links[l]; - if (link) { + if (link && color) { link.color = color; } } @@ -536,8 +544,8 @@ class RerouteNode extends RgthreeBaseVirtualNode { if (newSize[0] === 10 || newSize[1] === 10) { const props = this.properties; props["connections_layout"] = props["connections_layout"] || ["Left", "Right"]; - const layout = props["connections_layout"]; props["connections_dir"] = props["connections_dir"] || [-1, -1]; + const layout = props["connections_layout"]; const dir = props["connections_dir"]; if (oldSize[0] > 10 && newSize[0] === 10) { dir[0] = LiteGraph.DOWN; @@ -590,27 +598,22 @@ class RerouteNode extends RgthreeBaseVirtualNode { const h = this.size[1]; this.properties["connections_layout"] = this.properties["connections_layout"] || this.defaultConnectionsLayout; - const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(this.properties["connections_layout"][0]); - const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(this.properties["connections_layout"][1]); + const connections_layout = this.properties["connections_layout"]; + const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[0]); + const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[1]); if (degrees == 90 || degrees === -90) { if (degrees === -90) { - this.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex - 1) % 4) + 4) % 4]; - this.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex - 1) % 4) + 4) % 4]; + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex - 1) % 4) + 4) % 4]; + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex - 1) % 4) + 4) % 4]; } else { - this.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex + 1) % 4) + 4) % 4]; - this.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex + 1) % 4) + 4) % 4]; + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 1) % 4) + 4) % 4]; + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 1) % 4) + 4) % 4]; } } else if (degrees === 180) { - this.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]; - this.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]; + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]; + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]; } this.setSize([h, w]); } @@ -650,13 +653,15 @@ class RerouteNode extends RgthreeBaseVirtualNode { var _a, _b; const props = this.properties; props["connections_layout"] = props["connections_layout"] || ["Left", "Right"]; + const connections_layout = this.properties["connections_layout"]; const propIdx = ioDir == IoDirection.INPUT ? 0 : 1; const oppositeIdx = propIdx ? 0 : 1; - let currentLayout = props["connections_layout"][propIdx]; - let oppositeLayout = props["connections_layout"][oppositeIdx]; + let currentLayout = connections_layout[propIdx]; + let oppositeLayout = connections_layout[oppositeIdx]; if (this.size[0] === 10 || this.size[1] === 10) { props["connections_dir"] = props["connections_dir"] || [-1, -1]; - let currentDir = props["connections_dir"][propIdx]; + const connections_dir = this.properties["connections_dir"]; + let currentDir = connections_dir[propIdx]; const options = this.size[0] === 10 ? currentLayout === "Bottom" ? [LiteGraph.DOWN, LiteGraph.RIGHT, LiteGraph.LEFT] @@ -666,7 +671,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { : [LiteGraph.LEFT, LiteGraph.UP, LiteGraph.DOWN]; let idx = options.indexOf(currentDir); let next = (_a = options[idx + 1]) !== null && _a !== void 0 ? _a : options[0]; - this.properties["connections_dir"][propIdx] = next; + connections_dir[propIdx] = next; return; } let next = currentLayout; @@ -674,7 +679,7 @@ class RerouteNode extends RgthreeBaseVirtualNode { let idx = LAYOUT_CLOCKWISE.indexOf(next); next = (_b = LAYOUT_CLOCKWISE[idx + 1]) !== null && _b !== void 0 ? _b : LAYOUT_CLOCKWISE[0]; } while (next === oppositeLayout); - this.properties["connections_layout"][propIdx] = next; + connections_layout[propIdx] = next; this.setDirtyCanvas(true, true); } onMouseMove(event) { @@ -910,26 +915,23 @@ addMenuItem(RerouteNode, app, { node.rotate(180); } else { - const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(node.properties["connections_layout"][0]); - const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(node.properties["connections_layout"][1]); + const connections_layout = node.properties["connections_layout"]; + const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[0]); + const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[1]); if (value === null || value === void 0 ? void 0 : value.startsWith("Flip Horizontally")) { - if (["Left", "Right"].includes(node.properties["connections_layout"][0])) { - node.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]; + if (["Left", "Right"].includes(connections_layout[0])) { + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]; } - if (["Left", "Right"].includes(node.properties["connections_layout"][1])) { - node.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]; + if (["Left", "Right"].includes(connections_layout[1])) { + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]; } } else if (value === null || value === void 0 ? void 0 : value.startsWith("Flip Vertically")) { - if (["Top", "Bottom"].includes(node.properties["connections_layout"][0])) { - node.properties["connections_layout"][0] = - LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]; + if (["Top", "Bottom"].includes(connections_layout[0])) { + connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4]; } - if (["Top", "Bottom"].includes(node.properties["connections_layout"][1])) { - node.properties["connections_layout"][1] = - LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]; + if (["Top", "Bottom"].includes(connections_layout[1])) { + connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4]; } } } diff --git a/web/comfyui/rgthree.js b/web/comfyui/rgthree.js index 3d22056..5999664 100644 --- a/web/comfyui/rgthree.js +++ b/web/comfyui/rgthree.js @@ -135,7 +135,7 @@ class Rgthree extends EventTarget { this.processingMouseDown = false; this.processingMouseUp = false; this.processingMouseMove = false; - this.lastAdjustedMouseEvent = null; + this.lastCanvasMouseEvent = null; this.canvasCurrentlyCopyingToClipboard = false; this.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false; this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = null; @@ -181,11 +181,11 @@ class Rgthree extends EventTarget { LiteGraph.closeAllContextMenus(); if (e.button == 2) { const canvas = await waitForCanvas(); - new LiteGraph.ContextMenu(this.getRgthreeContextMenuItems(), { + new LiteGraph.ContextMenu(this.getRgthreeIContextMenuValues(), { title: `
${logoRgthree} rgthree-comfy
`, left: e.clientX, top: 5, - }, canvas.getCanvasWindow()); + }); return; } if (e.button == 0) { @@ -246,7 +246,7 @@ class Rgthree extends EventTarget { const adjustMouseEvent = LGraphCanvas.prototype.adjustMouseEvent; LGraphCanvas.prototype.adjustMouseEvent = function (e) { adjustMouseEvent.apply(this, [...arguments]); - rgthree.lastAdjustedMouseEvent = e; + rgthree.lastCanvasMouseEvent = e; }; const copyToClipboard = LGraphCanvas.prototype.copyToClipboard; LGraphCanvas.prototype.copyToClipboard = function (nodes) { @@ -311,7 +311,7 @@ class Rgthree extends EventTarget { content: logoRgthree + `rgthree-comfy`, className: "rgthree-contextmenu-item rgthree-contextmenu-main-item-rgthree-comfy", submenu: { - options: that.getRgthreeContextMenuItems(), + options: that.getRgthreeIContextMenuValues(), }, }); options.push(null); @@ -334,7 +334,7 @@ class Rgthree extends EventTarget { }; }, 1016); } - getRgthreeContextMenuItems() { + getRgthreeIContextMenuValues() { const [canvas, graph] = [app.canvas, app.graph]; const selectedNodes = Object.values(canvas.selected_nodes || {}); let rerouteNodes = []; @@ -361,13 +361,15 @@ class Rgthree extends EventTarget { options: getNodeTypeStrings(), callback: (value, options, event) => { const node = LiteGraph.createNode(addRgthree(value)); - node.pos = [ - rgthree.lastAdjustedMouseEvent.canvasX, - rgthree.lastAdjustedMouseEvent.canvasY, - ]; - canvas.graph.add(node); - canvas.selectNode(node); - app.graph.setDirtyCanvas(true, true); + if (node) { + node.pos = [ + rgthree.lastCanvasMouseEvent.canvasX, + rgthree.lastCanvasMouseEvent.canvasY, + ]; + canvas.graph.add(node); + canvas.selectNode(node); + app.graph.setDirtyCanvas(true, true); + } }, extra: { rgthree_doNotNest: true }, }, diff --git a/web/comfyui/seed.js b/web/comfyui/seed.js index 65bd1c3..2d6cf19 100644 --- a/web/comfyui/seed.js +++ b/web/comfyui/seed.js @@ -44,7 +44,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { } } onNodeCreated() { - var _a; + var _a, _b, _c; (_a = super.onNodeCreated) === null || _a === void 0 ? void 0 : _a.call(this); for (const [i, w] of this.widgets.entries()) { if (w.name === "seed") { @@ -56,17 +56,17 @@ class RgthreeSeed extends RgthreeBaseServerNode { } } let step = this.seedWidget.options.step || 1; - this.randMax = Math.min(1125899906842624, this.seedWidget.options.max); - this.randMin = Math.max(0, this.seedWidget.options.min); + this.randMax = Math.min(1125899906842624, (_b = this.seedWidget.options.max) !== null && _b !== void 0 ? _b : 0); + this.randMin = Math.max(0, (_c = this.seedWidget.options.min) !== null && _c !== void 0 ? _c : 0); this.randomRange = (this.randMax - Math.max(0, this.randMin)) / (step / 10); - this.addWidget("button", "🎲 Randomize Each Time", null, () => { + this.addWidget("button", "🎲 Randomize Each Time", '', () => { this.seedWidget.value = SPECIAL_SEED_RANDOM; }, { serialize: false }); - this.addWidget("button", "🎲 New Fixed Random", null, () => { + this.addWidget("button", "🎲 New Fixed Random", '', () => { this.seedWidget.value = Math.floor(Math.random() * this.randomRange) * (step / 10) + this.randMin; }, { serialize: false }); - this.lastSeedButton = this.addWidget("button", LAST_SEED_BUTTON_LABEL, null, () => { + this.lastSeedButton = this.addWidget("button", LAST_SEED_BUTTON_LABEL, '', () => { this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value; this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL; this.lastSeedButton.disabled = true; @@ -88,6 +88,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { } }, }); + return options; } addLastSeedValue() { if (this.lastSeedValue) @@ -141,7 +142,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { } } getSeedToUse() { - const inputSeed = this.seedWidget.value; + const inputSeed = Number(this.seedWidget.value); let seedToUse = null; if (SPECIAL_SEEDS.includes(inputSeed)) { if (typeof this.lastSeed === "number" && !SPECIAL_SEEDS.includes(this.lastSeed)) { diff --git a/web/comfyui/services/fast_groups_service.js b/web/comfyui/services/fast_groups_service.js index acc8eb1..574e071 100644 --- a/web/comfyui/services/fast_groups_service.js +++ b/web/comfyui/services/fast_groups_service.js @@ -63,7 +63,7 @@ class FastGroupsService { if (!this.cachedNodeBoundings) { this.cachedNodeBoundings = {}; for (const node of app.graph._nodes) { - this.cachedNodeBoundings[node.id] = node.getBounding(); + this.cachedNodeBoundings[Number(node.id)] = node.getBounding(); } setTimeout(() => { this.cachedNodeBoundings = null; @@ -76,7 +76,7 @@ class FastGroupsService { const nodes = group.graph._nodes; group._nodes.length = 0; for (const node of nodes) { - const node_bounding = cachedBoundings[node.id]; + const node_bounding = cachedBoundings[Number(node.id)]; if (!node_bounding || !LiteGraph.overlapBounding(group._bounding, node_bounding)) { continue; } diff --git a/web/comfyui/utils.js b/web/comfyui/utils.js index b736352..defec1f 100644 --- a/web/comfyui/utils.js +++ b/web/comfyui/utils.js @@ -130,7 +130,7 @@ export function addConnectionLayoutSupport(node, app, options = [ subMenuOptions: options.map((option) => option[0] + (option[1] ? " -> " + option[1] : "")), prepareValue: (value, node) => { var _a; - const values = value.split(" -> "); + const values = String(value).split(" -> "); if (!values[1] && !((_a = node.outputs) === null || _a === void 0 ? void 0 : _a.length)) { values[1] = LAYOUT_LABEL_OPPOSITES[values[0]]; } @@ -209,8 +209,9 @@ export function getConnectionPosForLayout(node, isInput, slotNumber, out) { return count; }, 0); cxn.dir = data[0]; - if ((node.size[0] == 10 || node.size[1] == 10) && node.properties["connections_dir"]) { - cxn.dir = node.properties["connections_dir"][isInput ? 0 : 1]; + const connections_dir = node.properties["connections_dir"]; + if ((node.size[0] == 10 || node.size[1] == 10) && connections_dir) { + cxn.dir = connections_dir[isInput ? 0 : 1]; } if (side === "Left") { if (node.flags.collapsed) { diff --git a/web/comfyui/utils_menu.js b/web/comfyui/utils_menu.js index a94ae14..7ca7c40 100644 --- a/web/comfyui/utils_menu.js +++ b/web/comfyui/utils_menu.js @@ -11,7 +11,7 @@ export async function showLoraChooser(event, callback, parentMenu, loras) { } new LiteGraph.ContextMenu(loras, { event: event, - parentMenu, + parentMenu: parentMenu != null ? parentMenu : undefined, title: "Choose a lora", scale: Math.max(1, (_b = (_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1), className: "dark", diff --git a/web/comfyui/utils_widgets.js b/web/comfyui/utils_widgets.js index 81bed18..f98ebfd 100644 --- a/web/comfyui/utils_widgets.js +++ b/web/comfyui/utils_widgets.js @@ -20,6 +20,9 @@ export function drawLabelAndValue(ctx, label, value, width, posY, height, option } export class RgthreeBaseWidget { constructor(name) { + this.type = "custom"; + this.options = {}; + this.y = 0; this.last_y = 0; this.mouseDowned = null; this.isMouseDownedAndOver = false; @@ -27,6 +30,9 @@ export class RgthreeBaseWidget { this.downedHitAreasForMove = []; this.name = name; } + serializeValue(node, index) { + return this.value; + } clickWasWithinBounds(pos, bounds) { let xStart = bounds[0]; let xEnd = xStart + (bounds.length > 2 ? bounds[2] : bounds[1]); @@ -115,8 +121,9 @@ export class RgthreeBetterButtonWidget extends RgthreeBaseWidget { return this.mouseUpCallback(event, pos, node); } } -export class RgthreeBetterTextWidget { +export class RgthreeBetterTextWidget extends RgthreeBaseWidget { constructor(name, value) { + super(name); this.name = name; this.value = value; } @@ -135,11 +142,11 @@ export class RgthreeBetterTextWidget { return false; } } -export class RgthreeDividerWidget { +export class RgthreeDividerWidget extends RgthreeBaseWidget { constructor(widgetOptions) { + super("divider"); this.options = { serialize: false }; - this.value = null; - this.name = "divider"; + this.value = ''; this.widgetOptions = { marginTop: 7, marginBottom: 7, @@ -166,13 +173,13 @@ export class RgthreeDividerWidget { ]; } } -export class RgthreeLabelWidget { +export class RgthreeLabelWidget extends RgthreeBaseWidget { constructor(name, widgetOptions) { + super(name); this.options = { serialize: false }; - this.value = null; + this.value = ''; this.widgetOptions = {}; this.posY = 0; - this.name = name; Object.assign(this.widgetOptions, widgetOptions); } draw(ctx, node, width, posY, height) { @@ -223,18 +230,23 @@ export class RgthreeLabelWidget { return true; } } -export class RgthreeInvisibleWidget { +export class RgthreeInvisibleWidget extends RgthreeBaseWidget { constructor(name, type, value, serializeValueFn) { - this.serializeValue = undefined; - this.name = name; - this.type = type; + super(name); this.value = value; - if (serializeValueFn) { - this.serializeValue = serializeValueFn; - } + this.serializeValueFn = serializeValueFn; + } + draw() { + return; + } + computeSize(width) { + return [0, 0]; + } + serializeValue(node, index) { + return this.serializeValueFn != null + ? this.serializeValueFn(node, index) + : super.serializeValue(node, index); } - draw() { return; } - computeSize(width) { return [0, 0]; } } export function drawWidgetButton(drawCtx, text, isMouseDownedAndOver = false) { if (!isLowQuality() && !isMouseDownedAndOver) { diff --git a/web/common/link_fixer.js b/web/common/link_fixer.js index 5df0b98..777b4f0 100644 --- a/web/common/link_fixer.js +++ b/web/common/link_fixer.js @@ -8,7 +8,7 @@ function getNodeById(graph, id) { return graph.getNodeById(id); } graph = graph; - return graph.nodes.find((n) => n.id === id); + return graph.nodes.find((node) => Number(node.id) === id); } function extendLink(link) { return {