diff --git a/src/scripts/app.js b/src/scripts/app.js index f96d197a8..feae322ea 100644 --- a/src/scripts/app.js +++ b/src/scripts/app.js @@ -4,7 +4,7 @@ import { ComfyUI, $el } from "./ui.js"; import { api } from "./api.js"; import { defaultGraph } from "./defaultGraph.js"; import { getPngMetadata, getWebpMetadata, importA1111, getLatentMetadata } from "./pnginfo.js"; -import { addDomClippingSetting } from "./domWidget.js"; +import { addDomClippingSetting } from "./domWidget"; import { createImageHost, calculateImageGrid } from "./ui/imagePreview.js" export const ANIM_PREVIEW_WIDGET = "$$comfy_animation_preview" diff --git a/src/scripts/domWidget.js b/src/scripts/domWidget.ts similarity index 86% rename from src/scripts/domWidget.js rename to src/scripts/domWidget.ts index b7f437ad2..9c19c7034 100644 --- a/src/scripts/domWidget.js +++ b/src/scripts/domWidget.ts @@ -1,8 +1,32 @@ import { app, ANIM_PREVIEW_WIDGET } from "./app.js"; +import type { LGraphNode, Vector4 } from "/types/litegraph"; + const SIZE = Symbol(); -function intersect(a, b) { + +interface Rect { + height: number; + width: number; + x: number; + y: number; +} + +interface DOMWidget { + type: string; + name: string; + computedHeight?: number; + element?: HTMLElement; + options: any; + value?: any; + y?: number; + callback?: (value: any) => void; + draw?: (ctx: CanvasRenderingContext2D, node: LGraphNode, widgetWidth: number, y: number, widgetHeight: number) => void; + onRemove?: () => void; +} + + +function intersect(a: Rect, b: Rect): Vector4 | null { const x = Math.max(a.x, b.x); const num1 = Math.min(a.x + a.width, b.x + b.width); const y = Math.max(a.y, b.y); @@ -11,8 +35,8 @@ function intersect(a, b) { else return null; } -function getClipPath(node, element) { - const selectedNode = Object.values(app.canvas.selected_nodes)[0]; +function getClipPath(node: LGraphNode, element: HTMLElement): string { + const selectedNode: LGraphNode = Object.values(app.canvas.selected_nodes)[0] as LGraphNode; if (selectedNode && selectedNode !== node) { const elRect = element.getBoundingClientRect(); const MARGIN = 7; @@ -44,7 +68,7 @@ function getClipPath(node, element) { return ""; } -function computeSize(size) { +function computeSize(size: [number, number]): void { if (this.widgets?.[0]?.last_y == null) return; let y = this.widgets[0].last_y; @@ -170,7 +194,7 @@ function computeSize(size) { // Override the compute visible nodes function to allow us to hide/show DOM elements when the node goes offscreen const elementWidgets = new Set(); const computeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes; -LGraphCanvas.prototype.computeVisibleNodes = function () { +LGraphCanvas.prototype.computeVisibleNodes = function (): LGraphNode[] { const visibleNodes = computeVisibleNodes.apply(this, arguments); for (const node of app.graph._nodes) { if (elementWidgets.has(node)) { @@ -192,7 +216,7 @@ LGraphCanvas.prototype.computeVisibleNodes = function () { let enableDomClipping = true; -export function addDomClippingSetting() { +export function addDomClippingSetting(): void { app.ui.settings.addSetting({ id: "Comfy.DOMClippingEnabled", name: "Enable DOM element clipping (enabling may reduce performance)", @@ -204,7 +228,12 @@ export function addDomClippingSetting() { }); } -LGraphNode.prototype.addDOMWidget = function (name, type, element, options) { +LGraphNode.prototype.addDOMWidget = function ( + name: string, + type: string, + element: HTMLElement, + options: Record +): DOMWidget { options = { hideOnZoom: true, selectOn: ["focus", "click"], ...options }; if (!element.parentElement) { @@ -221,7 +250,7 @@ LGraphNode.prototype.addDOMWidget = function (name, type, element, options) { document.addEventListener("mousedown", mouseDownHandler); } - const widget = { + const widget: DOMWidget = { type, name, get value() { @@ -231,7 +260,7 @@ LGraphNode.prototype.addDOMWidget = function (name, type, element, options) { options.setValue?.(v); widget.callback?.(widget.value); }, - draw: function (ctx, node, widgetWidth, y, widgetHeight) { + draw: function (ctx: CanvasRenderingContext2D, node: LGraphNode, widgetWidth: number, y: number, widgetHeight: number) { if (widget.computedHeight == null) { computeSize.call(node, node.size); } @@ -240,7 +269,7 @@ LGraphNode.prototype.addDOMWidget = function (name, type, element, options) { node.flags?.collapsed || (!!options.hideOnZoom && app.canvas.ds.scale < 0.5) || widget.computedHeight <= 0 || - widget.type === "converted-widget"|| + widget.type === "converted-widget" || widget.type === "hidden"; element.hidden = hidden; element.style.display = hidden ? "none" : null; @@ -297,9 +326,9 @@ LGraphNode.prototype.addDOMWidget = function (name, type, element, options) { elementWidgets.add(this); const collapse = this.collapse; - this.collapse = function() { + this.collapse = function () { collapse.apply(this, arguments); - if(this.flags?.collapsed) { + if (this.flags?.collapsed) { element.hidden = true; element.style.display = "none"; } diff --git a/src/scripts/widgets.js b/src/scripts/widgets.js index 6a6899705..aa1360120 100644 --- a/src/scripts/widgets.js +++ b/src/scripts/widgets.js @@ -1,5 +1,5 @@ import { api } from "./api.js" -import "./domWidget.js"; +import "./domWidget"; let controlValueRunBefore = false; export function updateControlWidgetLabel(widget) { diff --git a/src/types/litegraph.d.ts b/src/types/litegraph.d.ts index 6629e779f..a323a71cd 100644 --- a/src/types/litegraph.d.ts +++ b/src/types/litegraph.d.ts @@ -143,192 +143,6 @@ export type ContextMenuEventListener = ( 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; - DEFAULT_SHADOW_COLOR: string; - DEFAULT_GROUP_FONT: number; - - 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; - SQUARE_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; - - 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; - } - >; - - 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 (): LGraphNode }): 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[]; -}; - export type serializedLGraph< TNode = ReturnType, // https://github.com/jagenjo/litegraph.js/issues/74 @@ -1503,4 +1317,190 @@ declare global { interface Math { clamp(v: number, min: number, max: number): number; } + + 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; + DEFAULT_SHADOW_COLOR: string; + DEFAULT_GROUP_FONT: number; + + 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; + SQUARE_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; + + 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; + } + >; + + 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 (): LGraphNode }): 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[]; + }; } diff --git a/tsconfig.json b/tsconfig.json index 06835192d..a09c4601a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,8 @@ "baseUrl": ".", "paths": { "*": ["src/*"], - } + }, + "typeRoots": ["src/types", "node_modules/@types"] }, - "include": ["src/**/*"] + "include": ["src/**/*", "src/types/**/*.d.ts"], }