From d69a2ae9b00c36d3ffab35a15bb209ce9b7d0271 Mon Sep 17 00:00:00 2001 From: filtered <176114999+webfiltered@users.noreply.github.com> Date: Sat, 12 Oct 2024 03:21:10 +1100 Subject: [PATCH] Typescript LGraphCanvas (#202) * Format only * Revert accidental change * Fix redundant falsy check - uninit. var * nit - Refactor const/let * nit - Refactor const/let (manual) * nit - Redeclared params * Add TS types & minor refactor only * Refactor - Clean up / reformat - Add strings.ts helper functions - Remove unused vars & local function params - Simplifies code - Rename vars for clarity - Add TODOs and other comments - Add ts-expect-error * Redirect draw.ts enums to global file (temp.) Should be revisited after TS merge complete Corrects import of types from draw.ts into interfaces * Add measure.ts - move util funcs from Global * Add all imports required for TS conversion * Refactor - TS narrowing * nit - TS types & minor refactor * Add missing types from recent PRs Removes duplicate declarations Fixes some type mismatches * nit - Refactor recent PRs * Revert incorrect decls backported * Remove unused params * Add TS types only * Fix minor TS type coercion issues Also removes redundant code * nit - Refactor * Remove @ts-nocheck * Fix refactor regression - drag link to output Issue was the result of fixing var declared outside of closure * Restore original logic --------- Co-authored-by: huchenlei --- src/LGraphCanvas.ts | 7919 ++++++++++++++++++-------------------- src/LGraphGroup.ts | 2 +- src/LGraphNode.ts | 7 +- src/LLink.ts | 1 + src/LiteGraphGlobal.ts | 32 +- src/draw.ts | 27 +- src/interfaces.ts | 4 +- src/measure.ts | 188 + src/strings.ts | 17 + src/types/globalEnums.ts | 1 + 10 files changed, 4005 insertions(+), 4193 deletions(-) create mode 100644 src/measure.ts create mode 100644 src/strings.ts diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 891fa687..0cf39e34 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -1,21 +1,24 @@ -// @ts-nocheck -import type { INodeSlot, INodeInputSlot, INodeOutputSlot, Point, Rect, Size, ISlotType, CanvasColour, Dictionary, Rect32, ConnectingLink, IOptionalInputsData, IContextMenuValue } from "./interfaces" -import type { CanvasDragEvent, CanvasMouseEvent } from "./types/events" -import type { RenderShape, TitleMode } from "./types/globalEnums" -import type { IWidget } from "./types/widgets" -import { LLink } from "./LLink" -import { LGraphGroup } from "./LGraphGroup" -import { DragAndScale } from "./DragAndScale"; -import { drawSlot, LabelPosition } from "./draw"; -import { LGraphNode, LiteGraph, clamp } from "./litegraph"; -import { isInsideRectangle, distance, overlapBounding, LiteGraphGlobal } from "./LiteGraphGlobal"; -import { LGraphNode, NodeId } from "./LGraphNode" -import { LGraph } from "./LGraph" +import type { CanvasColour, Dictionary, Direction, IBoundaryNodes, IContextMenuOptions, INodeSlot, INodeInputSlot, INodeOutputSlot, IOptionalInputsData, Point, Rect, Rect32, Size, IContextMenuValue, ISlotType, ConnectingLink } from "./interfaces" +import type { IWidget, TWidgetValue } from "./types/widgets" +import type { LGraphNode, NodeId } from "./LGraphNode" +import type { CanvasDragEvent, CanvasMouseEvent, CanvasWheelEvent } from "./types/events" +import type { LinkDirection, RenderShape, TitleMode } from "./types/globalEnums" +import type { IClipboardContents } from "./types/serialisation" +import type { LLink } from "./LLink" +import type { LGraphGroup } from "./LGraphGroup" +import type { LGraph } from "./LGraph" +import type { ContextMenu } from "./ContextMenu" +import { LiteGraphGlobal } from "./LiteGraphGlobal" +import { isInsideRectangle, distance, overlapBounding, isPointInRectangle } from "./measure" +import { drawSlot, LabelPosition } from "./draw" +import { DragAndScale } from "./DragAndScale" +import { LiteGraph, clamp } from "./litegraph" +import { stringOrNull } from "./strings" interface IShowSearchOptions { node_to?: LGraphNode node_from?: LGraphNode - slot_from: INodeOutputSlot | INodeInputSlot + slot_from: number | INodeOutputSlot | INodeInputSlot type_filter_in?: ISlotType type_filter_out?: ISlotType @@ -32,11 +35,11 @@ interface INodeFromTo { // input nodeFrom?: LGraphNode // input - slotFrom?: INodeOutputSlot | INodeInputSlot + slotFrom?: number | INodeOutputSlot | INodeInputSlot // output nodeTo?: LGraphNode // output - slotTo?: INodeOutputSlot | INodeInputSlot + slotTo?: number | INodeOutputSlot | INodeInputSlot // pass the event coords } @@ -90,29 +93,28 @@ interface IDrawSelectionBoundingOptions { * @param {LGraph} graph [optional] * @param {Object} options [optional] { skip_rendering, autoresize, viewport } */ - export class LGraphCanvas { /* Interaction */ - static #temp = new Float32Array(4); - static #temp_vec2 = new Float32Array(2); - static #tmp_area = new Float32Array(4); - static #margin_area = new Float32Array(4); - static #link_bounding = new Float32Array(4); - static #tempA = new Float32Array(2); - static #tempB = new Float32Array(2); + static #temp = new Float32Array(4) + static #temp_vec2 = new Float32Array(2) + static #tmp_area = new Float32Array(4) + static #margin_area = new Float32Array(4) + static #link_bounding = new Float32Array(4) + static #tempA = new Float32Array(2) + static #tempB = new Float32Array(2) - static DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; + static DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=" // TODO: Remove workaround - this should be instance-based, regardless. "-1" formerly pointed to LiteGraph.EVENT_LINK_COLOR. - static link_type_colors = { + static link_type_colors: Record = { "-1": LiteGraphGlobal.DEFAULT_EVENT_LINK_COLOR, number: "#AAA", node: "#DCA" - }; - static gradients = {}; //cache of gradients + } + static gradients = {} //cache of gradients - static search_limit = -1; + static search_limit = -1 static node_colors = { red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" }, brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, @@ -127,39 +129,38 @@ export class LGraphCanvas { purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } - }; - public pointer_is_down: boolean = false; + } - private _dragging_canvas: boolean = false; + private _dragging_canvas: boolean = false get dragging_canvas(): boolean { - return this._dragging_canvas; + return this._dragging_canvas } set dragging_canvas(value: boolean) { if (value !== this._dragging_canvas) { - this._dragging_canvas = value; + this._dragging_canvas = value this.emitEvent({ subType: "dragging-canvas", draggingCanvas: value - }); + }) } } // Whether the canvas was previously being dragged prior to pressing space key. // null if space key is not pressed. - private _previously_dragging_canvas: boolean | null = null; + private _previously_dragging_canvas: boolean | null = null // if set to true users cannot modify the graph - private _read_only: boolean = false; + private _read_only: boolean = false get read_only(): boolean { - return this._read_only; + return this._read_only } set read_only(value: boolean) { if (value !== this._read_only) { - this._read_only = value; + this._read_only = value this.emitEvent({ subType: "read-only", readOnly: value - }); + }) } } options: { skip_events?: any; viewport?: any; skip_render?: any; autoresize?: any } @@ -196,6 +197,7 @@ export class LGraphCanvas { drag_mode: boolean dragging_rectangle?: Rect filter?: string + set_canvas_dirty_on_mouse_event: boolean always_render_background: boolean render_shadows: boolean render_canvas_border: boolean @@ -210,6 +212,7 @@ export class LGraphCanvas { links_render_mode: number mouse: Point graph_mouse: Point + canvas_mouse: Point onSearchBox?: (helper: Element, str: string, canvas: LGraphCanvas) => any onSearchBoxSelection?: (name: any, event: any, canvas: LGraphCanvas) => void onMouse?: (e: CanvasMouseEvent) => boolean @@ -239,6 +242,8 @@ export class LGraphCanvas { node_over?: LGraphNode node_capturing_input?: LGraphNode highlighted_links: Dictionary + link_over_widget?: IWidget + link_over_widget_type?: string dirty_canvas: boolean dirty_bgcanvas: boolean @@ -274,7 +279,9 @@ export class LGraphCanvas { last_mouse_dragging: boolean onMouseDown: (arg0: CanvasMouseEvent) => void _highlight_pos?: Point - _highlight_input?: INodeInputSlot + _highlight_input?: Point + _highlight_input_slot?: INodeInputSlot + _highlight_output?: Point // TODO: Check if panels are used node_panel options_panel @@ -310,141 +317,137 @@ export class LGraphCanvas { visible_rect?: Rect constructor(canvas: HTMLCanvasElement, graph: LGraph, options?: { viewport?: any; skip_events?: any; skip_render?: any; autoresize?: any }) { - this.options = options = options || {}; + this.options = options = options || {} //if(graph === undefined) // throw ("No graph assigned"); - this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE; + this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE if (canvas && canvas.constructor === String) { - canvas = document.querySelector(canvas); + canvas = document.querySelector(canvas) } - this.ds = new DragAndScale(); - this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much - this.zoom_speed = 1.1; // in range (1.01, 2.5). Less than 1 will invert the zoom direction + this.ds = new DragAndScale() + this.zoom_modify_alpha = true //otherwise it generates ugly patterns when scaling down too much + this.zoom_speed = 1.1 // in range (1.01, 2.5). Less than 1 will invert the zoom direction - this.title_text_font = "" + LiteGraph.NODE_TEXT_SIZE + "px Arial"; + this.title_text_font = "" + LiteGraph.NODE_TEXT_SIZE + "px Arial" this.inner_text_font = - "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial"; - this.node_title_color = LiteGraph.NODE_TITLE_COLOR; - this.default_link_color = LiteGraph.LINK_COLOR; + "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial" + this.node_title_color = LiteGraph.NODE_TITLE_COLOR + this.default_link_color = LiteGraph.LINK_COLOR this.default_connection_color = { input_off: "#778", input_on: "#7F7", //"#BBD" output_off: "#778", output_on: "#7F7" //"#BBD" - }; + } this.default_connection_color_byType = { /*number: "#7F7", string: "#77F", boolean: "#F77",*/ - }; + } this.default_connection_color_byTypeOff = { /*number: "#474", string: "#447", boolean: "#744",*/ - }; - - this.highquality_render = true; - this.use_gradients = false; //set to true to render titlebar with gradients - this.editor_alpha = 1; //used for transition - this.pause_rendering = false; - this.clear_background = true; - this.clear_background_color = "#222"; - - this.render_only_selected = true; - this.live_mode = false; - this.show_info = true; - this.allow_dragcanvas = true; - this.allow_dragnodes = true; - this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc - this.multi_select = false; //allow selecting multi nodes without pressing extra keys - this.allow_searchbox = true; - this.allow_reconnect_links = true; //allows to change a connection with having to redo it again - this.align_to_grid = false; //snap to grid - - this.drag_mode = false; - this.dragging_rectangle = null; - - this.filter = null; //allows to filter to only accept some type of nodes in a graph - - this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas on mouse events (except move) - this.always_render_background = false; - this.render_shadows = true; - this.render_canvas_border = true; - this.render_connections_shadows = false; //too much cpu - this.render_connections_border = true; - this.render_curved_connections = false; - this.render_connection_arrows = false; - this.render_collapsed_slots = true; - this.render_execution_order = false; - this.render_title_colored = true; - this.render_link_tooltip = true; - - this.links_render_mode = LiteGraph.SPLINE_LINK; - - this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle - this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle - this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD - + } + this.highquality_render = true + this.use_gradients = false //set to true to render titlebar with gradients + this.editor_alpha = 1 //used for transition + this.pause_rendering = false + this.clear_background = true + this.clear_background_color = "#222" + + this.render_only_selected = true + this.live_mode = false + this.show_info = true + this.allow_dragcanvas = true + this.allow_dragnodes = true + this.allow_interaction = true //allow to control widgets, buttons, collapse, etc + this.multi_select = false //allow selecting multi nodes without pressing extra keys + this.allow_searchbox = true + this.allow_reconnect_links = true //allows to change a connection with having to redo it again + this.align_to_grid = false //snap to grid + + this.drag_mode = false + this.dragging_rectangle = null + + this.filter = null //allows to filter to only accept some type of nodes in a graph + + this.set_canvas_dirty_on_mouse_event = true //forces to redraw the canvas on mouse events (except move) + this.always_render_background = false + this.render_shadows = true + this.render_canvas_border = true + this.render_connections_shadows = false //too much cpu + this.render_connections_border = true + this.render_curved_connections = false + this.render_connection_arrows = false + this.render_collapsed_slots = true + this.render_execution_order = false + this.render_title_colored = true + this.render_link_tooltip = true + + this.links_render_mode = LiteGraph.SPLINE_LINK + + this.mouse = [0, 0] //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle + this.graph_mouse = [0, 0] //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle + this.canvas_mouse = this.graph_mouse //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD //to personalize the search box - this.onSearchBox = null; - this.onSearchBoxSelection = null; + this.onSearchBox = null + this.onSearchBoxSelection = null //callbacks - this.onMouse = null; - this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform - this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform - this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs) - this.onDrawLinkTooltip = null; //called when rendering a tooltip - this.onNodeMoved = null; //called after moving a node - this.onSelectionChange = null; //called if the selection changes - this.onConnectingChange = null; //called before any link changes - this.onBeforeChange = null; //called before modifying the graph - this.onAfterChange = null; //called after modifying the graph - - this.connections_width = 3; - this.round_radius = 8; - - this.current_node = null; - this.node_widget = null; //used for widgets - this.over_link_center = null; - this.last_mouse_position = [0, 0]; - this.visible_area = this.ds.visible_area; - this.visible_links = []; - this.connecting_links = null; // Explicitly null-checked - - this.viewport = options.viewport || null; //to constraint render area to a portion of the canvas - - + this.onMouse = null + this.onDrawBackground = null //to render background objects (behind nodes and connections) in the canvas affected by transform + this.onDrawForeground = null //to render foreground objects (above nodes and connections) in the canvas affected by transform + this.onDrawOverlay = null //to render foreground objects not affected by transform (for GUIs) + this.onDrawLinkTooltip = null //called when rendering a tooltip + this.onNodeMoved = null //called after moving a node + this.onSelectionChange = null //called if the selection changes + // FIXME: Typo, does nothing + // @ts-expect-error + this.onConnectingChange = null //called before any link changes + this.onBeforeChange = null //called before modifying the graph + this.onAfterChange = null //called after modifying the graph + + this.connections_width = 3 + this.round_radius = 8 + + this.current_node = null + this.node_widget = null //used for widgets + this.over_link_center = null + this.last_mouse_position = [0, 0] + this.visible_area = this.ds.visible_area + this.visible_links = [] + this.connecting_links = null // Explicitly null-checked + + this.viewport = options.viewport || null //to constraint render area to a portion of the canvas //link canvas and graph - if (graph) { - graph.attachCanvas(this); - } + graph?.attachCanvas(this) - this.setCanvas(canvas, options.skip_events); - this.clear(); + this.setCanvas(canvas, options.skip_events) + this.clear() if (!options.skip_render) { - this.startRendering(); + this.startRendering() } - this.autoresize = options.autoresize; + this.autoresize = options.autoresize } static getFileExtension(url: string): string { - var question = url.indexOf("?"); + const question = url.indexOf("?") if (question != -1) { - url = url.substr(0, question); + url = url.substr(0, question) } - var point = url.lastIndexOf("."); + const point = url.lastIndexOf(".") if (point == -1) { - return ""; + return "" } - return url.substr(point + 1).toLowerCase(); + return url.substr(point + 1).toLowerCase() } /* this is an implementation for touch not in production and not ready */ @@ -504,39 +507,38 @@ export class LGraphCanvas { };*/ /* CONTEXT MENU ********************/ static onGroupAdd(info: unknown, entry: unknown, mouse_event: MouseEvent): void { - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); + const canvas = LGraphCanvas.active_canvas - var group = new LiteGraph.LGraphGroup(); - group.pos = canvas.convertEventToCanvasOffset(mouse_event); - canvas.graph.add(group); + const group = new LiteGraph.LGraphGroup() + group.pos = canvas.convertEventToCanvasOffset(mouse_event) + canvas.graph.add(group) } /** - * Determines the furthest nodes in each direction - * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted - * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} - */ + * Determines the furthest nodes in each direction + * @param {Dictionary} nodes the nodes to from which boundary nodes will be extracted + * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} + */ static getBoundaryNodes(nodes: LGraphNode[] | Dictionary): IBoundaryNodes { - let top = null; - let right = null; - let bottom = null; - let left = null; + let top = null + let right = null + let bottom = null + let left = null for (const nID in nodes) { - const node = nodes[nID]; - const [x, y] = node.pos; - const [width, height] = node.size; + const node = nodes[nID] + const [x, y] = node.pos + const [width, height] = node.size if (top === null || y < top.pos[1]) { - top = node; + top = node } if (right === null || x + width > right.pos[0] + right.size[0]) { - right = node; + right = node } if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) { - bottom = node; + bottom = node } if (left === null || x < left.pos[0]) { - left = node; + left = node } } @@ -545,61 +547,61 @@ export class LGraphCanvas { "right": right, "bottom": bottom, "left": left - }; + } } /** - * - * @param {LGraphNode[]} nodes a list of nodes - * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes - * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction) - */ + * + * @param {Dictionary} nodes a list of nodes + * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes + * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction) + */ static alignNodes(nodes: Dictionary, direction: Direction, align_to?: LGraphNode): void { if (!nodes) { - return; + return } - const canvas = LGraphCanvas.active_canvas; - let boundaryNodes = []; + const canvas = LGraphCanvas.active_canvas + let boundaryNodes: IBoundaryNodes if (align_to === undefined) { - boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes); + boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes) } else { boundaryNodes = { "top": align_to, "right": align_to, "bottom": align_to, "left": align_to - }; + } } - for (const [_, node] of Object.entries(canvas.selected_nodes)) { + for (const node of Object.values(canvas.selected_nodes)) { switch (direction) { case "right": - node.pos[0] = boundaryNodes["right"].pos[0] + boundaryNodes["right"].size[0] - node.size[0]; - break; + node.pos[0] = boundaryNodes["right"].pos[0] + boundaryNodes["right"].size[0] - node.size[0] + break case "left": - node.pos[0] = boundaryNodes["left"].pos[0]; - break; + node.pos[0] = boundaryNodes["left"].pos[0] + break case "top": - node.pos[1] = boundaryNodes["top"].pos[1]; - break; + node.pos[1] = boundaryNodes["top"].pos[1] + break case "bottom": - node.pos[1] = boundaryNodes["bottom"].pos[1] + boundaryNodes["bottom"].size[1] - node.size[1]; - break; + node.pos[1] = boundaryNodes["bottom"].pos[1] + boundaryNodes["bottom"].size[1] - node.size[1] + break } } - canvas.dirty_canvas = true; - canvas.dirty_bgcanvas = true; + canvas.dirty_canvas = true + canvas.dirty_bgcanvas = true } static onNodeAlign(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): void { new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { event: event, callback: inner_clicked, parentMenu: prev_menu, - }); + }) - function inner_clicked(value) { - LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node); + function inner_clicked(value: string) { + LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, (value.toLowerCase() as Direction), node) } } static onGroupAlign(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu): void { @@ -607,136 +609,135 @@ export class LGraphCanvas { event: event, callback: inner_clicked, parentMenu: prev_menu, - }); + }) function inner_clicked(value) { - LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase()); + LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase()) } } static onMenuAdd(node: LGraphNode, options: IContextMenuOptions, e: MouseEvent, prev_menu: ContextMenu, callback?: (node: LGraphNode) => void): boolean { - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - var graph = canvas.graph; + const canvas = LGraphCanvas.active_canvas + const ref_window = canvas.getCanvasWindow() + const graph = canvas.graph if (!graph) - return; + return - function inner_onMenuAdded(base_category, prev_menu) { + function inner_onMenuAdded(base_category: string, prev_menu: ContextMenu): void { - var categories = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function (category) { return category.startsWith(base_category); }); - var entries = []; + const categories = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function (category) { return category.startsWith(base_category) }) + const entries = [] categories.map(function (category) { if (!category) - return; + return - var base_category_regex = new RegExp('^(' + base_category + ')'); - var category_name = category.replace(base_category_regex, "").split('/')[0]; - var category_path = base_category === '' ? category_name + '/' : base_category + category_name + '/'; + const base_category_regex = new RegExp('^(' + base_category + ')') + const category_name = category.replace(base_category_regex, "").split('/')[0] + const category_path = base_category === '' ? category_name + '/' : base_category + category_name + '/' - var name = category_name; + let name = category_name if (name.indexOf("::") != -1) //in case it has a namespace like "shader::math/rand" it hides the namespace - name = name.split("::")[1]; + name = name.split("::")[1] - var index = entries.findIndex(function (entry) { return entry.value === category_path; }); + const index = entries.findIndex(function (entry) { return entry.value === category_path }) if (index === -1) { entries.push({ value: category_path, content: name, has_submenu: true, callback: function (value, event, mouseEvent, contextMenu) { - inner_onMenuAdded(value.value, contextMenu); + inner_onMenuAdded(value.value, contextMenu) } - }); + }) } - }); + }) - var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter); + const nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter) nodes.map(function (node) { if (node.skip_list) - return; + return - var entry = { + const entry = { value: node.type, content: node.title, has_submenu: false, callback: function (value, event, mouseEvent, contextMenu) { - var first_event = contextMenu.getFirstEvent(); - canvas.graph.beforeChange(); - var node = LiteGraph.createNode(value.value); + const first_event = contextMenu.getFirstEvent() + canvas.graph.beforeChange() + const node = LiteGraph.createNode(value.value) if (node) { - node.pos = canvas.convertEventToCanvasOffset(first_event); - canvas.graph.add(node); + node.pos = canvas.convertEventToCanvasOffset(first_event) + canvas.graph.add(node) } - if (callback) - callback(node); - canvas.graph.afterChange(); + + callback?.(node) + canvas.graph.afterChange() } - }; + } - entries.push(entry); + entries.push(entry) - }); + }) - new LiteGraph.ContextMenu(entries, { event: e, parentMenu: prev_menu }, ref_window); + // @ts-expect-error Remove param ref_window - unused + new LiteGraph.ContextMenu(entries, { event: e, parentMenu: prev_menu }, ref_window) } - inner_onMenuAdded('', prev_menu); - return false; + inner_onMenuAdded('', prev_menu) + return false } + static onMenuCollapseAll() { } static onMenuNodeEdit() { } - static showMenuNodeOptionalInputs(v: unknown, options: unknown, e: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): boolean { - if (!node) { - return; - } - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); + /** @param options Parameter is never used */ + static showMenuNodeOptionalInputs(v: unknown, options: INodeInputSlot[], e: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): boolean { + if (!node) return - var options = node.optional_inputs; - if (node.onGetInputs) { - options = node.onGetInputs(); - } + // FIXME: Static function this + const that = this + const canvas = LGraphCanvas.active_canvas + const ref_window = canvas.getCanvasWindow() - var entries = []; + options = node.onGetInputs + ? node.onGetInputs() + : node.optional_inputs + + let entries: IOptionalInputsData[] = [] if (options) { - for (var i = 0; i < options.length; i++) { - var entry = options[i]; + for (let i = 0; i < options.length; i++) { + const entry = options[i] if (!entry) { - entries.push(null); - continue; + entries.push(null) + continue } - var label = entry[0]; - if (!entry[2]) - entry[2] = {}; + let label = entry[0] + entry[2] ||= {} if (entry[2].label) { - label = entry[2].label; + label = entry[2].label } - entry[2].removable = true; - var data = { content: label, value: entry }; + entry[2].removable = true + const data: IOptionalInputsData = { content: label, value: entry } if (entry[1] == LiteGraph.ACTION) { - data.className = "event"; + data.className = "event" } - entries.push(data); + entries.push(data) } } - if (node.onMenuNodeInputs) { - var retEntries = node.onMenuNodeInputs(entries); - if (retEntries) entries = retEntries; - } + const retEntries = node.onMenuNodeInputs?.(entries) + if (retEntries) entries = retEntries if (!entries.length) { - console.log("no input entries"); - return; + console.log("no input entries") + return } - var menu = new LiteGraph.ContextMenu( + new LiteGraph.ContextMenu( entries, { event: e, @@ -744,95 +745,87 @@ export class LGraphCanvas { parentMenu: prev_menu, node: node }, + // @ts-expect-error Unused param ref_window - ); + ) function inner_clicked(v, e, prev) { if (!node) { - return; + return } - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } + v.callback?.call(that, node, v, e, prev) if (v.value) { - node.graph.beforeChange(); - node.addInput(v.value[0], v.value[1], v.value[2]); + node.graph.beforeChange() + node.addInput(v.value[0], v.value[1], v.value[2]) - if (node.onNodeInputAdd) { // callback to the node when adding a slot - node.onNodeInputAdd(v.value); - } - node.setDirtyCanvas(true, true); - node.graph.afterChange(); + // callback to the node when adding a slot + node.onNodeInputAdd?.(v.value) + node.setDirtyCanvas(true, true) + node.graph.afterChange() } } - return false; + return false } - static showMenuNodeOptionalOutputs(v: unknown, options: unknown, e: unknown, prev_menu: ContextMenu, node: LGraphNode): boolean { - if (!node) { - return; - } - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); + /** @param options Parameter is never used */ + static showMenuNodeOptionalOutputs(v: unknown, options: INodeOutputSlot[], e: unknown, prev_menu: ContextMenu, node: LGraphNode): boolean { + if (!node) return - var options = node.optional_outputs; - if (node.onGetOutputs) { - options = node.onGetOutputs(); - } + const that = this + const canvas = LGraphCanvas.active_canvas + const ref_window = canvas.getCanvasWindow() + + options = node.onGetOutputs + ? node.onGetOutputs() + : node.optional_outputs - var entries = []; + let entries: IOptionalInputsData[] = [] if (options) { - for (var i = 0; i < options.length; i++) { - var entry = options[i]; + for (let i = 0; i < options.length; i++) { + const entry = options[i] if (!entry) { //separator? - entries.push(null); - continue; + entries.push(null) + continue } if (node.flags && node.flags.skip_repeated_outputs && node.findOutputSlot(entry[0]) != -1) { - continue; + continue } //skip the ones already on - var label = entry[0]; - if (!entry[2]) - entry[2] = {}; + let label = entry[0] + entry[2] ||= {} if (entry[2].label) { - label = entry[2].label; + label = entry[2].label } - entry[2].removable = true; - var data = { content: label, value: entry }; + entry[2].removable = true + const data: IOptionalInputsData = { content: label, value: entry } if (entry[1] == LiteGraph.EVENT) { - data.className = "event"; + data.className = "event" } - entries.push(data); + entries.push(data) } } - if (this.onMenuNodeOutputs) { - entries = this.onMenuNodeOutputs(entries); - } + if (this.onMenuNodeOutputs) entries = this.onMenuNodeOutputs(entries) if (LiteGraph.do_add_triggers_slots) { //canvas.allow_addOutSlot_onExecuted if (node.findOutputSlot("onExecuted") == -1) { - entries.push({ content: "On Executed", value: ["onExecuted", LiteGraph.EVENT, { nameLocked: true }], className: "event" }); //, opts: {} + entries.push({ content: "On Executed", value: ["onExecuted", LiteGraph.EVENT, { nameLocked: true }], className: "event" }) //, opts: {} } } // add callback for modifing the menu elements onMenuNodeOutputs - if (node.onMenuNodeOutputs) { - var retEntries = node.onMenuNodeOutputs(entries); - if (retEntries) entries = retEntries; - } + const retEntries = node.onMenuNodeOutputs?.(entries) + if (retEntries) entries = retEntries if (!entries.length) { - return; + return } - var menu = new LiteGraph.ContextMenu( + new LiteGraph.ContextMenu( entries, { event: e, @@ -840,87 +833,87 @@ export class LGraphCanvas { parentMenu: prev_menu, node: node }, + // @ts-expect-error Unused ref_window - ); + ) function inner_clicked(v, e, prev) { if (!node) { - return; + return } if (v.callback) { - v.callback.call(that, node, v, e, prev); + // TODO: This is a static method, so the below "that" appears broken. + v.callback.call(that, node, v, e, prev) } if (!v.value) { - return; + return } - var value = v.value[1]; + const value = v.value[1] if (value && (value.constructor === Object || value.constructor === Array)) { //submenu why? - var entries = []; - for (var i in value) { - entries.push({ content: i, value: value[i] }); + const entries = [] + for (const i in value) { + entries.push({ content: i, value: value[i] }) } new LiteGraph.ContextMenu(entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node - }); - return false; + }) + return false } else { - node.graph.beforeChange(); - node.addOutput(v.value[0], v.value[1], v.value[2]); + node.graph.beforeChange() + node.addOutput(v.value[0], v.value[1], v.value[2]) - if (node.onNodeOutputAdd) { // a callback to the node when adding a slot - node.onNodeOutputAdd(v.value); - } - node.setDirtyCanvas(true, true); - node.graph.afterChange(); + // a callback to the node when adding a slot + node.onNodeOutputAdd?.(v.value) + node.setDirtyCanvas(true, true) + node.graph.afterChange() } } - return false; + return false } + + /** @param value Parameter is never used */ static onShowMenuNodeProperties(value: unknown, options: unknown, e: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): boolean { - if (!node || !node.properties) { - return; - } + if (!node || !node.properties) return - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); + const canvas = LGraphCanvas.active_canvas + const ref_window = canvas.getCanvasWindow() - var entries = []; - for (var i in node.properties) { - var value = node.properties[i] !== undefined ? node.properties[i] : " "; + const entries = [] + for (const i in node.properties) { + value = node.properties[i] !== undefined ? node.properties[i] : " " if (typeof value == "object") - value = JSON.stringify(value); - var info = node.getPropertyInfo(i); + value = JSON.stringify(value) + const info = node.getPropertyInfo(i) if (info.type == "enum" || info.type == "combo") - value = LGraphCanvas.getPropertyPrintableValue(value, info.values); + value = LGraphCanvas.getPropertyPrintableValue(value, info.values) //value could contain invalid html characters, clean that - value = LGraphCanvas.decodeHTML(value); + value = LGraphCanvas.decodeHTML(stringOrNull(value)) entries.push({ content: "" + - (info.label ? info.label : i) + + (info.label || i) + "" + "" + value + "", value: i - }); + }) } if (!entries.length) { - return; + return } - var menu = new LiteGraph.ContextMenu( + new LiteGraph.ContextMenu( entries, { event: e, @@ -929,226 +922,220 @@ export class LGraphCanvas { allow_html: true, node: node }, + // @ts-expect-error Unused ref_window - ); + ) - function inner_clicked(v, options, e, prev) { - if (!node) { - return; - } - var rect = this.getBoundingClientRect(); + function inner_clicked(v: { value: any }) { + if (!node) return + + const rect = this.getBoundingClientRect() canvas.showEditPropertyValue(node, v.value, { position: [rect.left, rect.top] - }); + }) } - return false; + return false } static decodeHTML(str: string): string { - var e = document.createElement("div"); - e.innerText = str; - return e.innerHTML; + const e = document.createElement("div") + e.innerText = str + return e.innerHTML } static onMenuResizeNode(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { - if (!node) { - return; - } + if (!node) return - var fApplyMultiNode = function (node) { - node.size = node.computeSize(); - if (node.onResize) - node.onResize(node.size); - }; + const fApplyMultiNode = function (node: LGraphNode) { + node.size = node.computeSize() + node.onResize?.(node.size) + } - var graphcanvas = LGraphCanvas.active_canvas; + const graphcanvas = LGraphCanvas.active_canvas if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) { - fApplyMultiNode(node); + fApplyMultiNode(node) } else { - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); + for (const i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]) } } - node.setDirtyCanvas(true, true); + node.setDirtyCanvas(true, true) } // TODO refactor :: this is used fot title but not for properties! static onShowPropertyEditor(item: { property: string; type: string }, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { - var input_html = ""; - var property = item.property || "title"; - var value = node[property]; + const property = item.property || "title" + const value = node[property] + // TODO: Remove "any" kludge // TODO refactor :: use createDialog ? - var dialog = document.createElement("div"); - dialog.is_modified = false; - dialog.className = "graphdialog"; + const dialog: any = document.createElement("div") + dialog.is_modified = false + dialog.className = "graphdialog" dialog.innerHTML = - ""; + "" dialog.close = function () { - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - var title = dialog.querySelector(".name"); - title.innerText = property; - var input = dialog.querySelector(".value"); + dialog.parentNode?.removeChild(dialog) + } + const title = dialog.querySelector(".name") + title.innerText = property + const input = dialog.querySelector(".value") if (input) { - input.value = value; - input.addEventListener("blur", function (e) { - this.focus(); - }); - input.addEventListener("keydown", function (e) { - dialog.is_modified = true; + input.value = value + input.addEventListener("blur", function () { + this.focus() + }) + input.addEventListener("keydown", function (e: KeyboardEvent) { + dialog.is_modified = true if (e.keyCode == 27) { //ESC - dialog.close(); + dialog.close() } else if (e.keyCode == 13) { - inner(); // save + inner() // save + // @ts-expect-error Intentional - undefined if not present } else if (e.keyCode != 13 && e.target.localName != "textarea") { - return; + return } - e.preventDefault(); - e.stopPropagation(); - }); + e.preventDefault() + e.stopPropagation() + }) } - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; + const graphcanvas = LGraphCanvas.active_canvas + const canvas = graphcanvas.canvas - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; + const rect = canvas.getBoundingClientRect() + let offsetx = -20 + let offsety = -20 if (rect) { - offsetx -= rect.left; - offsety -= rect.top; + offsetx -= rect.left + offsety -= rect.top } - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; + if (e) { + dialog.style.left = e.clientX + offsetx + "px" + dialog.style.top = e.clientY + offsety + "px" } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; + dialog.style.left = canvas.width * 0.5 + offsetx + "px" + dialog.style.top = canvas.height * 0.5 + offsety + "px" } - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - canvas.parentNode.appendChild(dialog); + const button = dialog.querySelector("button") + button.addEventListener("click", inner) + canvas.parentNode.appendChild(dialog) - if (input) input.focus(); + input?.focus() - var dialogCloseTimer = null; - dialog.addEventListener("mouseleave", function (e) { + let dialogCloseTimer = null + dialog.addEventListener("mouseleave", function () { if (LiteGraph.dialog_close_on_mouse_leave) if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - dialog.addEventListener("mouseenter", function (e) { + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay) //dialog.close(); + }) + dialog.addEventListener("mouseenter", function () { if (LiteGraph.dialog_close_on_mouse_leave) - if (dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); + if (dialogCloseTimer) clearTimeout(dialogCloseTimer) + }) function inner() { - if (input) setValue(input.value); + if (input) setValue(input.value) } function setValue(value) { if (item.type == "Number") { - value = Number(value); + value = Number(value) } else if (item.type == "Boolean") { - value = Boolean(value); - } - node[property] = value; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); + value = Boolean(value) } - node.setDirtyCanvas(true, true); + node[property] = value + dialog.parentNode?.removeChild(dialog) + node.setDirtyCanvas(true, true) } } static getPropertyPrintableValue(value: unknown, values: unknown[] | object): string { if (!values) - return String(value); + return String(value) if (values.constructor === Array) { - return String(value); + return String(value) } if (values.constructor === Object) { - var desc_value = ""; - for (var k in values) { + let desc_value = "" + for (const k in values) { if (values[k] != value) - continue; - desc_value = k; - break; + continue + desc_value = k + break } - return String(value) + " (" + desc_value + ")"; + return String(value) + " (" + desc_value + ")" } } static onMenuNodeCollapse(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { - node.graph.beforeChange( /*?*/); + node.graph.beforeChange( /*?*/) - var fApplyMultiNode = function (node) { - node.collapse(); - }; + const fApplyMultiNode = function (node) { + node.collapse() + } - var graphcanvas = LGraphCanvas.active_canvas; + const graphcanvas = LGraphCanvas.active_canvas if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) { - fApplyMultiNode(node); + fApplyMultiNode(node) } else { - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); + for (const i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]) } } - node.graph.afterChange( /*?*/); + node.graph.afterChange( /*?*/) } + // eslint-disable-next-line @typescript-eslint/no-unused-vars static onMenuNodePin(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { } static onMenuNodeMode(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): boolean { new LiteGraph.ContextMenu( LiteGraph.NODE_MODES, { event: e, callback: inner_clicked, parentMenu: menu, node: node } - ); + ) function inner_clicked(v) { - if (!node) { - return; - } - var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v); - var fApplyMultiNode = function (node) { + if (!node) return + + const kV = Object.values(LiteGraph.NODE_MODES).indexOf(v) + const fApplyMultiNode = function (node) { if (kV >= 0 && LiteGraph.NODE_MODES[kV]) - node.changeMode(kV); + node.changeMode(kV) else { - console.warn("unexpected mode: " + v); - node.changeMode(LiteGraph.ALWAYS); + console.warn("unexpected mode: " + v) + node.changeMode(LiteGraph.ALWAYS) } - }; + } - var graphcanvas = LGraphCanvas.active_canvas; + const graphcanvas = LGraphCanvas.active_canvas if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) { - fApplyMultiNode(node); + fApplyMultiNode(node) } else { - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); + for (const i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]) } } } - return false; + return false } + + /** @param value Parameter is never used */ static onMenuNodeColors(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): boolean { - if (!node) { - throw "no node for color"; - } + if (!node) throw "no node for color" - var values = []; + const values: IContextMenuValue[] = [] values.push({ value: null, content: "No color" - }); + }) - for (var i in LGraphCanvas.node_colors) { - var color = LGraphCanvas.node_colors[i]; - var value = { + for (const i in LGraphCanvas.node_colors) { + const color = LGraphCanvas.node_colors[i] + value = { value: i, content: "" + i + "" - }; - values.push(value); + } + values.push(value) } new LiteGraph.ContextMenu(values, { event: e, callback: inner_clicked, parentMenu: menu, node: node - }); + }) function inner_clicked(v) { - if (!node) { - return; - } + if (!node) return - var color = v.value ? LGraphCanvas.node_colors[v.value] : null; + const color = v.value ? LGraphCanvas.node_colors[v.value] : null - var fApplyColor = function (node) { + const fApplyColor = function (node) { if (color) { if (node.constructor === LiteGraph.LGraphGroup) { - node.color = color.groupcolor; + node.color = color.groupcolor } else { - node.color = color.color; - node.bgcolor = color.bgcolor; + node.color = color.color + node.bgcolor = color.bgcolor } } else { - delete node.color; - delete node.bgcolor; + delete node.color + delete node.bgcolor } - }; + } - var graphcanvas = LGraphCanvas.active_canvas; + const graphcanvas = LGraphCanvas.active_canvas if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) { - fApplyColor(node); + fApplyColor(node) } else { - for (var i in graphcanvas.selected_nodes) { - fApplyColor(graphcanvas.selected_nodes[i]); + for (const i in graphcanvas.selected_nodes) { + fApplyColor(graphcanvas.selected_nodes[i]) } } - node.setDirtyCanvas(true, true); + node.setDirtyCanvas(true, true) } - return false; + return false } static onMenuNodeShapes(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): boolean { - if (!node) { - throw "no node passed"; - } + if (!node) throw "no node passed" new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, { event: e, callback: inner_clicked, parentMenu: menu, node: node - }); + }) function inner_clicked(v) { - if (!node) { - return; - } - node.graph.beforeChange( /*?*/); //node + if (!node) return + + node.graph.beforeChange( /*?*/) //node - var fApplyMultiNode = function (node) { - node.shape = v; - }; + const fApplyMultiNode = function (node) { + node.shape = v + } - var graphcanvas = LGraphCanvas.active_canvas; + const graphcanvas = LGraphCanvas.active_canvas if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) { - fApplyMultiNode(node); + fApplyMultiNode(node) } else { - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); + for (const i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]) } } - node.graph.afterChange( /*?*/); //node - node.setDirtyCanvas(true); + node.graph.afterChange( /*?*/) //node + node.setDirtyCanvas(true) } - return false; + return false } static onMenuNodeRemove(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { - if (!node) { - throw "no node passed"; - } + if (!node) throw "no node passed" - var graph = node.graph; - graph.beforeChange(); + const graph = node.graph + graph.beforeChange() + const fApplyMultiNode = function (node: LGraphNode) { + if (node.removable === false) return - var fApplyMultiNode = function (node) { - if (node.removable === false) { - return; - } - graph.remove(node); - }; + graph.remove(node) + } - var graphcanvas = LGraphCanvas.active_canvas; + const graphcanvas = LGraphCanvas.active_canvas if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) { - fApplyMultiNode(node); + fApplyMultiNode(node) } else { - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); + for (const i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]) } } - graph.afterChange(); - node.setDirtyCanvas(true, true); + graph.afterChange() + node.setDirtyCanvas(true, true) } static onMenuNodeToSubgraph(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { - var graph = node.graph; - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas) //?? - return; + const graph = node.graph + const graphcanvas = LGraphCanvas.active_canvas + if (!graphcanvas) return - var nodes_list = Object.values(graphcanvas.selected_nodes || {}); + let nodes_list = Object.values(graphcanvas.selected_nodes || {}) if (!nodes_list.length) - nodes_list = [node]; + nodes_list = [node] - var subgraph_node = LiteGraph.createNode("graph/subgraph"); - subgraph_node.pos = node.pos.concat(); - graph.add(subgraph_node); + const subgraph_node = LiteGraph.createNode("graph/subgraph") + // @ts-expect-error Refactor this to use typed array. + subgraph_node.pos = node.pos.concat() + graph.add(subgraph_node) - subgraph_node.buildFromNodes(nodes_list); + // @ts-expect-error Doesn't exist anywhere... + subgraph_node.buildFromNodes(nodes_list) - graphcanvas.deselectAllNodes(); - node.setDirtyCanvas(true, true); + graphcanvas.deselectAllNodes() + node.setDirtyCanvas(true, true) } static onMenuNodeClone(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { - node.graph.beforeChange(); + node.graph.beforeChange() - var newSelected = {}; + const newSelected: Dictionary = {} - var fApplyMultiNode = function (node) { - if (node.clonable === false) { - return; - } - var newnode = node.clone(); - if (!newnode) { - return; - } - newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; - node.graph.add(newnode); - newSelected[newnode.id] = newnode; - }; + const fApplyMultiNode = function (node) { + if (node.clonable === false) return + + const newnode = node.clone() + if (!newnode) return + + newnode.pos = [node.pos[0] + 5, node.pos[1] + 5] + node.graph.add(newnode) + newSelected[newnode.id] = newnode + } - var graphcanvas = LGraphCanvas.active_canvas; + const graphcanvas = LGraphCanvas.active_canvas if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) { - fApplyMultiNode(node); + fApplyMultiNode(node) } else { - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); + for (const i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]) } } if (Object.keys(newSelected).length) { - graphcanvas.selectNodes(newSelected); + graphcanvas.selectNodes(newSelected) } - node.graph.afterChange(); + node.graph.afterChange() - node.setDirtyCanvas(true, true); + node.setDirtyCanvas(true, true) } /** - * clears all the data inside - * - * @method clear - */ + * clears all the data inside + * + */ clear(): void { - this.frame = 0; - this.last_draw_time = 0; - this.render_time = 0; - this.fps = 0; + this.frame = 0 + this.last_draw_time = 0 + this.render_time = 0 + this.fps = 0 //this.scale = 1; //this.offset = [0,0]; - this.dragging_rectangle = null; + this.dragging_rectangle = null - this.selected_nodes = {}; - this.selected_group = null; + this.selected_nodes = {} + this.selected_group = null - this.visible_nodes = []; - this.node_dragged = null; - this.node_over = null; - this.node_capturing_input = null; - this.connecting_links = null; - this.highlighted_links = {}; + this.visible_nodes = [] + this.node_dragged = null + this.node_over = null + this.node_capturing_input = null + this.connecting_links = null + this.highlighted_links = {} - this.dragging_canvas = false; + this.dragging_canvas = false - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.dirty_area = null; + this.dirty_canvas = true + this.dirty_bgcanvas = true + this.dirty_area = null - this.node_in_panel = null; - this.node_widget = null; + this.node_in_panel = null + this.node_widget = null - this.last_mouse = [0, 0]; - this.last_mouseclick = 0; - this.pointer_is_down = false; - this.pointer_is_double = false; - this.visible_area.set([0, 0, 0, 0]); + this.last_mouse = [0, 0] + this.last_mouseclick = 0 + this.pointer_is_down = false + this.pointer_is_double = false + this.visible_area.set([0, 0, 0, 0]) - if (this.onClear) { - this.onClear(); - } + this.onClear?.() } /** - * assigns a graph, you can reassign graphs to the same canvas - * - * @method setGraph - * @param {LGraph} graph - */ + * assigns a graph, you can reassign graphs to the same canvas + * + * @param {LGraph} graph + */ setGraph(graph: LGraph, skip_clear: boolean): void { - if (this.graph == graph) { - return; - } + if (this.graph == graph) return - if (!skip_clear) { - this.clear(); - } + if (!skip_clear) this.clear() if (!graph && this.graph) { - this.graph.detachCanvas(this); - return; + this.graph.detachCanvas(this) + return } - graph.attachCanvas(this); + graph.attachCanvas(this) //remove the graph stack in case a subgraph was open - if (this._graph_stack) - this._graph_stack = null; + this._graph_stack &&= null - this.setDirty(true, true); + this.setDirty(true, true) } /** - * returns the top level graph (in case there are subgraphs open on the canvas) - * - * @method getTopGraph - * @return {LGraph} graph - */ + * returns the top level graph (in case there are subgraphs open on the canvas) + * + * @return {LGraph} graph + */ getTopGraph(): LGraph { - if (this._graph_stack.length) - return this._graph_stack[0]; - return this.graph; + return this._graph_stack.length + ? this._graph_stack[0] + : this.graph } /** - * opens a graph contained inside a node in the current graph - * - * @method openSubgraph - * @param {LGraph} graph - */ + * opens a graph contained inside a node in the current graph + * + * @param {LGraph} graph + */ openSubgraph(graph: LGraph): void { - if (!graph) { - throw "graph cannot be null"; - } + if (!graph) throw "graph cannot be null" - if (this.graph == graph) { - throw "graph cannot be the same"; - } + if (this.graph == graph) throw "graph cannot be the same" - this.clear(); + this.clear() if (this.graph) { - if (!this._graph_stack) { - this._graph_stack = []; - } - this._graph_stack.push(this.graph); + this._graph_stack ||= [] + this._graph_stack.push(this.graph) } - graph.attachCanvas(this); - this.checkPanels(); - this.setDirty(true, true); + graph.attachCanvas(this) + this.checkPanels() + this.setDirty(true, true) } /** - * closes a subgraph contained inside a node - * - * @method closeSubgraph - * @param {LGraph} assigns a graph - */ + * closes a subgraph contained inside a node + * + * @param {LGraph} assigns a graph + */ closeSubgraph(): void { - if (!this._graph_stack || this._graph_stack.length == 0) { - return; - } - var subgraph_node = this.graph._subgraph_node; - var graph = this._graph_stack.pop(); - this.selected_nodes = {}; - this.highlighted_links = {}; - graph.attachCanvas(this); - this.setDirty(true, true); + if (!this._graph_stack || this._graph_stack.length == 0) return + + const subgraph_node = this.graph._subgraph_node + const graph = this._graph_stack.pop() + this.selected_nodes = {} + this.highlighted_links = {} + graph.attachCanvas(this) + this.setDirty(true, true) if (subgraph_node) { - this.centerOnNode(subgraph_node); - this.selectNodes([subgraph_node]); + this.centerOnNode(subgraph_node) + this.selectNodes([subgraph_node]) } // when close sub graph back to offset [0, 0] scale 1 - this.ds.offset = [0, 0]; - this.ds.scale = 1; + this.ds.offset = [0, 0] + this.ds.scale = 1 } /** - * returns the visually active graph (in case there are more in the stack) - * @method getCurrentGraph - * @return {LGraph} the active graph - */ + * returns the visually active graph (in case there are more in the stack) + * @return {LGraph} the active graph + */ getCurrentGraph(): LGraph { - return this.graph; + return this.graph } /** - * assigns a canvas - * - * @method setCanvas - * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) - */ + * assigns a canvas + * + * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) + */ setCanvas(canvas?: string | HTMLCanvasElement, skip_events?: boolean) { - var that = this; - - if (canvas) { - if (canvas.constructor === String) { - canvas = document.getElementById(canvas); - if (!canvas) { - throw "Error creating LiteGraph canvas: Canvas not found"; - } + // TODO: Remove input mutation + if (canvas?.constructor === String) { + // @ts-expect-error + canvas = document.getElementById(canvas) + if (!canvas) { + throw "Error creating LiteGraph canvas: Canvas not found" } } - if (canvas === this.canvas) { - return; - } + if (canvas === this.canvas) return if (!canvas && this.canvas) { //maybe detach events from old_canvas if (!skip_events) { - this.unbindEvents(); + this.unbindEvents() } } - this.canvas = canvas; - this.ds.element = canvas; + // @ts-expect-error + this.canvas = canvas + // @ts-expect-error + this.ds.element = canvas - if (!canvas) { - return; - } + if (!canvas) return //this.canvas.tabindex = "1000"; - canvas.className += " lgraphcanvas"; - canvas.data = this; - canvas.tabindex = "1"; //to allow key events - - - - //bg canvas: used for non changing stuff - this.bgcanvas = null; + // @ts-expect-error + canvas.className += " lgraphcanvas" + // @ts-expect-error + canvas.data = this + // @ts-expect-error + canvas.tabindex = "1" //to allow key events + + // Background canvas: To render objects behind nodes (background, links, groups) + this.bgcanvas = null if (!this.bgcanvas) { - this.bgcanvas = document.createElement("canvas"); - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; + this.bgcanvas = document.createElement("canvas") + this.bgcanvas.width = this.canvas.width + this.bgcanvas.height = this.canvas.height } - + // @ts-expect-error if (canvas.getContext == null) { + // @ts-expect-error if (canvas.localName != "canvas") { throw "Element supplied for LGraphCanvas must be a element, you passed a " + - canvas.localName; + // @ts-expect-error + canvas.localName } - throw "This browser doesn't support Canvas"; + throw "This browser doesn't support Canvas" } - var ctx = (this.ctx = canvas.getContext("2d")); + // @ts-expect-error + const ctx = (this.ctx = canvas.getContext("2d")) if (ctx == null) { + // @ts-expect-error if (!canvas.webgl_enabled) { console.warn( "This canvas seems to be WebGL, enabling WebGL renderer" - ); + ) } - this.enableWebGL(); + this.enableWebGL() } - //input: (move and up could be unbinded) - // why here? this._mousemove_callback = this.processMouseMove.bind(this); - // why here? this._mouseup_callback = this.processMouseUp.bind(this); - if (!skip_events) { - this.bindEvents(); - } + if (!skip_events) this.bindEvents() } //used in some events to capture them _doNothing(e: Event) { //console.log("pointerevents: _doNothing "+e.type); - e.preventDefault(); - return false; + e.preventDefault() + return false } _doReturnTrue(e: Event) { - e.preventDefault(); - return true; + e.preventDefault() + return true } /** - * binds mouse, keyboard, touch and drag events to the canvas - * @method bindEvents - **/ + * binds mouse, keyboard, touch and drag events to the canvas + **/ bindEvents(): void { if (this._events_binded) { - console.warn("LGraphCanvas: events already binded"); - return; + console.warn("LGraphCanvas: events already binded") + return } //console.log("pointerevents: bindEvents"); - var canvas = this.canvas; + const canvas = this.canvas - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; //hack used when moving canvas between windows + const ref_window = this.getCanvasWindow() + const document = ref_window.document //hack used when moving canvas between windows - this._mousedown_callback = this.processMouseDown.bind(this); - this._mousewheel_callback = this.processMouseWheel.bind(this); + this._mousedown_callback = this.processMouseDown.bind(this) + this._mousewheel_callback = this.processMouseWheel.bind(this) // why mousemove and mouseup were not binded here? - this._mousemove_callback = this.processMouseMove.bind(this); - this._mouseup_callback = this.processMouseUp.bind(this); + this._mousemove_callback = this.processMouseMove.bind(this) + this._mouseup_callback = this.processMouseUp.bind(this) - //touch events -- TODO IMPLEMENT - //this._touch_callback = this.touchHandler.bind(this); - LiteGraph.pointerListenerAdd(canvas, "down", this._mousedown_callback, true); //down do not need to store the binded - canvas.addEventListener("mousewheel", this._mousewheel_callback, false); + LiteGraph.pointerListenerAdd(canvas, "down", this._mousedown_callback, true) //down do not need to store the binded + canvas.addEventListener("mousewheel", this._mousewheel_callback, false) - LiteGraph.pointerListenerAdd(canvas, "up", this._mouseup_callback, true); // CHECK: ??? binded or not - LiteGraph.pointerListenerAdd(canvas, "move", this._mousemove_callback); + LiteGraph.pointerListenerAdd(canvas, "up", this._mouseup_callback, true) // CHECK: ??? binded or not + LiteGraph.pointerListenerAdd(canvas, "move", this._mousemove_callback) - canvas.addEventListener("contextmenu", this._doNothing); + canvas.addEventListener("contextmenu", this._doNothing) canvas.addEventListener( "DOMMouseScroll", this._mousewheel_callback, false - ); + ) - //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents - /*if( 'touchstart' in document.documentElement ) - { - canvas.addEventListener("touchstart", this._touch_callback, true); - canvas.addEventListener("touchmove", this._touch_callback, true); - canvas.addEventListener("touchend", this._touch_callback, true); - canvas.addEventListener("touchcancel", this._touch_callback, true); - }*/ //Keyboard ****************** - this._key_callback = this.processKey.bind(this); - - canvas.addEventListener("keydown", this._key_callback, true); - document.addEventListener("keyup", this._key_callback, true); //in document, otherwise it doesn't fire keyup - + this._key_callback = this.processKey.bind(this) + canvas.addEventListener("keydown", this._key_callback, true) + document.addEventListener("keyup", this._key_callback, true) //in document, otherwise it doesn't fire keyup //Dropping Stuff over nodes ************************************ - this._ondrop_callback = this.processDrop.bind(this); + this._ondrop_callback = this.processDrop.bind(this) - canvas.addEventListener("dragover", this._doNothing, false); - canvas.addEventListener("dragend", this._doNothing, false); - canvas.addEventListener("drop", this._ondrop_callback, false); - canvas.addEventListener("dragenter", this._doReturnTrue, false); + canvas.addEventListener("dragover", this._doNothing, false) + canvas.addEventListener("dragend", this._doNothing, false) + canvas.addEventListener("drop", this._ondrop_callback, false) + canvas.addEventListener("dragenter", this._doReturnTrue, false) - this._events_binded = true; + this._events_binded = true } /** - * unbinds mouse events from the canvas - * @method unbindEvents - **/ + * unbinds mouse events from the canvas + **/ unbindEvents(): void { if (!this._events_binded) { - console.warn("LGraphCanvas: no events binded"); - return; + console.warn("LGraphCanvas: no events binded") + return } //console.log("pointerevents: unbindEvents"); - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; + const ref_window = this.getCanvasWindow() + const document = ref_window.document - LiteGraph.pointerListenerRemove(this.canvas, "move", this._mousemove_callback); - LiteGraph.pointerListenerRemove(this.canvas, "up", this._mouseup_callback); - LiteGraph.pointerListenerRemove(this.canvas, "down", this._mousedown_callback); + LiteGraph.pointerListenerRemove(this.canvas, "move", this._mousemove_callback) + LiteGraph.pointerListenerRemove(this.canvas, "up", this._mouseup_callback) + LiteGraph.pointerListenerRemove(this.canvas, "down", this._mousedown_callback) this.canvas.removeEventListener( "mousewheel", this._mousewheel_callback - ); + ) this.canvas.removeEventListener( "DOMMouseScroll", this._mousewheel_callback - ); - this.canvas.removeEventListener("keydown", this._key_callback); - document.removeEventListener("keyup", this._key_callback); - this.canvas.removeEventListener("contextmenu", this._doNothing); - this.canvas.removeEventListener("drop", this._ondrop_callback); - this.canvas.removeEventListener("dragenter", this._doReturnTrue); - - //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents - /*this.canvas.removeEventListener("touchstart", this._touch_callback ); - this.canvas.removeEventListener("touchmove", this._touch_callback ); - this.canvas.removeEventListener("touchend", this._touch_callback ); - this.canvas.removeEventListener("touchcancel", this._touch_callback );*/ - this._mousedown_callback = null; - this._mousewheel_callback = null; - this._key_callback = null; - this._ondrop_callback = null; - - this._events_binded = false; + ) + this.canvas.removeEventListener("keydown", this._key_callback) + document.removeEventListener("keyup", this._key_callback) + this.canvas.removeEventListener("contextmenu", this._doNothing) + this.canvas.removeEventListener("drop", this._ondrop_callback) + this.canvas.removeEventListener("dragenter", this._doReturnTrue) + + this._mousedown_callback = null + this._mousewheel_callback = null + this._key_callback = null + this._ondrop_callback = null + + this._events_binded = false } /** - * 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 - * @method enableWebGL - **/ + * 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 { + // TODO: Delete or move all webgl to a module and never load it. + // @ts-expect-error if (typeof GL === "undefined") { - throw "litegl.js must be included to use a WebGL canvas"; + throw "litegl.js must be included to use a WebGL canvas" } + // @ts-expect-error if (typeof enableWebGLCanvas === "undefined") { - throw "webglCanvas.js must be included to use this feature"; + throw "webglCanvas.js must be included to use this feature" } - this.gl = this.ctx = enableWebGLCanvas(this.canvas); - this.ctx.webgl = true; - this.bgcanvas = this.canvas; - this.bgctx = this.gl; - this.canvas.webgl_enabled = true; + // @ts-expect-error + this.gl = this.ctx = enableWebGLCanvas(this.canvas) + // @ts-expect-error + this.ctx.webgl = true + this.bgcanvas = this.canvas + this.bgctx = this.gl + // @ts-expect-error + this.canvas.webgl_enabled = true /* GL.create({ canvas: this.bgcanvas }); @@ -1682,65 +1620,56 @@ export class LGraphCanvas { */ } /** - * marks as dirty the canvas, this way it will be rendered again - * - * @class LGraphCanvas - * @method setDirty - * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) - * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) - */ + * marks as dirty the canvas, this way it will be rendered again + * + * @class LGraphCanvas + * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) + * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) + */ setDirty(fgcanvas: boolean, bgcanvas?: boolean): void { - if (fgcanvas) { - this.dirty_canvas = true; - } - if (bgcanvas) { - this.dirty_bgcanvas = true; - } + if (fgcanvas) this.dirty_canvas = true + if (bgcanvas) this.dirty_bgcanvas = true } /** - * Used to attach the canvas in a popup - * - * @method getCanvasWindow - * @return {window} returns the window where the canvas is attached (the DOM root node) - */ + * Used to attach the canvas in a popup + * + * @return {window} returns the window where the canvas is attached (the DOM root node) + */ getCanvasWindow(): Window { - if (!this.canvas) { - return window; - } - var doc = this.canvas.ownerDocument; - return doc.defaultView || doc.parentWindow; + if (!this.canvas) return window + + const doc = this.canvas.ownerDocument + // @ts-expect-error Check if required + return doc.defaultView || doc.parentWindow } /** - * starts rendering the content of the canvas when needed - * - * @method startRendering - */ + * starts rendering the content of the canvas when needed + * + */ startRendering(): void { - if (this.is_rendering) { - return; - } //already rendering + //already rendering + if (this.is_rendering) return - this.is_rendering = true; - renderFrame.call(this); + this.is_rendering = true + renderFrame.call(this) - function renderFrame() { + function renderFrame(this: LGraphCanvas) { if (!this.pause_rendering) { - this.draw(); + this.draw() } - var window = this.getCanvasWindow(); + const window = this.getCanvasWindow() if (this.is_rendering) { - window.requestAnimationFrame(renderFrame.bind(this)); + window.requestAnimationFrame(renderFrame.bind(this)) } } } /** - * stops rendering the content of the canvas (to save resources) - * - * @method stopRendering - */ + * stops rendering the content of the canvas (to save resources) + * + */ stopRendering(): void { - this.is_rendering = false; + this.is_rendering = false /* if(this.rendering_timer_id) { @@ -1752,8 +1681,8 @@ export class LGraphCanvas { /* LiteGraphCanvas input */ //used to block future mouse events (because of im gui) blockClick(): void { - this.block_click = true; - this.last_mouseclick = 0; + this.block_click = true + this.last_mouseclick = 0 } /** @@ -1762,16 +1691,16 @@ export class LGraphCanvas { * @returns The widget located at the current cursor position or null */ getWidgetAtCursor(node?: LGraphNode): IWidget | null { - node ??= this.node_over; + node ??= this.node_over - if (!node.widgets) return null; + if (!node.widgets) return null const graphPos = this.graph_mouse const x = graphPos[0] - node.pos[0] const y = graphPos[1] - node.pos[1] for (const widget of node.widgets) { - let widgetWidth, widgetHeight; + let widgetWidth, widgetHeight if (widget.computeSize) { ([widgetWidth, widgetHeight] = widget.computeSize(node.size[0])) } else { @@ -1786,135 +1715,118 @@ export class LGraphCanvas { y >= widget.last_y && y <= widget.last_y + widgetHeight ) { - return widget; + return widget } } - return null; + return null } processMouseDown(e: CanvasMouseEvent): boolean { if (this.set_canvas_dirty_on_mouse_event) - this.dirty_canvas = true; + this.dirty_canvas = true - if (!this.graph) { - return; - } + if (!this.graph) return - this.adjustMouseEvent(e); + this.adjustMouseEvent(e) - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - LGraphCanvas.active_canvas = this; - var that = this; + const ref_window = this.getCanvasWindow() + LGraphCanvas.active_canvas = this - var x = e.clientX; - var y = e.clientY; + const x = e.clientX + const y = e.clientY //console.log(y,this.viewport); //console.log("pointerevents: processMouseDown pointerId:"+e.pointerId+" which:"+e.which+" isPrimary:"+e.isPrimary+" :: x y "+x+" "+y); - this.ds.viewport = this.viewport; - var is_inside = !this.viewport || (this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3])); + this.ds.viewport = this.viewport + const is_inside = !this.viewport || (this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3])) //move mouse move event to the window in case it drags outside of the canvas if (!this.options.skip_events) { - LiteGraph.pointerListenerRemove(this.canvas, "move", this._mousemove_callback); - LiteGraph.pointerListenerAdd(ref_window.document, "move", this._mousemove_callback, true); //catch for the entire window - LiteGraph.pointerListenerAdd(ref_window.document, "up", this._mouseup_callback, true); - } - - if (!is_inside) { - return; - } - - var node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes, 5); - var skip_dragging = false; - var skip_action = false; - var now = LiteGraph.getTime(); - var is_primary = (e.isPrimary === undefined || !e.isPrimary); - var is_double_click = (now - this.last_mouseclick < 300); - this.mouse[0] = e.clientX; - this.mouse[1] = e.clientY; - this.graph_mouse[0] = e.canvasX; - this.graph_mouse[1] = e.canvasY; - this.last_click_position = [this.mouse[0], this.mouse[1]]; - - if (this.pointer_is_down && is_primary) { - this.pointer_is_double = true; - //console.log("pointerevents: pointer_is_double start"); - } else { - this.pointer_is_double = false; + LiteGraph.pointerListenerRemove(this.canvas, "move", this._mousemove_callback) + LiteGraph.pointerListenerAdd(ref_window.document, "move", this._mousemove_callback, true) //catch for the entire window + LiteGraph.pointerListenerAdd(ref_window.document, "up", this._mouseup_callback, true) } - this.pointer_is_down = true; + if (!is_inside) return - this.canvas.focus(); + let node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes, 5) + let skip_action = false + const now = LiteGraph.getTime() + // @ts-expect-error + const is_primary = (e.isPrimary === undefined || !e.isPrimary) + const is_double_click = (now - this.last_mouseclick < 300) + this.mouse[0] = e.clientX + this.mouse[1] = e.clientY + this.graph_mouse[0] = e.canvasX + this.graph_mouse[1] = e.canvasY + this.last_click_position = [this.mouse[0], this.mouse[1]] - LiteGraph.closeAllContextMenus(ref_window); + this.pointer_is_double = this.pointer_is_down && is_primary + this.pointer_is_down = true - if (this.onMouse) { - if (this.onMouse(e) == true) - return; - } + this.canvas.focus() + + LiteGraph.closeAllContextMenus(ref_window) + + if (this.onMouse?.(e) == true) return //left button mouse / single finger if (e.which == 1 && !this.pointer_is_double) { if ((e.metaKey || e.ctrlKey) && !e.altKey) { - this.dragging_rectangle = new Float32Array(4); - this.dragging_rectangle[0] = e.canvasX; - this.dragging_rectangle[1] = e.canvasY; - this.dragging_rectangle[2] = 1; - this.dragging_rectangle[3] = 1; - skip_action = true; + this.dragging_rectangle = new Float32Array(4) + this.dragging_rectangle[0] = e.canvasX + this.dragging_rectangle[1] = e.canvasY + this.dragging_rectangle[2] = 1 + this.dragging_rectangle[3] = 1 + skip_action = true } // clone node ALT dragging if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && !e.ctrlKey && node && this.allow_interaction && !skip_action && !this.read_only) { - const cloned = node.clone(); + const cloned = node.clone() if (cloned) { - cloned.pos[0] += 5; - cloned.pos[1] += 5; - this.graph.add(cloned, false, { doCalcSize: false }); - node = cloned; - skip_action = true; - if (!block_drag_node) { - if (this.allow_dragnodes) { - this.graph.beforeChange(); - this.node_dragged = node; - } - if (!this.selected_nodes[node.id]) { - this.processNodeSelected(node, e); - } + cloned.pos[0] += 5 + cloned.pos[1] += 5 + // @ts-expect-error Not impl. - harmless + this.graph.add(cloned, false, { doCalcSize: false }) + node = cloned + skip_action = true + + if (this.allow_dragnodes) { + this.graph.beforeChange() + this.node_dragged = node + } + if (!this.selected_nodes[node.id]) { + this.processNodeSelected(node, e) } } } - var clicking_canvas_bg = false; + let clicking_canvas_bg = false //when clicked on top of a node //and it is not interactive if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) { if (!this.live_mode && !node.flags.pinned) { - this.bringToFront(node); + this.bringToFront(node) } //if it wasn't selected? - - //not dragging mouse to connect two slots if (this.allow_interaction && !this.connecting_links && !node.flags.collapsed && !this.live_mode) { //Search for corner for resize if (!skip_action && node.resizable !== false && node.inResizeCorner(e.canvasX, e.canvasY)) { - this.graph.beforeChange(); - this.resizing_node = node; - this.canvas.style.cursor = "se-resize"; - skip_action = true; + this.graph.beforeChange() + this.resizing_node = node + this.canvas.style.cursor = "se-resize" + skip_action = true } else { //search for outputs if (node.outputs) { - for (var i = 0, l = node.outputs.length; i < l; ++i) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); + for (let i = 0, l = node.outputs.length; i < l; ++i) { + const output = node.outputs[i] + const link_pos = node.getConnectionPos(false, i) if (isInsideRectangle( e.canvasX, e.canvasY, @@ -1927,13 +1839,13 @@ export class LGraphCanvas { if (e.shiftKey) { if (output.links?.length > 0) { - this.connecting_links = []; + this.connecting_links = [] for (const linkId of output.links) { - const link = this.graph.links[linkId]; - const slot = link.target_slot; - const linked_node = this.graph._nodes_by_id[link.target_id]; - const input = linked_node.inputs[slot]; - const pos = linked_node.getConnectionPos(true, slot); + const link = this.graph.links[linkId] + const slot = link.target_slot + const linked_node = this.graph._nodes_by_id[link.target_id] + const input = linked_node.inputs[slot] + const pos = linked_node.getConnectionPos(true, slot) this.connecting_links.push({ node: linked_node, @@ -1941,15 +1853,15 @@ export class LGraphCanvas { input: input, output: null, pos: pos, - }); + }) } - skip_action = true; - break; + skip_action = true + break } } - output.slot_index = i; + output.slot_index = i this.connecting_links = [ { node: node, @@ -1958,39 +1870,35 @@ export class LGraphCanvas { output: output, pos: link_pos, } - ]; + ] if (LiteGraph.shift_click_do_break_link_from) { if (e.shiftKey) { - node.disconnectOutput(i); + node.disconnectOutput(i) } } else if (LiteGraph.ctrl_alt_click_do_break_link) { if (e.ctrlKey && e.altKey && !e.shiftKey) { - node.disconnectOutput(i); + node.disconnectOutput(i) } } if (is_double_click) { - if (node.onOutputDblClick) { - node.onOutputDblClick(i, e); - } + node.onOutputDblClick?.(i, e) } else { - if (node.onOutputClick) { - node.onOutputClick(i, e); - } + node.onOutputClick?.(i, e) } - skip_action = true; - break; + skip_action = true + break } } } //search for inputs if (node.inputs) { - for (var i = 0, l = node.inputs.length; i < l; ++i) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); + for (let i = 0, l = node.inputs.length; i < l; ++i) { + const input = node.inputs[i] + const link_pos = node.getConnectionPos(true, i) if (isInsideRectangle( e.canvasX, e.canvasY, @@ -2000,34 +1908,30 @@ export class LGraphCanvas { 20 )) { if (is_double_click) { - if (node.onInputDblClick) { - node.onInputDblClick(i, e); - } + node.onInputDblClick?.(i, e) } else { - if (node.onInputClick) { - node.onInputClick(i, e); - } + node.onInputClick?.(i, e) } if (input.link !== null) { - var link_info = this.graph.links[input.link]; //before disconnecting - const slot = link_info.origin_slot; - const linked_node = this.graph._nodes_by_id[link_info.origin_id]; + const link_info = this.graph.links[input.link] //before disconnecting + const slot = link_info.origin_slot + const linked_node = this.graph._nodes_by_id[link_info.origin_id] if (LiteGraph.click_do_break_link_to || (LiteGraph.ctrl_alt_click_do_break_link && e.ctrlKey && e.altKey && !e.shiftKey)) { - node.disconnectInput(i); + node.disconnectInput(i) } else if (e.shiftKey) { this.connecting_links = [{ node: linked_node, slot, output: linked_node.outputs[slot], pos: linked_node.getConnectionPos(false, slot), - }]; + }] - this.dirty_bgcanvas = true; - skip_action = true; + this.dirty_bgcanvas = true + skip_action = true } else if (this.allow_reconnect_links) { if (!LiteGraph.click_do_break_link_to) { - node.disconnectInput(i); + node.disconnectInput(i) } this.connecting_links = [ { @@ -2037,15 +1941,14 @@ export class LGraphCanvas { output: linked_node.outputs[slot], pos: linked_node.getConnectionPos(false, slot), } - ]; + ] - this.dirty_bgcanvas = true; - skip_action = true; + this.dirty_bgcanvas = true + skip_action = true } else { // do same action as has not node ? } - } else { // has not node } @@ -2060,13 +1963,13 @@ export class LGraphCanvas { output: null, pos: link_pos, } - ]; + ] - this.dirty_bgcanvas = true; - skip_action = true; + this.dirty_bgcanvas = true + skip_action = true } - break; + break } } } @@ -2075,17 +1978,14 @@ export class LGraphCanvas { //it wasn't clicked on the links boxes if (!skip_action) { - var block_drag_node = false; - if (node?.pinned) { - block_drag_node = true; - } - var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; + let block_drag_node = node?.pinned ? true : false + const pos: Point = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]] //widgets - var widget = this.processNodeWidgets(node, this.graph_mouse, e); + const widget = this.processNodeWidgets(node, this.graph_mouse, e) if (widget) { - block_drag_node = true; - this.node_widget = [node, widget]; + block_drag_node = true + this.node_widget = [node, widget] } //double clicking @@ -2094,56 +1994,52 @@ export class LGraphCanvas { // Note: pos[1] is the y-coordinate of the node's body // If clicking on node header (title), pos[1] is negative if (pos[1] < 0) { - if (node.onNodeTitleDblClick) { - node.onNodeTitleDblClick(e, pos, this); - } + node.onNodeTitleDblClick?.(e, pos, this) } //double click node - if (node.onDblClick) { - node.onDblClick(e, pos, this); - } - this.processNodeDblClicked(node); - block_drag_node = true; + node.onDblClick?.(e, pos, this) + this.processNodeDblClicked(node) + block_drag_node = true } //if do not capture mouse - if (node.onMouseDown && node.onMouseDown(e, pos, this)) { - block_drag_node = true; + if (node.onMouseDown?.(e, pos, this)) { + block_drag_node = true } else { //open subgraph button if (node.subgraph && !node.skip_subgraph_button) { if (!node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0) { - var that = this; + const that = this setTimeout(function () { - that.openSubgraph(node.subgraph); - }, 10); + that.openSubgraph(node.subgraph) + }, 10) } } if (this.live_mode) { - clicking_canvas_bg = true; - block_drag_node = true; + clicking_canvas_bg = true + block_drag_node = true } } if (!block_drag_node) { if (this.allow_dragnodes) { - this.graph.beforeChange(); - this.node_dragged = node; + this.graph.beforeChange() + this.node_dragged = node } // Account for shift + click + drag if (!(e.shiftKey && !e.ctrlKey && !e.altKey) || !node.is_selected) { - this.processNodeSelected(node, e); + this.processNodeSelected(node, e) } } else { // double-click /** * Don't call the function if the block is already selected. * Otherwise, it could cause the block to be unselected while its panel is open. */ - if (!node.is_selected) this.processNodeSelected(node, e); + if (!node.is_selected) this.processNodeSelected(node, e) } - this.dirty_canvas = true; + this.dirty_canvas = true } } //clicked outside of nodes else { @@ -2151,12 +2047,12 @@ export class LGraphCanvas { //search for link connector if (!this.read_only) { // Set the width of the line for isPointInStroke checks - const lineWidth = this.ctx.lineWidth; - this.ctx.lineWidth = this.connections_width + 7; - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - let overLink = null; + const lineWidth = this.ctx.lineWidth + this.ctx.lineWidth = this.connections_width + 7 + for (let i = 0; i < this.visible_links.length; ++i) { + const link = this.visible_links[i] + const center = link._pos + let overLink = null if (!center || e.canvasX < center[0] - 4 || e.canvasX > center[0] + 4 || @@ -2164,47 +2060,47 @@ export class LGraphCanvas { e.canvasY > center[1] + 4) { // If we shift click on a link then start a link from that input if (e.shiftKey && link.path && this.ctx.isPointInStroke(link.path, e.canvasX, e.canvasY)) { - overLink = link; + overLink = link } else { - continue; + continue } } if (overLink) { - const slot = overLink.origin_slot; - const originNode = this.graph._nodes_by_id[overLink.origin_id]; + const slot = overLink.origin_slot + const originNode = this.graph._nodes_by_id[overLink.origin_id] - this.connecting_links ??= []; + this.connecting_links ??= [] this.connecting_links.push({ node: originNode, slot, output: originNode.outputs[slot], pos: originNode.getConnectionPos(false, slot), - }); - skip_action = true; + }) + skip_action = true } else { //link clicked - this.showLinkMenu(link, e); - this.over_link_center = null; //clear tooltip + this.showLinkMenu(link, e) + this.over_link_center = null //clear tooltip } - break; + break } // Restore line width - this.ctx.lineWidth = lineWidth; + this.ctx.lineWidth = lineWidth } - this.selected_group = this.graph.getGroupOnPos(e.canvasX, e.canvasY); - this.selected_group_resizing = false; + this.selected_group = this.graph.getGroupOnPos(e.canvasX, e.canvasY) + this.selected_group_resizing = false if (this.selected_group && !this.read_only) { if (e.ctrlKey) { - this.dragging_rectangle = null; + this.dragging_rectangle = null } - var dist = distance([e.canvasX, e.canvasY], [this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1]]); + const dist = distance([e.canvasX, e.canvasY], [this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1]]) if (dist * this.ds.scale < 10) { - this.selected_group_resizing = true; + this.selected_group_resizing = true } else { - this.selected_group.recomputeInsideNodes(); + this.selected_group.recomputeInsideNodes() } if (is_double_click) { @@ -2218,14 +2114,14 @@ export class LGraphCanvas { group: this.selected_group, } } - )); + )) } } else if (is_double_click && !this.read_only) { // Double click within group should not trigger the searchbox. if (this.allow_searchbox) { - this.showSearchBox(e); - e.preventDefault(); - e.stopPropagation(); + this.showSearchBox(e) + e.preventDefault() + e.stopPropagation() } this.canvas.dispatchEvent(new CustomEvent( "litegraph:canvas", @@ -2236,16 +2132,16 @@ export class LGraphCanvas { originalEvent: e, } } - )); + )) } - clicking_canvas_bg = true; + clicking_canvas_bg = true } } if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) { //console.log("pointerevents: dragging_canvas start"); - this.dragging_canvas = true; + this.dragging_canvas = true } } else if (e.which == 2) { @@ -2256,64 +2152,63 @@ export class LGraphCanvas { if (!this.connecting_links && !node.flags.collapsed && !this.live_mode) { - var mClikSlot = false; - var mClikSlot_index = false; - var mClikSlot_isOut = false; + let mClikSlot: INodeSlot | false = false + let mClikSlot_index: number | false = false + let mClikSlot_isOut: boolean = false //search for outputs if (node.outputs) { - for (var i = 0, l = node.outputs.length; i < l; ++i) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); + for (let i = 0, l = node.outputs.length; i < l; ++i) { + const output = node.outputs[i] + const link_pos = node.getConnectionPos(false, i) if (isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 15, link_pos[1] - 10, 30, 20)) { - mClikSlot = output; - mClikSlot_index = i; - mClikSlot_isOut = true; - break; + mClikSlot = output + mClikSlot_index = i + mClikSlot_isOut = true + break } } } //search for inputs if (node.inputs) { - for (var i = 0, l = node.inputs.length; i < l; ++i) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); + for (let i = 0, l = node.inputs.length; i < l; ++i) { + const input = node.inputs[i] + const link_pos = node.getConnectionPos(true, i) if (isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 15, link_pos[1] - 10, 30, 20)) { - mClikSlot = input; - mClikSlot_index = i; - mClikSlot_isOut = false; - break; + mClikSlot = input + mClikSlot_index = i + mClikSlot_isOut = false + break } } } //console.log("middleClickSlots? "+mClikSlot+" & "+(mClikSlot_index!==false)); if (mClikSlot && mClikSlot_index !== false) { - var alphaPosY = 0.5 - ((mClikSlot_index + 1) / ((mClikSlot_isOut ? node.outputs.length : node.inputs.length))); - var node_bounding = node.getBounding(); + const alphaPosY = 0.5 - ((mClikSlot_index + 1) / ((mClikSlot_isOut ? node.outputs.length : node.inputs.length))) + const node_bounding = node.getBounding() // estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes - var posRef = [(!mClikSlot_isOut ? node_bounding[0] : node_bounding[0] + node_bounding[2]) // + node_bounding[0]/this.canvas.width*150 + const posRef: Point = [(!mClikSlot_isOut ? node_bounding[0] : node_bounding[0] + node_bounding[2]) // + node_bounding[0]/this.canvas.width*150 , e.canvasY - 80 // + node_bounding[0]/this.canvas.width*66 // vertical "derive" - ]; - var nodeCreated = this.createDefaultNodeForSlot({ + ] + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const nodeCreated = this.createDefaultNodeForSlot({ nodeFrom: !mClikSlot_isOut ? null : node, slotFrom: !mClikSlot_isOut ? null : mClikSlot_index, nodeTo: !mClikSlot_isOut ? node : null, slotTo: !mClikSlot_isOut ? mClikSlot_index : null, - position: posRef //,e: e - , - - nodeType: "AUTO" //nodeNewType - , - - posAdd: [!mClikSlot_isOut ? -30 : 30, -alphaPosY * 130] //-alphaPosY*30] - , - - posSizeFix: [!mClikSlot_isOut ? -1 : 0, 0] //-alphaPosY*2*/ - }); - skip_action = true; + //,e: e + position: posRef, + //nodeNewType + nodeType: "AUTO", + //-alphaPosY*30] + posAdd: [!mClikSlot_isOut ? -30 : 30, -alphaPosY * 130], + //-alphaPosY*2*/ + posSizeFix: [!mClikSlot_isOut ? -1 : 0, 0] + }) + skip_action = true } } } @@ -2321,10 +2216,9 @@ export class LGraphCanvas { if (!skip_action && this.allow_dragcanvas) { //console.log("pointerevents: dragging_canvas start from middle button"); - this.dragging_canvas = true; + this.dragging_canvas = true } - } else if (e.which == 3 || this.pointer_is_double) { //right button @@ -2335,15 +2229,15 @@ export class LGraphCanvas { if (Object.keys(this.selected_nodes).length && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey)) { // is multiselected or using shift to include the now node - if (!this.selected_nodes[node.id]) this.selectNodes([node], true); // add this if not present + if (!this.selected_nodes[node.id]) this.selectNodes([node], true) // add this if not present } else { // update selection - this.selectNodes([node]); + this.selectNodes([node]) } } - // show menu on this node - this.processContextMenu(node, e); + // Show context menu for the node or group under the pointer + this.processContextMenu(node, e) } } @@ -2351,72 +2245,63 @@ export class LGraphCanvas { //TODO //if(this.node_selected != prev_selected) // this.onNodeSelectionChange(this.node_selected); - this.last_mouse[0] = e.clientX; - this.last_mouse[1] = e.clientY; - this.last_mouseclick = LiteGraph.getTime(); - this.last_mouse_dragging = true; + this.last_mouse[0] = e.clientX + this.last_mouse[1] = e.clientY + this.last_mouseclick = LiteGraph.getTime() + this.last_mouse_dragging = true /* if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) this.draw(); */ - this.graph.change(); + this.graph.change() //this is to ensure to defocus(blur) if a text input element is on focus if (!ref_window.document.activeElement || - (ref_window.document.activeElement.nodeName.toLowerCase() != - "input" && - ref_window.document.activeElement.nodeName.toLowerCase() != - "textarea")) { - e.preventDefault(); + (ref_window.document.activeElement.nodeName.toLowerCase() != "input" && + ref_window.document.activeElement.nodeName.toLowerCase() != "textarea")) { + e.preventDefault() } - e.stopPropagation(); + e.stopPropagation() - if (this.onMouseDown) { - this.onMouseDown(e); - } + this.onMouseDown?.(e) - return false; + return false } /** - * Called when a mouse move event has to be processed - * @method processMouseMove - **/ + * Called when a mouse move event has to be processed + **/ processMouseMove(e: CanvasMouseEvent): boolean { - this.link_over_widget = null; + this.link_over_widget = null - if (this.autoresize) { - this.resize(); - } + if (this.autoresize) this.resize() if (this.set_canvas_dirty_on_mouse_event) - this.dirty_canvas = true; + this.dirty_canvas = true - if (!this.graph) { - return; - } + if (!this.graph) return - LGraphCanvas.active_canvas = this; - this.adjustMouseEvent(e); - var mouse = [e.clientX, e.clientY]; - this.mouse[0] = mouse[0]; - this.mouse[1] = mouse[1]; - var delta = [ + LGraphCanvas.active_canvas = this + this.adjustMouseEvent(e) + const mouse: Point = [e.clientX, e.clientY] + this.mouse[0] = mouse[0] + this.mouse[1] = mouse[1] + const delta = [ mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1] - ]; - this.last_mouse = mouse; - this.graph_mouse[0] = e.canvasX; - this.graph_mouse[1] = e.canvasY; + ] + this.last_mouse = mouse + this.graph_mouse[0] = e.canvasX + this.graph_mouse[1] = e.canvasY //console.log("pointerevents: processMouseMove "+e.pointerId+" "+e.isPrimary); if (this.block_click) { //console.log("pointerevents: processMouseMove block_click"); - e.preventDefault(); - return false; + e.preventDefault() + return false } - e.dragging = this.last_mouse_dragging; + e.dragging = this.last_mouse_dragging if (this.node_widget) { this.processNodeWidgets( @@ -2424,17 +2309,17 @@ export class LGraphCanvas { this.graph_mouse, e, this.node_widget[1] - ); - this.dirty_canvas = true; + ) + this.dirty_canvas = true } //get node over - var node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes); + const node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes) if (this.dragging_rectangle) { - this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; - this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; - this.dirty_canvas = true; + this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0] + this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1] + this.dirty_canvas = true } else if (this.selected_group && !this.read_only) { //moving/resizing a group @@ -2442,37 +2327,37 @@ export class LGraphCanvas { this.selected_group.resize( e.canvasX - this.selected_group.pos[0], e.canvasY - this.selected_group.pos[1] - ); + ) } else { - var deltax = delta[0] / this.ds.scale; - var deltay = delta[1] / this.ds.scale; - this.selected_group.move(deltax, deltay, e.ctrlKey); + const deltax = delta[0] / this.ds.scale + const deltay = delta[1] / this.ds.scale + this.selected_group.move(deltax, deltay, e.ctrlKey) if (this.selected_group._nodes.length) { - this.dirty_canvas = true; + this.dirty_canvas = true } } - this.dirty_bgcanvas = true; + this.dirty_bgcanvas = true } else if (this.dragging_canvas) { ////console.log("pointerevents: processMouseMove is dragging_canvas"); - this.ds.offset[0] += delta[0] / this.ds.scale; - this.ds.offset[1] += delta[1] / this.ds.scale; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; + this.ds.offset[0] += delta[0] / this.ds.scale + this.ds.offset[1] += delta[1] / this.ds.scale + this.dirty_canvas = true + this.dirty_bgcanvas = true } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) { if (this.connecting_links) { - this.dirty_canvas = true; + this.dirty_canvas = true } //remove mouseover flag - for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { + for (let i = 0, l = this.graph._nodes.length; i < l; ++i) { if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i]) { //mouse leave - this.graph._nodes[i].mouseOver = false; + this.graph._nodes[i].mouseOver = false if (this.node_over && this.node_over.onMouseLeave) { - this.node_over.onMouseLeave(e); + this.node_over.onMouseLeave(e) } - this.node_over = null; - this.dirty_canvas = true; + this.node_over = null + this.dirty_canvas = true } } @@ -2480,66 +2365,62 @@ export class LGraphCanvas { if (node) { if (node.redraw_on_mouse) - this.dirty_canvas = true; + this.dirty_canvas = true //this.canvas.style.cursor = "move"; if (!node.mouseOver) { //mouse enter - node.mouseOver = true; - this.node_over = node; - this.dirty_canvas = true; + node.mouseOver = true + this.node_over = node + this.dirty_canvas = true - if (node.onMouseEnter) { - node.onMouseEnter(e); - } + node.onMouseEnter?.(e) } //in case the node wants to do something - if (node.onMouseMove) { - node.onMouseMove(e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this); - } + node.onMouseMove?.(e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this) //if dragging a link if (this.connecting_links) { - const firstLink = this.connecting_links[0]; + const firstLink = this.connecting_links[0] if (firstLink.output) { - var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput + const pos = this._highlight_input || [0, 0] //to store the output of isOverNodeInput //on top of input if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { //mouse on top of the corner box, don't know what to do } else { //check if I have a slot below de mouse - var slot = this.isOverNodeInput(node, e.canvasX, e.canvasY, pos); + const slot = this.isOverNodeInput(node, e.canvasX, e.canvasY, pos) if (slot != -1 && node.inputs[slot] && LiteGraph.isValidConnection(firstLink.output.type, node.inputs[slot].type)) { - this._highlight_input = pos; - this._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS + this._highlight_input = pos + this._highlight_input_slot = node.inputs[slot] // XXX CHECK THIS } else { // Allow support for linking to widgets, handled externally to LiteGraph if (this.getWidgetLinkType) { - const overWidget = this.getWidgetAtCursor(node); - + const overWidget = this.getWidgetAtCursor(node) + if (overWidget) { - const widgetLinkType = this.getWidgetLinkType(overWidget, node); + const widgetLinkType = this.getWidgetLinkType(overWidget, node) if (widgetLinkType && LiteGraph.isValidConnection(firstLink.output.type, widgetLinkType)) { if (firstLink.node.isValidWidgetLink?.(firstLink.output.slot_index, node, overWidget) !== false) { - this.link_over_widget = overWidget; - this.link_over_widget_type = widgetLinkType; + this.link_over_widget = overWidget + this.link_over_widget_type = widgetLinkType } } } } - this._highlight_input = null; - this._highlight_input_slot = null; // XXX CHECK THIS + this._highlight_input = null + this._highlight_input_slot = null // XXX CHECK THIS } } } else if (firstLink.input) { - var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput + const pos = this._highlight_output || [0, 0] //to store the output of isOverNodeOutput @@ -2548,11 +2429,11 @@ export class LGraphCanvas { //mouse on top of the corner box, don't know what to do } else { //check if I have a slot below de mouse - var slot = this.isOverNodeOutput(node, e.canvasX, e.canvasY, pos); + const slot = this.isOverNodeOutput(node, e.canvasX, e.canvasY, pos) if (slot != -1 && node.outputs[slot] && LiteGraph.isValidConnection(firstLink.input.type, node.outputs[slot].type)) { - this._highlight_output = pos; + this._highlight_output = pos } else { - this._highlight_output = null; + this._highlight_output = null } } } @@ -2560,207 +2441,196 @@ export class LGraphCanvas { //Search for corner if (this.canvas) { - if (node.inResizeCorner(e.canvasX, e.canvasY)) { - this.canvas.style.cursor = "se-resize"; - } else { - this.canvas.style.cursor = "crosshair"; - } + this.canvas.style.cursor = node.inResizeCorner(e.canvasX, e.canvasY) + ? "se-resize" + : "crosshair" } } else { //not over a node //search for link connector - var over_link = null; - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; + let over_link: LLink = null + for (let i = 0; i < this.visible_links.length; ++i) { + const link = this.visible_links[i] + const center = link._pos if (!center || e.canvasX < center[0] - 4 || e.canvasX > center[0] + 4 || e.canvasY < center[1] - 4 || e.canvasY > center[1] + 4) { - continue; + continue } - over_link = link; - break; + over_link = link + break } if (over_link != this.over_link_center) { - this.over_link_center = over_link; - this.dirty_canvas = true; + this.over_link_center = over_link + this.dirty_canvas = true } if (this.canvas) { - this.canvas.style.cursor = ""; + this.canvas.style.cursor = "" } } //end - - //send event to node if capturing input (used with widgets that allow drag outside of the area of the node) - if (this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove) { - this.node_capturing_input.onMouseMove(e, [e.canvasX - this.node_capturing_input.pos[0], e.canvasY - this.node_capturing_input.pos[1]], this); + if (this.node_capturing_input && this.node_capturing_input != node) { + this.node_capturing_input.onMouseMove?.(e, [e.canvasX - this.node_capturing_input.pos[0], e.canvasY - this.node_capturing_input.pos[1]], this) } //node being dragged if (this.node_dragged && !this.live_mode) { //console.log("draggin!",this.selected_nodes); - for (var i in this.selected_nodes) { - var n = this.selected_nodes[i]; - n.pos[0] += delta[0] / this.ds.scale; - n.pos[1] += delta[1] / this.ds.scale; - if (!n.is_selected) this.processNodeSelected(n, e); /* - * Don't call the function if the block is already selected. - * Otherwise, it could cause the block to be unselected while dragging. - */ - - - - - + for (const i in this.selected_nodes) { + const n = this.selected_nodes[i] + n.pos[0] += delta[0] / this.ds.scale + n.pos[1] += delta[1] / this.ds.scale + /* + * Don't call the function if the block is already selected. + * Otherwise, it could cause the block to be unselected while dragging. + */ + if (!n.is_selected) this.processNodeSelected(n, e) } - this.dirty_canvas = true; - this.dirty_bgcanvas = true; + this.dirty_canvas = true + this.dirty_bgcanvas = true } if (this.resizing_node && !this.live_mode) { //convert mouse to node space - var desired_size = [e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1]]; - var min_size = this.resizing_node.computeSize(); - desired_size[0] = Math.max(min_size[0], desired_size[0]); - desired_size[1] = Math.max(min_size[1], desired_size[1]); - this.resizing_node.setSize(desired_size); + const desired_size: Size = [e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1]] + const min_size = this.resizing_node.computeSize() + desired_size[0] = Math.max(min_size[0], desired_size[0]) + desired_size[1] = Math.max(min_size[1], desired_size[1]) + this.resizing_node.setSize(desired_size) - this.canvas.style.cursor = "se-resize"; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; + this.canvas.style.cursor = "se-resize" + this.dirty_canvas = true + this.dirty_bgcanvas = true } } - e.preventDefault(); - return false; + e.preventDefault() + return false } /** - * Called when a mouse up event has to be processed - * @method processMouseUp - **/ + * Called when a mouse up event has to be processed + **/ processMouseUp(e: CanvasMouseEvent): boolean { - var is_primary = (e.isPrimary === undefined || e.isPrimary); + // @ts-expect-error + const is_primary = (e.isPrimary === undefined || e.isPrimary) //early exit for extra pointer if (!is_primary) { /*e.stopPropagation(); e.preventDefault();*/ //console.log("pointerevents: processMouseUp pointerN_stop "+e.pointerId+" "+e.isPrimary); - return false; + return false } //console.log("pointerevents: processMouseUp "+e.pointerId+" "+e.isPrimary+" :: "+e.clientX+" "+e.clientY); - if (!this.graph) - return; + if (!this.graph) return - var window = this.getCanvasWindow(); - var document = window.document; - LGraphCanvas.active_canvas = this; + const window = this.getCanvasWindow() + const document = window.document + LGraphCanvas.active_canvas = this //restore the mousemove event back to the canvas if (!this.options.skip_events) { //console.log("pointerevents: processMouseUp adjustEventListener"); - LiteGraph.pointerListenerRemove(document, "move", this._mousemove_callback, true); - LiteGraph.pointerListenerAdd(this.canvas, "move", this._mousemove_callback, true); - LiteGraph.pointerListenerRemove(document, "up", this._mouseup_callback, true); + LiteGraph.pointerListenerRemove(document, "move", this._mousemove_callback, true) + LiteGraph.pointerListenerAdd(this.canvas, "move", this._mousemove_callback, true) + LiteGraph.pointerListenerRemove(document, "up", this._mouseup_callback, true) } - this.adjustMouseEvent(e); - var now = LiteGraph.getTime(); - e.click_time = now - this.last_mouseclick; - this.last_mouse_dragging = false; - this.last_click_position = null; + this.adjustMouseEvent(e) + const now = LiteGraph.getTime() + e.click_time = now - this.last_mouseclick + this.last_mouse_dragging = false + this.last_click_position = null - if (this.block_click) { - //console.log("pointerevents: processMouseUp block_clicks"); - this.block_click = false; //used to avoid sending twice a click in a immediate button - } + //used to avoid sending twice a click in an immediate button + this.block_click &&= false //console.log("pointerevents: processMouseUp which: "+e.which); if (e.which == 1) { if (this.node_widget) { - this.processNodeWidgets(this.node_widget[0], this.graph_mouse, e); + this.processNodeWidgets(this.node_widget[0], this.graph_mouse, e) } //left button - this.node_widget = null; + this.node_widget = null if (this.selected_group) { - var diffx = this.selected_group.pos[0] - - Math.round(this.selected_group.pos[0]); - var diffy = this.selected_group.pos[1] - - Math.round(this.selected_group.pos[1]); - this.selected_group.move(diffx, diffy, e.ctrlKey); + const diffx = this.selected_group.pos[0] - + Math.round(this.selected_group.pos[0]) + const diffy = this.selected_group.pos[1] - + Math.round(this.selected_group.pos[1]) + this.selected_group.move(diffx, diffy, e.ctrlKey) this.selected_group.pos[0] = Math.round( this.selected_group.pos[0] - ); + ) this.selected_group.pos[1] = Math.round( this.selected_group.pos[1] - ); + ) if (this.selected_group._nodes.length) { - this.dirty_canvas = true; + this.dirty_canvas = true } - this.selected_group = null; + this.selected_group = null } - this.selected_group_resizing = false; + this.selected_group_resizing = false - var node = this.graph.getNodeOnPos( + let node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes - ); + ) if (this.dragging_rectangle) { if (this.graph) { - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); + const nodes = this.graph._nodes + const node_bounding = new Float32Array(4) //compute bounding and flip if left to right - var w = Math.abs(this.dragging_rectangle[2]); - var h = Math.abs(this.dragging_rectangle[3]); - var startx = this.dragging_rectangle[2] < 0 + const w = Math.abs(this.dragging_rectangle[2]) + const h = Math.abs(this.dragging_rectangle[3]) + const startx = this.dragging_rectangle[2] < 0 ? this.dragging_rectangle[0] - w - : this.dragging_rectangle[0]; - var starty = this.dragging_rectangle[3] < 0 + : this.dragging_rectangle[0] + const starty = this.dragging_rectangle[3] < 0 ? this.dragging_rectangle[1] - h - : this.dragging_rectangle[1]; - this.dragging_rectangle[0] = startx; - this.dragging_rectangle[1] = starty; - this.dragging_rectangle[2] = w; - this.dragging_rectangle[3] = h; + : this.dragging_rectangle[1] + this.dragging_rectangle[0] = startx + this.dragging_rectangle[1] = starty + this.dragging_rectangle[2] = w + this.dragging_rectangle[3] = h // test dragging rect size, if minimun simulate a click if (!node || (w > 10 && h > 10)) { //test against all nodes (not visible because the rectangle maybe start outside - var to_select = []; - for (var i = 0; i < nodes.length; ++i) { - var nodeX = nodes[i]; - nodeX.getBounding(node_bounding); + const to_select = [] + for (let i = 0; i < nodes.length; ++i) { + const nodeX = nodes[i] + nodeX.getBounding(node_bounding) if (!overlapBounding( this.dragging_rectangle, node_bounding )) { - continue; + continue } //out of the visible area - to_select.push(nodeX); + to_select.push(nodeX) } if (to_select.length) { - this.selectNodes(to_select, e.shiftKey); // add to selection with shift + this.selectNodes(to_select, e.shiftKey) // add to selection with shift } } else { // will select of update selection - this.selectNodes([node], e.shiftKey || e.ctrlKey || e.metaKey); // add to selection add to selection with ctrlKey or shiftKey + this.selectNodes([node], e.shiftKey || e.ctrlKey || e.metaKey) // add to selection add to selection with ctrlKey or shiftKey } } - this.dragging_rectangle = null; + this.dragging_rectangle = null } else if (this.connecting_links) { //node below mouse @@ -2768,8 +2638,8 @@ export class LGraphCanvas { for (const link of this.connecting_links) { //dragging a connection - this.dirty_canvas = true; - this.dirty_bgcanvas = true; + this.dirty_canvas = true + this.dirty_bgcanvas = true /* no need to condition on event type.. just another type if ( @@ -2787,13 +2657,13 @@ export class LGraphCanvas { //slot below mouse? connect if (link.output) { - var slot = this.isOverNodeInput( + const slot = this.isOverNodeInput( node, e.canvasX, e.canvasY - ); + ) if (slot != -1) { - link.node.connect(link.slot, node, slot); + link.node.connect(link.slot, node, slot) } else if (this.link_over_widget) { this.emitEvent({ subType: "connectingWidgetLink", @@ -2805,26 +2675,26 @@ export class LGraphCanvas { } else { //not on top of an input // look for a good slot - link.node.connectByType(link.slot, node, link.output.type); + link.node.connectByType(link.slot, node, link.output.type) } } else if (link.input) { - var slot = this.isOverNodeOutput( + const slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY - ); + ) if (slot != -1) { - node.connect(slot, link.node, link.slot); // this is inverted has output-input nature like + node.connect(slot, link.node, link.slot) // this is inverted has output-input nature like } else { //not on top of an input // look for a good slot - link.node.connectByTypeOutput(link.slot, node, link.input.type); + link.node.connectByTypeOutput(link.slot, node, link.input.type) } } } } else { - const firstLink = this.connecting_links[0]; + const firstLink = this.connecting_links[0] const linkReleaseContext = firstLink.output ? { node_from: firstLink.node, slot_from: firstLink.output, @@ -2833,11 +2703,11 @@ export class LGraphCanvas { node_to: firstLink.node, slot_from: firstLink.input, type_filter_out: firstLink.input.type - }; + } // For external event only. const linkReleaseContextExtended = { links: this.connecting_links, - }; + } this.canvas.dispatchEvent(new CustomEvent( "litegraph:canvas", { bubbles: true, @@ -2847,87 +2717,82 @@ export class LGraphCanvas { linkReleaseContext: linkReleaseContextExtended, }, } - )); + )) // add menu when releasing link in empty space if (LiteGraph.release_link_on_empty_shows_menu) { if (e.shiftKey) { if (this.allow_searchbox) { - this.showSearchBox(e, linkReleaseContext); + this.showSearchBox(e, linkReleaseContext) } } else { if (firstLink.output) { - this.showConnectionMenu({ nodeFrom: firstLink.node, slotFrom: firstLink.output, e: e }); + this.showConnectionMenu({ nodeFrom: firstLink.node, slotFrom: firstLink.output, e: e }) } else if (firstLink.input) { - this.showConnectionMenu({ nodeTo: firstLink.node, slotTo: firstLink.input, e: e }); + this.showConnectionMenu({ nodeTo: firstLink.node, slotTo: firstLink.input, e: e }) } } } } - this.connecting_links = null; + this.connecting_links = null } //not dragging connection else if (this.resizing_node) { - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.graph.afterChange(this.resizing_node); - this.resizing_node = null; + this.dirty_canvas = true + this.dirty_bgcanvas = true + this.graph.afterChange(this.resizing_node) + this.resizing_node = null } else if (this.node_dragged) { //node being dragged? - var node = this.node_dragged; + node = this.node_dragged if (node && e.click_time < 300 && isInsideRectangle(e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT)) { - node.collapse(); + node.collapse() } - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); - this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); + this.dirty_canvas = true + this.dirty_bgcanvas = true + this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]) + this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]) if (this.graph.config.align_to_grid || this.align_to_grid) { - this.node_dragged.alignToGrid(); + this.node_dragged.alignToGrid() } - if (this.onNodeMoved) - this.onNodeMoved(this.node_dragged); - this.graph.afterChange(this.node_dragged); - this.node_dragged = null; + this.onNodeMoved?.(this.node_dragged) + this.graph.afterChange(this.node_dragged) + this.node_dragged = null } //no node being dragged else { //get node over - var node = this.graph.getNodeOnPos( + node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes - ); + ) if (!node && e.click_time < 300) { - this.deselectAllNodes(); + this.deselectAllNodes() } - this.dirty_canvas = true; - this.dragging_canvas = false; + this.dirty_canvas = true + this.dragging_canvas = false - if (this.node_over && this.node_over.onMouseUp) { - this.node_over.onMouseUp(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]], this); - } - if (this.node_capturing_input && - this.node_capturing_input.onMouseUp) { - this.node_capturing_input.onMouseUp(e, [ - e.canvasX - this.node_capturing_input.pos[0], - e.canvasY - this.node_capturing_input.pos[1] - ]); - } + // @ts-expect-error Unused param + this.node_over?.onMouseUp?.(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]], this) + this.node_capturing_input?.onMouseUp?.(e, [ + e.canvasX - this.node_capturing_input.pos[0], + e.canvasY - this.node_capturing_input.pos[1] + ]) } } else if (e.which == 2) { //middle button //trace("middle"); - this.dirty_canvas = true; - this.dragging_canvas = false; + this.dirty_canvas = true + this.dragging_canvas = false } else if (e.which == 3) { //right button //trace("right"); - this.dirty_canvas = true; - this.dragging_canvas = false; + this.dirty_canvas = true + this.dragging_canvas = false } /* @@ -2935,80 +2800,66 @@ export class LGraphCanvas { this.draw(); */ if (is_primary) { - this.pointer_is_down = false; - this.pointer_is_double = false; + this.pointer_is_down = false + this.pointer_is_double = false } - this.graph.change(); + this.graph.change() - //console.log("pointerevents: processMouseUp stopPropagation"); - e.stopPropagation(); - e.preventDefault(); - return false; + e.stopPropagation() + e.preventDefault() + return false } /** - * Called when a mouse wheel event has to be processed - * @method processMouseWheel - **/ + * Called when a mouse wheel event has to be processed + **/ processMouseWheel(e: WheelEvent): boolean { - if (!this.graph || !this.allow_dragcanvas) { - return; - } + if (!this.graph || !this.allow_dragcanvas) return - var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; + // TODO: Mouse wheel zoom rewrite + // @ts-expect-error + const delta = e.wheelDeltaY ?? e.detail * -60 - this.adjustMouseEvent(e); + this.adjustMouseEvent(e) - var x = e.clientX; - var y = e.clientY; - var is_inside = !this.viewport || (this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3])); - if (!is_inside) - return; + const pos: Point = [e.clientX, e.clientY] + if (this.viewport && !isPointInRectangle(pos, this.viewport)) return - var scale = this.ds.scale; + let scale = this.ds.scale - if (delta > 0) { - scale *= this.zoom_speed; - } else if (delta < 0) { - scale *= 1 / this.zoom_speed; - } + if (delta > 0) scale *= this.zoom_speed + else if (delta < 0) scale *= 1 / this.zoom_speed - //this.setZoom( scale, [ e.clientX, e.clientY ] ); - this.ds.changeScale(scale, [e.clientX, e.clientY]); + this.ds.changeScale(scale, [e.clientX, e.clientY]) - this.graph.change(); + this.graph.change() - e.preventDefault(); - return false; // prevent default + e.preventDefault() + return false } /** - * returns true if a position (in graph space) is on top of a node little corner box - * @method isOverNodeBox - **/ + * 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 { - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - if (isInsideRectangle( + const title_height = LiteGraph.NODE_TITLE_HEIGHT + return Boolean(isInsideRectangle( canvasx, canvasy, node.pos[0] + 2, node.pos[1] + 2 - title_height, title_height - 4, title_height - 4 - )) { - return true; - } - return false; + )) } /** - * returns the INDEX if a position (in graph space) is on top of a node input slot - * @method isOverNodeInput - **/ + * returns the INDEX if a position (in graph space) is on top of a node input slot + **/ isOverNodeInput(node: LGraphNode, canvasx: number, canvasy: number, slot_pos?: Point): number { if (node.inputs) { - for (var i = 0, l = node.inputs.length; i < l; ++i) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - var is_inside = false; + for (let i = 0, l = node.inputs.length; i < l; ++i) { + const input = node.inputs[i] + const link_pos = node.getConnectionPos(true, i) + let is_inside = false if (node.horizontal) { is_inside = isInsideRectangle( canvasx, @@ -3017,11 +2868,11 @@ export class LGraphCanvas { link_pos[1] - 10, 10, 20 - ); + ) } else { // TODO: Find a cheap way to measure text, and do it on node label change instead of here // Input icon width + text approximation - const width = 20 + (((input.label?.length ?? input.name?.length) || 3) * 7); + const width = 20 + (((input.label?.length ?? input.name?.length) || 3) * 7) is_inside = isInsideRectangle( canvasx, canvasy, @@ -3029,29 +2880,27 @@ export class LGraphCanvas { link_pos[1] - 10, width, 20 - ); + ) } if (is_inside) { if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; + slot_pos[0] = link_pos[0] + slot_pos[1] = link_pos[1] } - return i; + return i } } } - return -1; + return -1 } /** - * returns the INDEX if a position (in graph space) is on top of a node output slot - * @method isOverNodeOuput - **/ + * returns the INDEX if a position (in graph space) is on top of a node output slot + **/ isOverNodeOutput(node: LGraphNode, canvasx: number, canvasy: number, slot_pos?: Point): number { if (node.outputs) { - for (var i = 0, l = node.outputs.length; i < l; ++i) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - var is_inside = false; + for (let i = 0, l = node.outputs.length; i < l; ++i) { + const link_pos = node.getConnectionPos(false, i) + let is_inside = false if (node.horizontal) { is_inside = isInsideRectangle( canvasx, @@ -3060,7 +2909,7 @@ export class LGraphCanvas { link_pos[1] - 10, 10, 20 - ); + ) } else { is_inside = isInsideRectangle( canvasx, @@ -3069,77 +2918,74 @@ export class LGraphCanvas { link_pos[1] - 10, 40, 20 - ); + ) } if (is_inside) { if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; + slot_pos[0] = link_pos[0] + slot_pos[1] = link_pos[1] } - return i; + return i } } } - return -1; + return -1 } /** - * process a key event - * @method processKey - **/ + * process a key event + **/ processKey(e: KeyboardEvent): boolean | null { - if (!this.graph) { - return; - } + if (!this.graph) return - var block_default = false; + let block_default = false //console.log(e); //debug - if (e.target.localName == "input") { - return; - } + // @ts-expect-error + if (e.target.localName == "input") return if (e.type == "keydown") { + // TODO: Switch if (e.keyCode == 32) { // space - this.read_only = true; + this.read_only = true if (this._previously_dragging_canvas === null) { - this._previously_dragging_canvas = this.dragging_canvas; + this._previously_dragging_canvas = this.dragging_canvas } - this.dragging_canvas = this.pointer_is_down; - block_default = true; + this.dragging_canvas = this.pointer_is_down + block_default = true } - if (e.keyCode == 27) { + else if (e.keyCode == 27) { //esc - if (this.node_panel) this.node_panel.close(); - if (this.options_panel) this.options_panel.close(); - block_default = true; + this.node_panel?.close() + this.options_panel?.close() + block_default = true } //select all Control A - if (e.keyCode == 65 && e.ctrlKey) { - this.selectNodes(); - block_default = true; + else if (e.keyCode == 65 && e.ctrlKey) { + this.selectNodes() + block_default = true } - if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) { + else if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) { //copy if (this.selected_nodes) { - this.copyToClipboard(); - block_default = true; + this.copyToClipboard() + block_default = true } } - if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) { + else if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) { //paste - this.pasteFromClipboard(e.shiftKey); + this.pasteFromClipboard(e.shiftKey) } //delete or backspace - if (e.keyCode == 46 || e.keyCode == 8) { - if (e.target.localName != "input" && - e.target.localName != "textarea") { - this.deleteSelectedNodes(); - block_default = true; + else if (e.keyCode == 46 || e.keyCode == 8) { + // @ts-expect-error + if (e.target.localName != "input" && e.target.localName != "textarea") { + this.deleteSelectedNodes() + block_default = true } } @@ -3147,92 +2993,84 @@ export class LGraphCanvas { //... //TODO if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyDown) { - this.selected_nodes[i].onKeyDown(e); - } + for (const i in this.selected_nodes) { + this.selected_nodes[i].onKeyDown?.(e) } } } else if (e.type == "keyup") { if (e.keyCode == 32) { // space - this.read_only = false; - this.dragging_canvas = this._previously_dragging_canvas ?? false; - this._previously_dragging_canvas = null; + this.read_only = false + this.dragging_canvas = this._previously_dragging_canvas ?? false + this._previously_dragging_canvas = null } if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyUp) { - this.selected_nodes[i].onKeyUp(e); - } + for (const i in this.selected_nodes) { + this.selected_nodes[i].onKeyUp?.(e) } } } - this.graph.change(); + // TODO: Do we need to remeasure and recalculate everything on every key down/up? + this.graph.change() if (block_default) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; + e.preventDefault() + e.stopImmediatePropagation() + return false } } copyToClipboard(nodes?: Dictionary): void { - var clipboard_info = { + const clipboard_info: IClipboardContents = { nodes: [], links: [] - }; - var index = 0; - var selected_nodes_array = []; - if (!nodes) nodes = this.selected_nodes; - for (var i in nodes) { - var node = nodes[i]; - if (node.clonable === false) - continue; - node._relative_id = index; - selected_nodes_array.push(node); - index += 1; - } - - for (var i = 0; i < selected_nodes_array.length; ++i) { - var node = selected_nodes_array[i]; - var cloned = node.clone(); + } + let index = 0 + const selected_nodes_array: LGraphNode[] = [] + if (!nodes) nodes = this.selected_nodes + for (const i in nodes) { + const node = nodes[i] + if (node.clonable === false) continue + + node._relative_id = index + selected_nodes_array.push(node) + index += 1 + } + + for (let i = 0; i < selected_nodes_array.length; ++i) { + const node = selected_nodes_array[i] + const cloned = node.clone() if (!cloned) { - console.warn("node type not found: " + node.type); - continue; - } - clipboard_info.nodes.push(cloned.serialize()); - if (node.inputs && node.inputs.length) { - for (var j = 0; j < node.inputs.length; ++j) { - var input = node.inputs[j]; - if (!input || input.link == null) { - continue; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - continue; - } - var target_node = this.graph.getNodeById( - link_info.origin_id - ); - if (!target_node) { - continue; - } + console.warn("node type not found: " + node.type) + continue + } + clipboard_info.nodes.push(cloned.serialize()) + if (node.inputs?.length) { + for (let j = 0; j < node.inputs.length; ++j) { + const input = node.inputs[j] + if (!input || input.link == null) continue + + const link_info = this.graph.links[input.link] + if (!link_info) continue + + const target_node = this.graph.getNodeById(link_info.origin_id) + if (!target_node) continue + clipboard_info.links.push([ target_node._relative_id, link_info.origin_slot, //j, node._relative_id, link_info.target_slot, target_node.id - ]); + ]) } } } localStorage.setItem( "litegrapheditor_clipboard", JSON.stringify(clipboard_info) - ); + ) } emitEvent(detail) { @@ -3242,429 +3080,366 @@ export class LGraphCanvas { bubbles: true, detail } - )); + )) } emitBeforeChange() { this.emitEvent({ subType: "before-change", - }); + }) } emitAfterChange() { this.emitEvent({ subType: "after-change", - }); + }) } - _pasteFromClipboard(isConnectUnselected = false) { + _pasteFromClipboard(isConnectUnselected = false): void { // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior - if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { - return; - } - var data = localStorage.getItem("litegrapheditor_clipboard"); - if (!data) { - return; - } + if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) return + const data = localStorage.getItem("litegrapheditor_clipboard") + if (!data) return - this.graph.beforeChange(); + this.graph.beforeChange() //create nodes - var clipboard_info = JSON.parse(data); + const clipboard_info: IClipboardContents = JSON.parse(data) // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos - var posMin = false; - var posMinIndexes = false; - for (var i = 0; i < clipboard_info.nodes.length; ++i) { + let posMin: false | [number, number] = false + let posMinIndexes: false | [number, number] = false + for (let i = 0; i < clipboard_info.nodes.length; ++i) { if (posMin) { if (posMin[0] > clipboard_info.nodes[i].pos[0]) { - posMin[0] = clipboard_info.nodes[i].pos[0]; - posMinIndexes[0] = i; + posMin[0] = clipboard_info.nodes[i].pos[0] + posMinIndexes[0] = i } if (posMin[1] > clipboard_info.nodes[i].pos[1]) { - posMin[1] = clipboard_info.nodes[i].pos[1]; - posMinIndexes[1] = i; + posMin[1] = clipboard_info.nodes[i].pos[1] + posMinIndexes[1] = i } } else { - posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]]; - posMinIndexes = [i, i]; + posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]] + posMinIndexes = [i, i] } } - var nodes = []; - for (var i = 0; i < clipboard_info.nodes.length; ++i) { - var node_data = clipboard_info.nodes[i]; - var node = LiteGraph.createNode(node_data.type); + const nodes: LGraphNode[] = [] + for (let i = 0; i < clipboard_info.nodes.length; ++i) { + const node_data = clipboard_info.nodes[i] + const node = LiteGraph.createNode(node_data.type) if (node) { - node.configure(node_data); + node.configure(node_data) //paste in last known mouse position - node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5; - node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5; + node.pos[0] += this.graph_mouse[0] - posMin[0] //+= 5; + node.pos[1] += this.graph_mouse[1] - posMin[1] //+= 5; - this.graph.add(node, { doProcessChange: false }); + // @ts-expect-error This can be changed to just "true", given the functionality of .add() - but the param should be removed + this.graph.add(node, { doProcessChange: false }) - nodes.push(node); + nodes.push(node) } } //create links - for (var i = 0; i < clipboard_info.links.length; ++i) { - var link_info = clipboard_info.links[i]; - var origin_node = undefined; - var origin_node_relative_id = link_info[0]; + for (let i = 0; i < clipboard_info.links.length; ++i) { + const link_info = clipboard_info.links[i] + let origin_node: LGraphNode = undefined + const origin_node_relative_id = link_info[0] if (origin_node_relative_id != null) { - origin_node = nodes[origin_node_relative_id]; + origin_node = nodes[origin_node_relative_id] } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { - var origin_node_id = link_info[4]; + const origin_node_id = link_info[4] if (origin_node_id) { - origin_node = this.graph.getNodeById(origin_node_id); + origin_node = this.graph.getNodeById(origin_node_id) } } - var target_node = nodes[link_info[2]]; + const target_node = nodes[link_info[2]] if (origin_node && target_node) - origin_node.connect(link_info[1], target_node, link_info[3]); - + origin_node.connect(link_info[1], target_node, link_info[3]) else - console.warn("Warning, nodes missing on pasting"); + console.warn("Warning, nodes missing on pasting") } - this.selectNodes(nodes); + this.selectNodes(nodes) - this.graph.afterChange(); + this.graph.afterChange() } pasteFromClipboard(isConnectUnselected = false): void { - this.emitBeforeChange(); + this.emitBeforeChange() try { - this._pasteFromClipboard(isConnectUnselected); + this._pasteFromClipboard(isConnectUnselected) } finally { - this.emitAfterChange(); + this.emitAfterChange() } } /** - * process a item drop event on top the canvas - * @method processDrop - **/ + * process a item drop event on top the canvas + **/ processDrop(e: CanvasDragEvent): boolean { - e.preventDefault(); - this.adjustMouseEvent(e); - var x = e.clientX; - var y = e.clientY; - var is_inside = !this.viewport || (this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3])); - if (!is_inside) { - return; - // --- BREAK --- - } + e.preventDefault() + this.adjustMouseEvent(e) + const x = e.clientX + const y = e.clientY + const is_inside = !this.viewport || (this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3])) + if (!is_inside) return - var pos = [e.canvasX, e.canvasY]; - - - var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null; + const pos = [e.canvasX, e.canvasY] + const node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null if (!node) { - var r = null; - if (this.onDropItem) { - r = this.onDropItem(event); - } - if (!r) { - this.checkDropItem(e); - } - return; + const r = this.onDropItem?.(e) + if (!r) this.checkDropItem(e) + return } if (node.onDropFile || node.onDropData) { - var files = e.dataTransfer.files; + const files = e.dataTransfer.files if (files && files.length) { - for (var i = 0; i < files.length; i++) { - var file = e.dataTransfer.files[0]; - var filename = file.name; - var ext = LGraphCanvas.getFileExtension(filename); - //console.log(file); - if (node.onDropFile) { - node.onDropFile(file); - } + for (let i = 0; i < files.length; i++) { + const file = e.dataTransfer.files[0] + const filename = file.name + node.onDropFile?.(file) if (node.onDropData) { //prepare reader - var reader = new FileReader(); + const reader = new FileReader() reader.onload = function (event) { - //console.log(event.target); - var data = event.target.result; - node.onDropData(data, filename, file); - }; + const data = event.target.result + node.onDropData(data, filename, file) + } //read data - var type = file.type.split("/")[0]; + const type = file.type.split("/")[0] if (type == "text" || type == "") { - reader.readAsText(file); + reader.readAsText(file) } else if (type == "image") { - reader.readAsDataURL(file); + reader.readAsDataURL(file) } else { - reader.readAsArrayBuffer(file); + reader.readAsArrayBuffer(file) } } } } } - if (node.onDropItem) { - if (node.onDropItem(event)) { - return true; - } - } - - if (this.onDropItem) { - return this.onDropItem(event); - } + if (node.onDropItem?.(e)) return true - return false; + return this.onDropItem + ? this.onDropItem(e) + : false } //called if the graph doesn't have a default drop item behaviour checkDropItem(e: CanvasDragEvent): void { - if (e.dataTransfer.files.length) { - var file = e.dataTransfer.files[0]; - var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase(); - var nodetype = LiteGraph.node_types_by_file_extension[ext]; - if (nodetype) { - this.graph.beforeChange(); - var node = LiteGraph.createNode(nodetype.type); - node.pos = [e.canvasX, e.canvasY]; - this.graph.add(node); - if (node.onDropFile) { - node.onDropFile(file); - } - this.graph.afterChange(); - } - } + if (!e.dataTransfer.files.length) return + + const file = e.dataTransfer.files[0] + const ext = LGraphCanvas.getFileExtension(file.name).toLowerCase() + const nodetype = LiteGraph.node_types_by_file_extension[ext] + if (!nodetype) return + + this.graph.beforeChange() + const node = LiteGraph.createNode(nodetype.type) + node.pos = [e.canvasX, e.canvasY] + this.graph.add(node) + node.onDropFile?.(file) + this.graph.afterChange() } processNodeDblClicked(n: LGraphNode): void { - if (this.onShowNodePanel) { - this.onShowNodePanel(n); - } - - if (this.onNodeDblClicked) { - this.onNodeDblClicked(n); - } + this.onShowNodePanel?.(n) + this.onNodeDblClicked?.(n) - this.setDirty(true); + this.setDirty(true) } processNodeSelected(node: LGraphNode, e: CanvasMouseEvent): void { - this.selectNode(node, e && (e.shiftKey || e.metaKey || e.ctrlKey || this.multi_select)); - if (this.onNodeSelected) { - this.onNodeSelected(node); - } + this.selectNode(node, e && (e.shiftKey || e.metaKey || e.ctrlKey || this.multi_select)) + this.onNodeSelected?.(node) } /** - * selects a given node (or adds it to the current selection) - * @method selectNode - **/ + * selects a given node (or adds it to the current selection) + **/ selectNode(node: LGraphNode, add_to_current_selection?: boolean): void { if (node == null) { - this.deselectAllNodes(); + this.deselectAllNodes() } else { - this.selectNodes([node], add_to_current_selection); + this.selectNodes([node], add_to_current_selection) } } /** - * selects several nodes (or adds them to the current selection) - * @method selectNodes - **/ + * selects several nodes (or adds them to the current selection) + **/ selectNodes(nodes?: LGraphNode[] | Dictionary, add_to_current_selection?: boolean): void { if (!add_to_current_selection) { - this.deselectAllNodes(); + this.deselectAllNodes() } - nodes = nodes || this.graph._nodes; - if (typeof nodes == "string") nodes = [nodes]; - for (var i in nodes) { - var node = nodes[i]; + nodes = nodes || this.graph._nodes + if (typeof nodes == "string") nodes = [nodes] + for (const i in nodes) { + const node: LGraphNode = nodes[i] if (node.is_selected) { - this.deselectNode(node); - continue; + this.deselectNode(node) + continue } - if (!node.is_selected && node.onSelected) { - node.onSelected(); + if (!node.is_selected) { + node.onSelected?.() } - node.is_selected = true; - this.selected_nodes[node.id] = node; + node.is_selected = true + this.selected_nodes[node.id] = node if (node.inputs) { - for (var j = 0; j < node.inputs.length; ++j) { - this.highlighted_links[node.inputs[j].link] = true; + for (let j = 0; j < node.inputs.length; ++j) { + this.highlighted_links[node.inputs[j].link] = true } } if (node.outputs) { - for (var j = 0; j < node.outputs.length; ++j) { - var out = node.outputs[j]; + for (let j = 0; j < node.outputs.length; ++j) { + const out = node.outputs[j] if (out.links) { - for (var k = 0; k < out.links.length; ++k) { - this.highlighted_links[out.links[k]] = true; + for (let k = 0; k < out.links.length; ++k) { + this.highlighted_links[out.links[k]] = true } } } } } - if (this.onSelectionChange) - this.onSelectionChange(this.selected_nodes); + this.onSelectionChange?.(this.selected_nodes) - this.setDirty(true); + this.setDirty(true) } /** - * removes a node from the current selection - * @method deselectNode - **/ + * removes a node from the current selection + **/ deselectNode(node: LGraphNode): void { - if (!node.is_selected) { - return; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - delete this.selected_nodes[node.id]; + if (!node.is_selected) return + node.onDeselected?.() + node.is_selected = false + delete this.selected_nodes[node.id] - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } + this.onNodeDeselected?.(node) //remove highlighted if (node.inputs) { - for (var i = 0; i < node.inputs.length; ++i) { - delete this.highlighted_links[node.inputs[i].link]; + for (let i = 0; i < node.inputs.length; ++i) { + delete this.highlighted_links[node.inputs[i].link] } } if (node.outputs) { - for (var i = 0; i < node.outputs.length; ++i) { - var out = node.outputs[i]; + for (let i = 0; i < node.outputs.length; ++i) { + const out = node.outputs[i] if (out.links) { - for (var j = 0; j < out.links.length; ++j) { - delete this.highlighted_links[out.links[j]]; + for (let j = 0; j < out.links.length; ++j) { + delete this.highlighted_links[out.links[j]] } } } } } /** - * removes all nodes from the current selection - * @method deselectAllNodes - **/ + * removes all nodes from the current selection + **/ deselectAllNodes(): void { - if (!this.graph) { - return; - } - var nodes = this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var node = nodes[i]; + if (!this.graph) return + const nodes = this.graph._nodes + for (let i = 0, l = nodes.length; i < l; ++i) { + const node = nodes[i] if (!node.is_selected) { - continue; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - if (this.onNodeDeselected) { - this.onNodeDeselected(node); + continue } + node.onDeselected?.() + node.is_selected = false + this.onNodeDeselected?.(node) } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - if (this.onSelectionChange) - this.onSelectionChange(this.selected_nodes); - this.setDirty(true); + this.selected_nodes = {} + this.current_node = null + this.highlighted_links = {} + this.onSelectionChange?.(this.selected_nodes) + this.setDirty(true) } /** - * deletes all nodes in the current selection from the graph - * @method deleteSelectedNodes - **/ + * deletes all nodes in the current selection from the graph + **/ deleteSelectedNodes(): void { - this.graph.beforeChange(); + this.graph.beforeChange() - for (var i in this.selected_nodes) { - var node = this.selected_nodes[i]; + for (const i in this.selected_nodes) { + const node = this.selected_nodes[i] - if (node.block_delete) - continue; + if (node.block_delete) continue //autoconnect when possible (very basic, only takes into account first input-output) - if (node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection(node.inputs[0].type, node.outputs[0].type) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length) { - var input_link = node.graph.links[node.inputs[0].link]; - var output_link = node.graph.links[node.outputs[0].links[0]]; - var input_node = node.getInputNode(0); - var output_node = node.getOutputNodes(0)[0]; + if (node.inputs?.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection(node.inputs[0].type, node.outputs[0].type) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length) { + const input_link = node.graph.links[node.inputs[0].link] + const output_link = node.graph.links[node.outputs[0].links[0]] + const input_node = node.getInputNode(0) + const output_node = node.getOutputNodes(0)[0] if (input_node && output_node) - input_node.connect(input_link.origin_slot, output_node, output_link.target_slot); - } - this.graph.remove(node); - if (this.onNodeDeselected) { - this.onNodeDeselected(node); + input_node.connect(input_link.origin_slot, output_node, output_link.target_slot) } + this.graph.remove(node) + this.onNodeDeselected?.(node) } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - this.setDirty(true); - this.graph.afterChange(); + this.selected_nodes = {} + this.current_node = null + this.highlighted_links = {} + this.setDirty(true) + this.graph.afterChange() } /** - * centers the camera on a given node - * @method centerOnNode - **/ + * centers the camera on a given node + **/ centerOnNode(node: LGraphNode): void { - const dpi = window?.devicePixelRatio || 1; + const dpi = window?.devicePixelRatio || 1 this.ds.offset[0] = -node.pos[0] - node.size[0] * 0.5 + - (this.canvas.width * 0.5) / (this.ds.scale * dpi); + (this.canvas.width * 0.5) / (this.ds.scale * dpi) this.ds.offset[1] = -node.pos[1] - node.size[1] * 0.5 + - (this.canvas.height * 0.5) / (this.ds.scale * dpi); - this.setDirty(true, true); + (this.canvas.height * 0.5) / (this.ds.scale * dpi) + this.setDirty(true, true) } /** - * adds some useful properties to a mouse event, like the position in graph coordinates - * @method adjustMouseEvent - **/ + * adds some useful properties to a mouse event, like the position in graph coordinates + **/ adjustMouseEvent(e: CanvasMouseEvent | CanvasDragEvent | CanvasWheelEvent): asserts e is CanvasMouseEvent { - var clientX_rel = 0; - var clientY_rel = 0; + let clientX_rel = e.clientX + let clientY_rel = e.clientY if (this.canvas) { - var b = this.canvas.getBoundingClientRect(); - clientX_rel = e.clientX - b.left; - clientY_rel = e.clientY - b.top; - } else { - clientX_rel = e.clientX; - clientY_rel = e.clientY; + const b = this.canvas.getBoundingClientRect() + clientX_rel -= b.left + clientY_rel -= b.top } + // TODO: Find a less brittle way to do this + // Only set deltaX and deltaY if not already set. // If deltaX and deltaY are already present, they are read-only. // Setting them would result browser error => zoom in/out feature broken. - if (e.deltaX === undefined) - e.deltaX = clientX_rel - this.last_mouse_position[0]; - if (e.deltaY === undefined) - e.deltaY = clientY_rel - this.last_mouse_position[1]; - - this.last_mouse_position[0] = clientX_rel; - this.last_mouse_position[1] = clientY_rel; + // @ts-expect-error This behaviour is not guaranteed but for now works on all browsers + if (e.deltaX === undefined) e.deltaX = clientX_rel - this.last_mouse_position[0] + // @ts-expect-error This behaviour is not guaranteed but for now works on all browsers + if (e.deltaY === undefined) e.deltaY = clientY_rel - this.last_mouse_position[1] - e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0]; - e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1]; + this.last_mouse_position[0] = clientX_rel + this.last_mouse_position[1] = clientY_rel - //console.log("pointerevents: adjustMouseEvent "+e.clientX+":"+e.clientY+" "+clientX_rel+":"+clientY_rel+" "+e.canvasX+":"+e.canvasY); + e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0] + e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1] } /** - * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom - * @method setZoom - **/ + * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom + **/ setZoom(value: number, zooming_center: Point) { - this.ds.changeScale(value, zooming_center); + this.ds.changeScale(value, zooming_center) /* if(!zooming_center && this.canvas) zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; @@ -3684,99 +3459,93 @@ export class LGraphCanvas { this.offset[0] += delta_offset[0]; this.offset[1] += delta_offset[1]; */ - this.dirty_canvas = true; - this.dirty_bgcanvas = true; + this.dirty_canvas = true + this.dirty_bgcanvas = true } /** - * converts a coordinate from graph coordinates to canvas2D coordinates - * @method convertOffsetToCanvas - **/ + * converts a coordinate from graph coordinates to canvas2D coordinates + **/ convertOffsetToCanvas(pos: Point, out: Point): Point { - return this.ds.convertOffsetToCanvas(pos, out); + // @ts-expect-error Unused param + return this.ds.convertOffsetToCanvas(pos, out) } /** - * converts a coordinate from Canvas2D coordinates to graph space - * @method convertCanvasToOffset - **/ + * converts a coordinate from Canvas2D coordinates to graph space + **/ convertCanvasToOffset(pos: Point, out?: Point): Point { - return this.ds.convertCanvasToOffset(pos, out); + return this.ds.convertCanvasToOffset(pos, out) } //converts event coordinates from canvas2D to graph coordinates convertEventToCanvasOffset(e: MouseEvent): Point { - var rect = this.canvas.getBoundingClientRect(); + const rect = this.canvas.getBoundingClientRect() + // TODO: -> this.ds.convertCanvasToOffset return this.convertCanvasToOffset([ e.clientX - rect.left, e.clientY - rect.top - ]); + ]) } /** - * brings a node to front (above all other nodes) - * @method bringToFront - **/ + * brings a node to front (above all other nodes) + **/ bringToFront(node: LGraphNode): void { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } + const i = this.graph._nodes.indexOf(node) + if (i == -1) return - this.graph._nodes.splice(i, 1); - this.graph._nodes.push(node); + this.graph._nodes.splice(i, 1) + this.graph._nodes.push(node) } /** - * sends a node to the back (below all other nodes) - * @method sendToBack - **/ + * sends a node to the back (below all other nodes) + **/ sendToBack(node: LGraphNode): void { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } + const i = this.graph._nodes.indexOf(node) + if (i == -1) return - this.graph._nodes.splice(i, 1); - this.graph._nodes.unshift(node); + this.graph._nodes.splice(i, 1) + this.graph._nodes.unshift(node) } /* LGraphCanvas render */ /** - * checks which nodes are visible (inside the camera area) - * @method computeVisibleNodes - **/ + * Determines which nodes are visible and populates {@link out} with the results. + * @param nodes The list of nodes to check - if falsy, all nodes in the graph will be checked + * @param out Array to write visible nodes into - if falsy, a new array is created instead + * @returns {LGraphNode[]} Array passed ({@link out}), or a new array containing all visible nodes + */ computeVisibleNodes(nodes?: LGraphNode[], out?: LGraphNode[]): LGraphNode[] { - var visible_nodes = out || []; - visible_nodes.length = 0; - nodes = nodes || this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var n = nodes[i]; + const visible_nodes = out || [] + visible_nodes.length = 0 + nodes = nodes || this.graph._nodes + for (let i = 0, l = nodes.length; i < l; ++i) { + const n = nodes[i] //skip rendering nodes in live mode if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) { - continue; + continue } if (!overlapBounding(this.visible_area, n.getBounding(LGraphCanvas.#temp, true))) { - continue; + continue } //out of the visible area - visible_nodes.push(n); + visible_nodes.push(n) } - return visible_nodes; + return visible_nodes } + /** - * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) - * @method draw - **/ + * 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(force_canvas?: boolean, force_bgcanvas?: boolean): void { - if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { - return; - } + if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) return //fps counting - var now = LiteGraph.getTime(); - this.render_time = (now - this.last_draw_time) * 0.001; - this.last_draw_time = now; + const now = LiteGraph.getTime() + this.render_time = (now - this.last_draw_time) * 0.001 + this.last_draw_time = now if (this.graph) { - this.ds.computeVisibleArea(this.viewport); + this.ds.computeVisibleArea(this.viewport) } if (this.dirty_bgcanvas || @@ -3785,142 +3554,134 @@ export class LGraphCanvas { (this.graph && this.graph._last_trigger_time && now - this.graph._last_trigger_time < 1000)) { - this.drawBackCanvas(); + this.drawBackCanvas() } if (this.dirty_canvas || force_canvas) { - this.drawFrontCanvas(); + this.drawFrontCanvas() } - this.fps = this.render_time ? 1.0 / this.render_time : 0; - this.frame += 1; + this.fps = this.render_time ? 1.0 / this.render_time : 0 + this.frame += 1 } /** - * draws the front canvas (the one containing all the nodes) - * @method drawFrontCanvas - **/ + * draws the front canvas (the one containing all the nodes) + **/ drawFrontCanvas(): void { - this.dirty_canvas = false; + this.dirty_canvas = false if (!this.ctx) { - this.ctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.ctx; - if (!ctx) { - //maybe is using webgl... - return; + this.ctx = this.bgcanvas.getContext("2d") } + const ctx = this.ctx + //maybe is using webgl... + if (!ctx) return - var canvas = this.canvas; + const canvas = this.canvas + // @ts-expect-error if (ctx.start2D && !this.viewport) { - ctx.start2D(); - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); + // @ts-expect-error + ctx.start2D() + ctx.restore() + ctx.setTransform(1, 0, 0, 1, 0, 0) } //clip dirty area if there is one, otherwise work in full canvas - var area = this.viewport || this.dirty_area; + const area = this.viewport || this.dirty_area if (area) { - ctx.save(); - ctx.beginPath(); - ctx.rect(area[0], area[1], area[2], area[3]); - ctx.clip(); + ctx.save() + ctx.beginPath() + ctx.rect(area[0], area[1], area[2], area[3]) + ctx.clip() } //clear //canvas.width = canvas.width; if (this.clear_background) { if (area) - ctx.clearRect(area[0], area[1], area[2], area[3]); - - + ctx.clearRect(area[0], area[1], area[2], area[3]) else - ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height) } //draw bg canvas if (this.bgcanvas == this.canvas) { - this.drawBackCanvas(); + this.drawBackCanvas() } else { - let scale = window.devicePixelRatio; - ctx.drawImage(this.bgcanvas, 0, 0, this.bgcanvas.width / scale, this.bgcanvas.height / scale); + const scale = window.devicePixelRatio + ctx.drawImage(this.bgcanvas, 0, 0, this.bgcanvas.width / scale, this.bgcanvas.height / scale) } //rendering - if (this.onRender) { - this.onRender(canvas, ctx); - } + this.onRender?.(canvas, ctx) //info widget if (this.show_info) { - this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0); + this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0) } if (this.graph) { //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); + ctx.save() + this.ds.toCanvasContext(ctx) //draw nodes - var drawn_nodes = 0; - var visible_nodes = this.computeVisibleNodes( + const visible_nodes = this.computeVisibleNodes( null, this.visible_nodes - ); + ) - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; + for (let i = 0; i < visible_nodes.length; ++i) { + const node = visible_nodes[i] //transform coords system - ctx.save(); - ctx.translate(node.pos[0], node.pos[1]); + ctx.save() + ctx.translate(node.pos[0], node.pos[1]) //Draw - this.drawNode(node, ctx); - drawn_nodes += 1; + this.drawNode(node, ctx) //Restore - ctx.restore(); + ctx.restore() } //on top (debug) if (this.render_execution_order) { - this.drawExecutionOrder(ctx); + this.drawExecutionOrder(ctx) } //connections ontop? if (this.graph.config.links_ontop) { if (!this.live_mode) { - this.drawConnections(ctx); + this.drawConnections(ctx) } } if (this.connecting_links) { //current connection (the one being dragged by the mouse) for (const link of this.connecting_links) { - ctx.lineWidth = this.connections_width; - var link_color = null; + ctx.lineWidth = this.connections_width + let link_color = null - var connInOrOut = link.output || link.input; + const connInOrOut = link.output || link.input - var connType = connInOrOut.type; - var connDir = connInOrOut.dir; + const connType = connInOrOut.type + let connDir = connInOrOut.dir if (connDir == null) { if (link.output) - connDir = link.node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; - + connDir = link.node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT else - connDir = link.node.horizontal ? LiteGraph.UP : LiteGraph.LEFT; + connDir = link.node.horizontal ? LiteGraph.UP : LiteGraph.LEFT } - var connShape = connInOrOut.shape; + const connShape = connInOrOut.shape switch (connType) { case LiteGraph.EVENT: - link_color = LiteGraph.EVENT_LINK_COLOR; - break; + link_color = LiteGraph.EVENT_LINK_COLOR + break default: - link_color = LiteGraph.CONNECTING_LINK_COLOR; + link_color = LiteGraph.CONNECTING_LINK_COLOR } //the connection being dragged by the mouse @@ -3934,9 +3695,9 @@ export class LGraphCanvas { link_color, connDir, LiteGraph.CENTER - ); + ) - ctx.beginPath(); + ctx.beginPath() if (connType === LiteGraph.EVENT || connShape === LiteGraph.BOX_SHAPE) { ctx.rect( @@ -3944,20 +3705,20 @@ export class LGraphCanvas { link.pos[1] - 5 + 0.5, 14, 10 - ); - ctx.fill(); - ctx.beginPath(); + ) + ctx.fill() + ctx.beginPath() ctx.rect( this.graph_mouse[0] - 6 + 0.5, this.graph_mouse[1] - 5 + 0.5, 14, 10 - ); + ) } else if (connShape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(link.pos[0] + 8, link.pos[1] + 0.5); - ctx.lineTo(link.pos[0] - 4, link.pos[1] + 6 + 0.5); - ctx.lineTo(link.pos[0] - 4, link.pos[1] - 6 + 0.5); - ctx.closePath(); + ctx.moveTo(link.pos[0] + 8, link.pos[1] + 0.5) + ctx.lineTo(link.pos[0] - 4, link.pos[1] + 6 + 0.5) + ctx.lineTo(link.pos[0] - 4, link.pos[1] - 6 + 0.5) + ctx.closePath() } else { ctx.arc( @@ -3966,28 +3727,27 @@ export class LGraphCanvas { 4, 0, Math.PI * 2 - ); - ctx.fill(); - ctx.beginPath(); + ) + ctx.fill() + ctx.beginPath() ctx.arc( this.graph_mouse[0], this.graph_mouse[1], 4, 0, Math.PI * 2 - ); + ) } - ctx.fill(); + ctx.fill() - ctx.fillStyle = "#ffcc00"; + ctx.fillStyle = "#ffcc00" if (this._highlight_input) { - ctx.beginPath(); - var shape = this._highlight_input_slot.shape; - if (shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5); - ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5); - ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5); - ctx.closePath(); + ctx.beginPath() + if (this._highlight_input_slot?.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5) + ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5) + ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5) + ctx.closePath() } else { ctx.arc( this._highlight_input[0], @@ -3995,17 +3755,17 @@ export class LGraphCanvas { 6, 0, Math.PI * 2 - ); + ) } - ctx.fill(); + ctx.fill() } if (this._highlight_output) { - ctx.beginPath(); - if (shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5); - ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5); - ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5); - ctx.closePath(); + ctx.beginPath() + if (this._highlight_input_slot?.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5) + ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5) + ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5) + ctx.closePath() } else { ctx.arc( this._highlight_output[0], @@ -4013,365 +3773,350 @@ export class LGraphCanvas { 6, 0, Math.PI * 2 - ); + ) } - ctx.fill(); + ctx.fill() } } } //the selection rectangle if (this.dragging_rectangle) { - ctx.strokeStyle = "#FFF"; + ctx.strokeStyle = "#FFF" ctx.strokeRect( this.dragging_rectangle[0], this.dragging_rectangle[1], this.dragging_rectangle[2], this.dragging_rectangle[3] - ); + ) } //on top of link center if (this.over_link_center && this.render_link_tooltip) - this.drawLinkTooltip(ctx, this.over_link_center); + this.drawLinkTooltip(ctx, this.over_link_center) - else if (this.onDrawLinkTooltip) //to remove - this.onDrawLinkTooltip(ctx, null); + //to remove + else + this.onDrawLinkTooltip?.(ctx, null) //custom info - if (this.onDrawForeground) { - this.onDrawForeground(ctx, this.visible_rect); - } + // FIXME: Has never worked - visible_rect is undefined + this.onDrawForeground?.(ctx, this.visible_rect) - ctx.restore(); + ctx.restore() } //draws panel in the corner - if (this._graph_stack && this._graph_stack.length) { - this.drawSubgraphPanel(ctx); + if (this._graph_stack?.length) { + this.drawSubgraphPanel(ctx) } + this.onDrawOverlay?.(ctx) - if (this.onDrawOverlay) { - this.onDrawOverlay(ctx); - } - - if (area) { - ctx.restore(); - } + if (area) ctx.restore() - if (ctx.finish2D) { - //this is a function I use in webgl renderer - ctx.finish2D(); - } + // FIXME: Remove this hook + //this is a function I use in webgl renderer + // @ts-expect-error + if (ctx.finish2D) ctx.finish2D() } /** - * draws the panel in the corner that shows subgraph properties - * @method drawSubgraphPanel - **/ + * draws the panel in the corner that shows subgraph properties + **/ drawSubgraphPanel(ctx: CanvasRenderingContext2D): void { - var subgraph = this.graph; - var subnode = subgraph._subgraph_node; + const subgraph = this.graph + const subnode = subgraph._subgraph_node if (!subnode) { - console.warn("subgraph without subnode"); - return; + console.warn("subgraph without subnode") + return } - this.drawSubgraphPanelLeft(subgraph, subnode, ctx); - this.drawSubgraphPanelRight(subgraph, subnode, ctx); + this.drawSubgraphPanelLeft(subgraph, subnode, ctx) + this.drawSubgraphPanelRight(subgraph, subnode, ctx) } drawSubgraphPanelLeft(subgraph: LGraph, subnode: LGraphNode, ctx: CanvasRenderingContext2D): void { - var num = subnode.inputs ? subnode.inputs.length : 0; - var w = 200; - var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); - - ctx.fillStyle = "#111"; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]); - ctx.fill(); - ctx.globalAlpha = 1; - - ctx.fillStyle = "#888"; - ctx.font = "14px Arial"; - ctx.textAlign = "left"; - ctx.fillText("Graph Inputs", 20, 34); + const num = subnode.inputs ? subnode.inputs.length : 0 + const w = 200 + const h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6) + + ctx.fillStyle = "#111" + ctx.globalAlpha = 0.8 + ctx.beginPath() + ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]) + ctx.fill() + ctx.globalAlpha = 1 + + ctx.fillStyle = "#888" + ctx.font = "14px Arial" + ctx.textAlign = "left" + ctx.fillText("Graph Inputs", 20, 34) // var pos = this.mouse; if (this.drawButton(w - 20, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; + this.closeSubgraph() + return } - var y = 50; - ctx.font = "14px Arial"; + let y = 50 + ctx.font = "14px Arial" if (subnode.inputs) - for (var i = 0; i < subnode.inputs.length; ++i) { - var input = subnode.inputs[i]; - if (input.not_subgraph_input) - continue; + for (let i = 0; i < subnode.inputs.length; ++i) { + const input = subnode.inputs[i] + if (input.not_subgraph_input) continue //input button clicked if (this.drawButton(20, y + 2, w - 20, h - 2)) { - var type = subnode.constructor.input_node_type || "graph/input"; - this.graph.beforeChange(); - var newnode = LiteGraph.createNode(type); + // @ts-expect-error ctor props + const type = subnode.constructor.input_node_type || "graph/input" + this.graph.beforeChange() + const newnode = LiteGraph.createNode(type) if (newnode) { - subgraph.add(newnode); - this.block_click = false; - this.last_click_position = null; - this.selectNodes([newnode]); - this.node_dragged = newnode; - this.dragging_canvas = false; - newnode.setProperty("name", input.name); - newnode.setProperty("type", input.type); - this.node_dragged.pos[0] = this.graph_mouse[0] - 5; - this.node_dragged.pos[1] = this.graph_mouse[1] - 5; - this.graph.afterChange(); + subgraph.add(newnode) + this.block_click = false + this.last_click_position = null + this.selectNodes([newnode]) + this.node_dragged = newnode + this.dragging_canvas = false + newnode.setProperty("name", input.name) + newnode.setProperty("type", input.type) + this.node_dragged.pos[0] = this.graph_mouse[0] - 5 + this.node_dragged.pos[1] = this.graph_mouse[1] - 5 + this.graph.afterChange() } - else - console.error("graph input node not found:", type); - } - ctx.fillStyle = "#9C9"; - ctx.beginPath(); - ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = "#AAA"; - ctx.fillText(input.name, 30, y + h * 0.75); + console.error("graph input node not found:", type) + } + ctx.fillStyle = "#9C9" + ctx.beginPath() + ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI) + ctx.fill() + ctx.fillStyle = "#AAA" + ctx.fillText(input.name, 30, y + h * 0.75) // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - ctx.fillText(input.type, 130, y + h * 0.75); - y += h; + ctx.fillStyle = "#777" + // @ts-expect-error FIXME: Should be a string? Should be a number? + ctx.fillText(input.type, 130, y + h * 0.75) + y += h } //add + button if (this.drawButton(20, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { - this.showSubgraphPropertiesDialog(subnode); + this.showSubgraphPropertiesDialog(subnode) } } drawSubgraphPanelRight(subgraph: LGraph, subnode: LGraphNode, ctx: CanvasRenderingContext2D): void { - var num = subnode.outputs ? subnode.outputs.length : 0; - var canvas_w = this.bgcanvas.width; - var w = 200; - var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); - - ctx.fillStyle = "#111"; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]); - ctx.fill(); - ctx.globalAlpha = 1; - - ctx.fillStyle = "#888"; - ctx.font = "14px Arial"; - ctx.textAlign = "left"; - var title_text = "Graph Outputs"; - var tw = ctx.measureText(title_text).width; - ctx.fillText(title_text, (canvas_w - tw) - 20, 34); + const num = subnode.outputs ? subnode.outputs.length : 0 + const canvas_w = this.bgcanvas.width + const w = 200 + const h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6) + + ctx.fillStyle = "#111" + ctx.globalAlpha = 0.8 + ctx.beginPath() + ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]) + ctx.fill() + ctx.globalAlpha = 1 + + ctx.fillStyle = "#888" + ctx.font = "14px Arial" + ctx.textAlign = "left" + const title_text = "Graph Outputs" + const tw = ctx.measureText(title_text).width + ctx.fillText(title_text, (canvas_w - tw) - 20, 34) // var pos = this.mouse; if (this.drawButton(canvas_w - w, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; + this.closeSubgraph() + return } - var y = 50; - ctx.font = "14px Arial"; + let y = 50 + ctx.font = "14px Arial" if (subnode.outputs) - for (var i = 0; i < subnode.outputs.length; ++i) { - var output = subnode.outputs[i]; - if (output.not_subgraph_input) - continue; + for (let i = 0; i < subnode.outputs.length; ++i) { + const output = subnode.outputs[i] + if (output.not_subgraph_input) continue //output button clicked if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) { - var type = subnode.constructor.output_node_type || "graph/output"; - this.graph.beforeChange(); - var newnode = LiteGraph.createNode(type); + // @ts-expect-error ctor props + const type = subnode.constructor.output_node_type || "graph/output" + this.graph.beforeChange() + const newnode = LiteGraph.createNode(type) if (newnode) { - subgraph.add(newnode); - this.block_click = false; - this.last_click_position = null; - this.selectNodes([newnode]); - this.node_dragged = newnode; - this.dragging_canvas = false; - newnode.setProperty("name", output.name); - newnode.setProperty("type", output.type); - this.node_dragged.pos[0] = this.graph_mouse[0] - 5; - this.node_dragged.pos[1] = this.graph_mouse[1] - 5; - this.graph.afterChange(); + subgraph.add(newnode) + this.block_click = false + this.last_click_position = null + this.selectNodes([newnode]) + this.node_dragged = newnode + this.dragging_canvas = false + newnode.setProperty("name", output.name) + newnode.setProperty("type", output.type) + this.node_dragged.pos[0] = this.graph_mouse[0] - 5 + this.node_dragged.pos[1] = this.graph_mouse[1] - 5 + this.graph.afterChange() } - else - console.error("graph input node not found:", type); - } - ctx.fillStyle = "#9C9"; - ctx.beginPath(); - ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = "#AAA"; - ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75); + console.error("graph input node not found:", type) + } + ctx.fillStyle = "#9C9" + ctx.beginPath() + ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI) + ctx.fill() + ctx.fillStyle = "#AAA" + ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75) // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75); - y += h; + ctx.fillStyle = "#777" + // @ts-expect-error slot type issue + ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75) + y += h } //add + button if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { - this.showSubgraphPropertiesDialogRight(subnode); + this.showSubgraphPropertiesDialogRight(subnode) } } //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm drawButton(x: number, y: number, w: number, h: number, text?: string, bgcolor?: CanvasColour, hovercolor?: CanvasColour, textcolor?: CanvasColour): boolean { - var ctx = this.ctx; - bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; - hovercolor = hovercolor || "#555"; - textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; - var pos = this.ds.convertOffsetToCanvas(this.graph_mouse); - var hover = LiteGraph.isInsideRectangle(pos[0], pos[1], x, y, w, h); - pos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null; + const ctx = this.ctx + bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR + hovercolor = hovercolor || "#555" + textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR + let pos = this.ds.convertOffsetToCanvas(this.graph_mouse) + const hover = LiteGraph.isInsideRectangle(pos[0], pos[1], x, y, w, h) + pos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null if (pos) { - var rect = this.canvas.getBoundingClientRect(); - pos[0] -= rect.left; - pos[1] -= rect.top; + const rect = this.canvas.getBoundingClientRect() + pos[0] -= rect.left + pos[1] -= rect.top } - var clicked = pos && LiteGraph.isInsideRectangle(pos[0], pos[1], x, y, w, h); + const clicked = pos && LiteGraph.isInsideRectangle(pos[0], pos[1], x, y, w, h) - ctx.fillStyle = hover ? hovercolor : bgcolor; - if (clicked) - ctx.fillStyle = "#AAA"; - ctx.beginPath(); - ctx.roundRect(x, y, w, h, [4]); - ctx.fill(); + ctx.fillStyle = hover ? hovercolor : bgcolor + if (clicked) ctx.fillStyle = "#AAA" + ctx.beginPath() + ctx.roundRect(x, y, w, h, [4]) + ctx.fill() if (text != null) { if (text.constructor == String) { - ctx.fillStyle = textcolor; - ctx.textAlign = "center"; - ctx.font = ((h * 0.65) | 0) + "px Arial"; - ctx.fillText(text, x + w * 0.5, y + h * 0.75); - ctx.textAlign = "left"; + ctx.fillStyle = textcolor + ctx.textAlign = "center" + ctx.font = ((h * 0.65) | 0) + "px Arial" + ctx.fillText(text, x + w * 0.5, y + h * 0.75) + ctx.textAlign = "left" } } - var was_clicked = clicked && !this.block_click; - if (clicked) - this.blockClick(); - return was_clicked; + const was_clicked = clicked && !this.block_click + if (clicked) this.blockClick() + return was_clicked } isAreaClicked(x: number, y: number, w: number, h: number, hold_click: boolean): boolean { - var pos = this.mouse; - var hover = LiteGraph.isInsideRectangle(pos[0], pos[1], x, y, w, h); - pos = this.last_click_position; - var clicked = pos && LiteGraph.isInsideRectangle(pos[0], pos[1], x, y, w, h); - var was_clicked = clicked && !this.block_click; - if (clicked && hold_click) - this.blockClick(); - return was_clicked; + const clickPos = this.last_click_position + const clicked = clickPos && LiteGraph.isInsideRectangle(clickPos[0], clickPos[1], x, y, w, h) + const was_clicked = clicked && !this.block_click + if (clicked && hold_click) this.blockClick() + return was_clicked } /** - * draws some useful stats in the corner of the canvas - * @method renderInfo - **/ - renderInfo(ctx: CanvasRenderingContext2D, x: number, y: Number): void { - x = x || 10; - y = y || this.canvas.offsetHeight - 80; - - ctx.save(); - ctx.translate(x, y); - - ctx.font = "10px Arial"; - ctx.fillStyle = "#888"; - ctx.textAlign = "left"; + * draws some useful stats in the corner of the canvas + **/ + renderInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void { + x = x || 10 + y = y || this.canvas.offsetHeight - 80 + + ctx.save() + ctx.translate(x, y) + + ctx.font = "10px Arial" + ctx.fillStyle = "#888" + ctx.textAlign = "left" if (this.graph) { - ctx.fillText("T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13 * 1); - ctx.fillText("I: " + this.graph.iteration, 5, 13 * 2); - ctx.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 13 * 3); - ctx.fillText("V: " + this.graph._version, 5, 13 * 4); - ctx.fillText("FPS:" + this.fps.toFixed(2), 5, 13 * 5); + ctx.fillText("T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13 * 1) + ctx.fillText("I: " + this.graph.iteration, 5, 13 * 2) + ctx.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 13 * 3) + ctx.fillText("V: " + this.graph._version, 5, 13 * 4) + ctx.fillText("FPS:" + this.fps.toFixed(2), 5, 13 * 5) } else { - ctx.fillText("No graph selected", 5, 13 * 1); + ctx.fillText("No graph selected", 5, 13 * 1) } - ctx.restore(); + ctx.restore() } /** - * draws the back canvas (the one containing the background and the connections) - * @method drawBackCanvas - **/ + * draws the back canvas (the one containing the background and the connections) + **/ drawBackCanvas(): void { - var canvas = this.bgcanvas; + const canvas = this.bgcanvas if (canvas.width != this.canvas.width || canvas.height != this.canvas.height) { - canvas.width = this.canvas.width; - canvas.height = this.canvas.height; + canvas.width = this.canvas.width + canvas.height = this.canvas.height } if (!this.bgctx) { - this.bgctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.bgctx; - if (ctx.start) { - ctx.start(); + this.bgctx = this.bgcanvas.getContext("2d") } + const ctx = this.bgctx + // TODO: Remove this + // @ts-expect-error + if (ctx.start) ctx.start() - var viewport = this.viewport || [0, 0, ctx.canvas.width, ctx.canvas.height]; + const viewport = this.viewport || [0, 0, ctx.canvas.width, ctx.canvas.height] //clear if (this.clear_background) { - ctx.clearRect(viewport[0], viewport[1], viewport[2], viewport[3]); + ctx.clearRect(viewport[0], viewport[1], viewport[2], viewport[3]) } //show subgraph stack header - if (this._graph_stack && this._graph_stack.length) { - ctx.save(); - var parent_graph = this._graph_stack[this._graph_stack.length - 1]; - var subgraph_node = this.graph._subgraph_node; - ctx.strokeStyle = subgraph_node.bgcolor; - ctx.lineWidth = 10; - ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2); - ctx.lineWidth = 1; - ctx.font = "40px Arial"; - ctx.textAlign = "center"; - ctx.fillStyle = subgraph_node.bgcolor || "#AAA"; - var title = ""; - for (var i = 1; i < this._graph_stack.length; ++i) { + if (this._graph_stack?.length) { + ctx.save() + const subgraph_node = this.graph._subgraph_node + ctx.strokeStyle = subgraph_node.bgcolor + ctx.lineWidth = 10 + ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2) + ctx.lineWidth = 1 + ctx.font = "40px Arial" + ctx.textAlign = "center" + ctx.fillStyle = subgraph_node.bgcolor || "#AAA" + let title = "" + for (let i = 1; i < this._graph_stack.length; ++i) { title += - this._graph_stack[i]._subgraph_node.getTitle() + " >> "; + this._graph_stack[i]._subgraph_node.getTitle() + " >> " } ctx.fillText( title + subgraph_node.getTitle(), canvas.width * 0.5, 40 - ); - ctx.restore(); + ) + ctx.restore() } - var bg_already_painted = false; - if (this.onRenderBackground) { - bg_already_painted = this.onRenderBackground(canvas, ctx); - } + const bg_already_painted = this.onRenderBackground + ? this.onRenderBackground(canvas, ctx) + : false //reset in case of error if (!this.viewport) { - let scale = window.devicePixelRatio; - ctx.restore(); - ctx.setTransform(scale, 0, 0, scale, 0, 0); + const scale = window.devicePixelRatio + ctx.restore() + ctx.setTransform(scale, 0, 0, scale, 0, 0) } - this.visible_links.length = 0; + this.visible_links.length = 0 if (this.graph) { //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); + ctx.save() + this.ds.toCanvasContext(ctx) //render BG if (this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color) { - ctx.fillStyle = this.clear_background_color; + ctx.fillStyle = this.clear_background_color ctx.fillRect( this.visible_area[0], this.visible_area[1], this.visible_area[2], this.visible_area[3] - ); + ) } if (this.background_image && @@ -4379,59 +4124,61 @@ export class LGraphCanvas { !bg_already_painted) { if (this.zoom_modify_alpha) { ctx.globalAlpha = - (1.0 - 0.5 / this.ds.scale) * this.editor_alpha; + (1.0 - 0.5 / this.ds.scale) * this.editor_alpha } else { - ctx.globalAlpha = this.editor_alpha; + ctx.globalAlpha = this.editor_alpha } - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = + ctx.imageSmoothingEnabled = false if (!this._bg_img || this._bg_img.name != this.background_image) { - this._bg_img = new Image(); - this._bg_img.name = this.background_image; - this._bg_img.src = this.background_image; - var that = this; + this._bg_img = new Image() + this._bg_img.name = this.background_image + this._bg_img.src = this.background_image + const that = this this._bg_img.onload = function () { - that.draw(true, true); - }; + that.draw(true, true) + } } - var pattern = null; - if (this._pattern == null && this._bg_img.width > 0) { - pattern = ctx.createPattern(this._bg_img, "repeat"); - this._pattern_img = this._bg_img; - this._pattern = pattern; - } else { - pattern = this._pattern; + let pattern = this._pattern + if (pattern == null && this._bg_img.width > 0) { + pattern = ctx.createPattern(this._bg_img, "repeat") + this._pattern_img = this._bg_img + this._pattern = pattern } + + // NOTE: This ridiculous kludge provides a significant performance increase when rendering many large (> canvas width) paths in HTML canvas. + // I could find no documentation or explanation. Requires that the BG image is set. if (pattern) { - ctx.fillStyle = pattern; + ctx.fillStyle = pattern ctx.fillRect( this.visible_area[0], this.visible_area[1], this.visible_area[2], this.visible_area[3] - ); - ctx.fillStyle = "transparent"; + ) + ctx.fillStyle = "transparent" } - ctx.globalAlpha = 1.0; - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled + ctx.globalAlpha = 1.0 + ctx.imageSmoothingEnabled = true } //groups if (this.graph._groups.length && !this.live_mode) { - this.drawGroups(canvas, ctx); + this.drawGroups(canvas, ctx) } - if (this.onDrawBackground) { - this.onDrawBackground(ctx, this.visible_area); - } + this.onDrawBackground?.(ctx, this.visible_area) + // TODO: Just delete this... + // @ts-expect-error if (this.onBackgroundRender) { //LEGACY console.error( "WARNING! onBackgroundRender deprecated, now is named onDrawBackground " - ); - this.onBackgroundRender = null; + ) + // @ts-expect-error + this.onBackgroundRender = null } //DEBUG: show clipping area @@ -4439,115 +4186,105 @@ export class LGraphCanvas { //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20); //bg if (this.render_canvas_border) { - ctx.strokeStyle = "#235"; - ctx.strokeRect(0, 0, canvas.width, canvas.height); + ctx.strokeStyle = "#235" + ctx.strokeRect(0, 0, canvas.width, canvas.height) } if (this.render_connections_shadows) { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 6; + ctx.shadowColor = "#000" + ctx.shadowOffsetX = 0 + ctx.shadowOffsetY = 0 + ctx.shadowBlur = 6 } else { - ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.shadowColor = "rgba(0,0,0,0)" } //draw connections if (!this.live_mode) { - this.drawConnections(ctx); + this.drawConnections(ctx) } - ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.shadowColor = "rgba(0,0,0,0)" //restore state - ctx.restore(); + ctx.restore() } - if (ctx.finish) { - ctx.finish(); - } + // TODO: Remove this + // @ts-expect-error + ctx.finish?.() - this.dirty_bgcanvas = false; - this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas + this.dirty_bgcanvas = false + //to force to repaint the front canvas with the bgcanvas + // But why would you actually want to do this? + this.dirty_canvas = true } /** - * draws the given node inside the canvas - * @method drawNode - **/ + * draws the given node inside the canvas + **/ drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void { - var glow = false; - this.current_node = node; - - var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; - var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; - - //shadow and glow - if (node.mouseOver) { - glow = true; - } - - var low_quality = this.ds.scale < 0.6; //zoomed out + this.current_node = node + // @ts-expect-error ctor props + const color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR + // @ts-expect-error ctor props + let bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR + const low_quality = this.ds.scale < 0.6 //zoomed out //only render if it forces it to do it if (this.live_mode) { if (!node.flags.collapsed) { - ctx.shadowColor = "transparent"; - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } + ctx.shadowColor = "transparent" + node.onDrawForeground?.(ctx, this, this.canvas) } - return; + return } - var editor_alpha = this.editor_alpha; - ctx.globalAlpha = editor_alpha; + const editor_alpha = this.editor_alpha + ctx.globalAlpha = editor_alpha if (this.render_shadows && !low_quality) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - ctx.shadowOffsetX = 2 * this.ds.scale; - ctx.shadowOffsetY = 2 * this.ds.scale; - ctx.shadowBlur = 3 * this.ds.scale; + ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR + ctx.shadowOffsetX = 2 * this.ds.scale + ctx.shadowOffsetY = 2 * this.ds.scale + ctx.shadowBlur = 3 * this.ds.scale } else { - ctx.shadowColor = "transparent"; + ctx.shadowColor = "transparent" } //custom draw collapsed method (draw after shadows because they are affected) - if (node.flags.collapsed && - node.onDrawCollapsed && - node.onDrawCollapsed(ctx, this) == true) { - return; - } + if (node.flags.collapsed && node.onDrawCollapsed?.(ctx, this) == true) + return //clip if required (mask) - var shape = node._shape || LiteGraph.BOX_SHAPE; - var size = LGraphCanvas.#temp_vec2; - LGraphCanvas.#temp_vec2.set(node.size); - var horizontal = node.horizontal; // || node.flags.horizontal; + const shape = node._shape || LiteGraph.BOX_SHAPE + const size = LGraphCanvas.#temp_vec2 + LGraphCanvas.#temp_vec2.set(node.size) + const horizontal = node.horizontal // || node.flags.horizontal; if (node.flags.collapsed) { - ctx.font = this.inner_text_font; - var title = node.getTitle ? node.getTitle() : node.title; + ctx.font = this.inner_text_font + const title = node.getTitle ? node.getTitle() : node.title if (title != null) { node._collapsed_width = Math.min( node.size[0], ctx.measureText(title).width + LiteGraph.NODE_TITLE_HEIGHT * 2 - ); //LiteGraph.NODE_COLLAPSED_WIDTH; - size[0] = node._collapsed_width; - size[1] = 0; + ) //LiteGraph.NODE_COLLAPSED_WIDTH; + size[0] = node._collapsed_width + size[1] = 0 } } if (node.clip_area) { //Start clipping - ctx.save(); - ctx.beginPath(); + ctx.save() + ctx.beginPath() if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect(0, 0, size[0], size[1]); + ctx.rect(0, 0, size[0], size[1]) } else if (shape == LiteGraph.ROUND_SHAPE) { - ctx.roundRect(0, 0, size[0], size[1], [10]); + ctx.roundRect(0, 0, size[0], size[1], [10]) } else if (shape == LiteGraph.CIRCLE_SHAPE) { ctx.arc( size[0] * 0.5, @@ -4555,14 +4292,14 @@ export class LGraphCanvas { size[0] * 0.5, 0, Math.PI * 2 - ); + ) } - ctx.clip(); + ctx.clip() } //draw shape if (node.has_errors) { - bgcolor = "red"; + bgcolor = "red" } this.drawNodeShape( node, @@ -4572,47 +4309,43 @@ export class LGraphCanvas { bgcolor, node.is_selected, node.mouseOver - ); + ) if (!low_quality) { - node.drawBadges(ctx); + node.drawBadges(ctx) } - ctx.shadowColor = "transparent"; + ctx.shadowColor = "transparent" //draw foreground - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } + node.onDrawForeground?.(ctx, this, this.canvas) //connection slots - ctx.textAlign = horizontal ? "center" : "left"; - ctx.font = this.inner_text_font; - - var render_text = !low_quality; + ctx.textAlign = horizontal ? "center" : "left" + ctx.font = this.inner_text_font - var out_slot = this.connecting_links ? this.connecting_links[0].output : null; - var in_slot = this.connecting_links ? this.connecting_links[0].input : null; - ctx.lineWidth = 1; - - var max_y = 0; - var slot_pos = new Float32Array(2); //to reuse + const render_text = !low_quality + const out_slot = this.connecting_links ? this.connecting_links[0].output : null + const in_slot = this.connecting_links ? this.connecting_links[0].input : null + ctx.lineWidth = 1 + let max_y = 0 + const slot_pos = new Float32Array(2) //to reuse //render inputs and outputs if (!node.flags.collapsed) { //input connection slots if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; + for (let i = 0; i < node.inputs.length; i++) { + const slot = node.inputs[i] - var slot_type = slot.type; + const slot_type = slot.type - ctx.globalAlpha = editor_alpha; + ctx.globalAlpha = editor_alpha //change opacity of incompatible slots when dragging a connection if (out_slot && !LiteGraph.isValidConnection(slot.type, out_slot.type)) { - ctx.globalAlpha = 0.4 * editor_alpha; + ctx.globalAlpha = 0.4 * editor_alpha } ctx.fillStyle = @@ -4623,13 +4356,13 @@ export class LGraphCanvas { : slot.color_off || this.default_connection_color_byTypeOff[slot_type] || this.default_connection_color_byType[slot_type] || - this.default_connection_color.input_off; + this.default_connection_color.input_off - var pos = node.getConnectionPos(true, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; + const pos = node.getConnectionPos(true, i, slot_pos) + pos[0] -= node.pos[0] + pos[1] -= node.pos[1] if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; + max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5 } drawSlot(ctx, slot, pos, { @@ -4640,29 +4373,29 @@ export class LGraphCanvas { label_position: LabelPosition.Right, // Input slot is not stroked. do_stroke: false, - }); + }) } } //output connection slots - ctx.textAlign = horizontal ? "center" : "right"; - ctx.strokeStyle = "black"; + ctx.textAlign = horizontal ? "center" : "right" + ctx.strokeStyle = "black" if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; + for (let i = 0; i < node.outputs.length; i++) { + const slot = node.outputs[i] - var slot_type = slot.type; + const slot_type = slot.type //change opacity of incompatible slots when dragging a connection if (in_slot && !LiteGraph.isValidConnection(slot_type, in_slot.type)) { - ctx.globalAlpha = 0.4 * editor_alpha; + ctx.globalAlpha = 0.4 * editor_alpha } - var pos = node.getConnectionPos(false, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; + const pos = node.getConnectionPos(false, i, slot_pos) + pos[0] -= node.pos[0] + pos[1] -= node.pos[1] if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; + max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5 } ctx.fillStyle = @@ -4673,7 +4406,7 @@ export class LGraphCanvas { : slot.color_off || this.default_connection_color_byTypeOff[slot_type] || this.default_connection_color_byType[slot_type] || - this.default_connection_color.output_off; + this.default_connection_color.output_off drawSlot(ctx, slot, pos, { horizontal, @@ -4682,20 +4415,20 @@ export class LGraphCanvas { label_color: LiteGraph.NODE_TEXT_COLOR, label_position: LabelPosition.Left, do_stroke: true, - }); + }) } } - ctx.textAlign = "left"; - ctx.globalAlpha = 1; + ctx.textAlign = "left" + ctx.globalAlpha = 1 if (node.widgets) { - var widgets_y = max_y; + let widgets_y = max_y if (horizontal || node.widgets_up) { - widgets_y = 2; + widgets_y = 2 } if (node.widgets_start_y != null) - widgets_y = node.widgets_start_y; + widgets_y = node.widgets_start_y this.drawNodeWidgets( node, widgets_y, @@ -4703,147 +4436,146 @@ export class LGraphCanvas { this.node_widget && this.node_widget[0] == node ? this.node_widget[1] : null - ); + ) } } else if (this.render_collapsed_slots) { //if collapsed - var input_slot = null; - var output_slot = null; + let input_slot = null + let output_slot = null + let slot //get first connected slot to render if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; + for (let i = 0; i < node.inputs.length; i++) { + slot = node.inputs[i] if (slot.link == null) { - continue; + continue } - input_slot = slot; - break; + input_slot = slot + break } } if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; + for (let i = 0; i < node.outputs.length; i++) { + slot = node.outputs[i] if (!slot.links || !slot.links.length) { - continue; + continue } - output_slot = slot; + output_slot = slot } } if (input_slot) { - var x = 0; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center + let x = 0 + let y = LiteGraph.NODE_TITLE_HEIGHT * -0.5 //center if (horizontal) { - x = node._collapsed_width * 0.5; - y = -LiteGraph.NODE_TITLE_HEIGHT; + x = node._collapsed_width * 0.5 + y = -LiteGraph.NODE_TITLE_HEIGHT } - ctx.fillStyle = "#686"; - ctx.beginPath(); + ctx.fillStyle = "#686" + ctx.beginPath() if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); + ctx.rect(x - 7 + 0.5, y - 4, 14, 8) } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 8, y); - ctx.lineTo(x + -4, y - 4); - ctx.lineTo(x + -4, y + 4); - ctx.closePath(); + ctx.moveTo(x + 8, y) + ctx.lineTo(x + -4, y - 4) + ctx.lineTo(x + -4, y + 4) + ctx.closePath() } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); + ctx.arc(x, y, 4, 0, Math.PI * 2) } - ctx.fill(); + ctx.fill() } if (output_slot) { - var x = node._collapsed_width; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center + let x = node._collapsed_width + let y = LiteGraph.NODE_TITLE_HEIGHT * -0.5 //center if (horizontal) { - x = node._collapsed_width * 0.5; - y = 0; + x = node._collapsed_width * 0.5 + y = 0 } - ctx.fillStyle = "#686"; - ctx.strokeStyle = "black"; - ctx.beginPath(); + ctx.fillStyle = "#686" + ctx.strokeStyle = "black" + ctx.beginPath() if (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); + ctx.rect(x - 7 + 0.5, y - 4, 14, 8) } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 6, y); - ctx.lineTo(x - 6, y - 4); - ctx.lineTo(x - 6, y + 4); - ctx.closePath(); + ctx.moveTo(x + 6, y) + ctx.lineTo(x - 6, y - 4) + ctx.lineTo(x - 6, y + 4) + ctx.closePath() } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); + ctx.arc(x, y, 4, 0, Math.PI * 2) } - ctx.fill(); + ctx.fill() //ctx.stroke(); } } if (node.clip_area) { - ctx.restore(); + ctx.restore() } - ctx.globalAlpha = 1.0; + ctx.globalAlpha = 1.0 } //used by this.over_link_center drawLinkTooltip(ctx: CanvasRenderingContext2D, link: LLink): void { - var pos = link._pos; - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 3, 0, Math.PI * 2); - ctx.fill(); + const pos = link._pos + ctx.fillStyle = "black" + ctx.beginPath() + ctx.arc(pos[0], pos[1], 3, 0, Math.PI * 2) + ctx.fill() if (link.data == null) - return; + return - if (this.onDrawLinkTooltip) - if (this.onDrawLinkTooltip(ctx, link, this) == true) - return; + if (this.onDrawLinkTooltip?.(ctx, link, this) == true) + return - var data = link.data; - var text = null; + // TODO: Better value typing + const data = link.data + let text: string = null - if (data.constructor === Number) - text = data.toFixed(2); - else if (data.constructor === String) - text = "\"" + data + "\""; - else if (data.constructor === Boolean) - text = String(data); + if (typeof data === "number") + text = data.toFixed(2) + else if (typeof data === "string") + text = "\"" + data + "\"" + else if (typeof data === "boolean") + text = String(data) else if (data.toToolTip) - text = data.toToolTip(); - - + text = data.toToolTip() else - text = "[" + data.constructor.name + "]"; - - if (text == null) - return; - text = text.substr(0, 30); //avoid weird - - ctx.font = "14px Courier New"; - var info = ctx.measureText(text); - var w = info.width + 20; - var h = 24; - ctx.shadowColor = "black"; - ctx.shadowOffsetX = 2; - ctx.shadowOffsetY = 2; - ctx.shadowBlur = 3; - ctx.fillStyle = "#454"; - ctx.beginPath(); - ctx.roundRect(pos[0] - w * 0.5, pos[1] - 15 - h, w, h, [3]); - ctx.moveTo(pos[0] - 10, pos[1] - 15); - ctx.lineTo(pos[0] + 10, pos[1] - 15); - ctx.lineTo(pos[0], pos[1] - 5); - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.textAlign = "center"; - ctx.fillStyle = "#CEC"; - ctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3); + text = "[" + data.constructor.name + "]" + + if (text == null) return + + // Hard-coded tooltip limit + text = text.substring(0, 30) + + ctx.font = "14px Courier New" + const info = ctx.measureText(text) + const w = info.width + 20 + const h = 24 + ctx.shadowColor = "black" + ctx.shadowOffsetX = 2 + ctx.shadowOffsetY = 2 + ctx.shadowBlur = 3 + ctx.fillStyle = "#454" + ctx.beginPath() + ctx.roundRect(pos[0] - w * 0.5, pos[1] - 15 - h, w, h, [3]) + ctx.moveTo(pos[0] - 10, pos[1] - 15) + ctx.lineTo(pos[0] + 10, pos[1] - 15) + ctx.lineTo(pos[0], pos[1] - 5) + ctx.fill() + ctx.shadowColor = "transparent" + ctx.textAlign = "center" + ctx.fillStyle = "#CEC" + ctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3) } /** * draws the shape of the given node in the canvas - * @method drawNodeShape **/ drawNodeShape( node: LGraphNode, @@ -4855,38 +4587,40 @@ export class LGraphCanvas { mouse_over: boolean ): void { //bg rect - ctx.strokeStyle = fgcolor; - ctx.fillStyle = bgcolor; + ctx.strokeStyle = fgcolor + ctx.fillStyle = bgcolor - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - var low_quality = this.ds.scale < 0.5; + const title_height = LiteGraph.NODE_TITLE_HEIGHT + const low_quality = this.ds.scale < 0.5 //render node area depending on shape - var shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; + // @ts-expect-error ctor props + const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE - var title_mode = node.constructor.title_mode; + // @ts-expect-error ctor props + const title_mode = node.constructor.title_mode - var render_title = true; + let render_title = true if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) { - render_title = false; + render_title = false } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) { - render_title = true; + render_title = true } - var area = LGraphCanvas.#tmp_area; - area[0] = 0; //x - area[1] = render_title ? -title_height : 0; //y - area[2] = size[0] + 1; //w - area[3] = render_title ? size[1] + title_height : size[1]; //h + const area = LGraphCanvas.#tmp_area + area[0] = 0 //x + area[1] = render_title ? -title_height : 0 //y + area[2] = size[0] + 1 //w + area[3] = render_title ? size[1] + title_height : size[1] //h - var old_alpha = ctx.globalAlpha; + const old_alpha = ctx.globalAlpha //full node shape //if(node.flags.collapsed) { - ctx.beginPath(); + ctx.beginPath() if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.fillRect(area[0], area[1], area[2], area[3]); + ctx.fillRect(area[0], area[1], area[2], area[3]) } else if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE) { ctx.roundRect( @@ -4895,7 +4629,7 @@ export class LGraphCanvas { area[2], area[3], shape == LiteGraph.CARD_SHAPE ? [this.round_radius, this.round_radius, 0, 0] : [this.round_radius] - ); + ) } else if (shape == LiteGraph.CIRCLE_SHAPE) { ctx.arc( size[0] * 0.5, @@ -4903,53 +4637,53 @@ export class LGraphCanvas { size[0] * 0.5, 0, Math.PI * 2 - ); + ) } - ctx.fill(); + ctx.fill() //separator if (!node.flags.collapsed && render_title) { - ctx.shadowColor = "transparent"; - ctx.fillStyle = "rgba(0,0,0,0.2)"; - ctx.fillRect(0, -1, area[2], 2); + ctx.shadowColor = "transparent" + ctx.fillStyle = "rgba(0,0,0,0.2)" + ctx.fillRect(0, -1, area[2], 2) } } - ctx.shadowColor = "transparent"; + ctx.shadowColor = "transparent" - if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse); - } + node.onDrawBackground?.(ctx, this, this.canvas, this.graph_mouse) //title bg (remember, it is rendered ABOVE the node) if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { //title bar if (node.onDrawTitleBar) { - node.onDrawTitleBar(ctx, title_height, size, this.ds.scale, fgcolor); + node.onDrawTitleBar(ctx, title_height, size, this.ds.scale, fgcolor) } else if (title_mode != LiteGraph.TRANSPARENT_TITLE && + // @ts-expect-error ctor props (node.constructor.title_color || this.render_title_colored)) { - var title_color = node.constructor.title_color || fgcolor; + // @ts-expect-error ctor props + const title_color = node.constructor.title_color || fgcolor if (node.flags.collapsed) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; + ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR } //* gradient test if (this.use_gradients) { - var grad = LGraphCanvas.gradients[title_color]; + let grad = LGraphCanvas.gradients[title_color] if (!grad) { - grad = LGraphCanvas.gradients[title_color] = ctx.createLinearGradient(0, 0, 400, 0); - grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException - grad.addColorStop(1, "#000"); + grad = LGraphCanvas.gradients[title_color] = ctx.createLinearGradient(0, 0, 400, 0) + grad.addColorStop(0, title_color) // TODO refactor: validate color !! prevent DOMException + grad.addColorStop(1, "#000") } - ctx.fillStyle = grad; + ctx.fillStyle = grad } else { - ctx.fillStyle = title_color; + ctx.fillStyle = title_color } //ctx.globalAlpha = 0.5 * old_alpha; - ctx.beginPath(); + ctx.beginPath() if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.rect(0, -title_height, size[0] + 1, title_height); + ctx.rect(0, -title_height, size[0] + 1, title_height) } else if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE) { ctx.roundRect( 0, @@ -4957,76 +4691,76 @@ export class LGraphCanvas { size[0] + 1, title_height, node.flags.collapsed ? [this.round_radius] : [this.round_radius, this.round_radius, 0, 0] - ); + ) } - ctx.fill(); - ctx.shadowColor = "transparent"; + ctx.fill() + ctx.shadowColor = "transparent" } - var colState = false; + let colState: string | boolean = false if (LiteGraph.node_box_coloured_by_mode) { if (LiteGraph.NODE_MODES_COLORS[node.mode]) { - colState = LiteGraph.NODE_MODES_COLORS[node.mode]; + colState = LiteGraph.NODE_MODES_COLORS[node.mode] } } if (LiteGraph.node_box_coloured_when_on) { - colState = node.action_triggered ? "#FFF" : (node.execute_triggered ? "#AAA" : colState); + colState = node.action_triggered ? "#FFF" : (node.execute_triggered ? "#AAA" : colState) } //title box - var box_size = 10; + const box_size = 10 if (node.onDrawTitleBox) { - node.onDrawTitleBox(ctx, title_height, size, this.ds.scale); + node.onDrawTitleBox(ctx, title_height, size, this.ds.scale) } else if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CIRCLE_SHAPE || shape == LiteGraph.CARD_SHAPE) { if (low_quality) { - ctx.fillStyle = "black"; - ctx.beginPath(); + ctx.fillStyle = "black" + ctx.beginPath() ctx.arc( title_height * 0.5, title_height * -0.5, box_size * 0.5 + 1, 0, Math.PI * 2 - ); - ctx.fill(); + ) + ctx.fill() } - ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR if (low_quality) - ctx.fillRect(title_height * 0.5 - box_size * 0.5, title_height * -0.5 - box_size * 0.5, box_size, box_size); + ctx.fillRect(title_height * 0.5 - box_size * 0.5, title_height * -0.5 - box_size * 0.5, box_size, box_size) else { - ctx.beginPath(); + ctx.beginPath() ctx.arc( title_height * 0.5, title_height * -0.5, box_size * 0.5, 0, Math.PI * 2 - ); - ctx.fill(); + ) + ctx.fill() } } else { if (low_quality) { - ctx.fillStyle = "black"; + ctx.fillStyle = "black" ctx.fillRect( (title_height - box_size) * 0.5 - 1, (title_height + box_size) * -0.5 - 1, box_size + 2, box_size + 2 - ); + ) } - ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR ctx.fillRect( (title_height - box_size) * 0.5, (title_height + box_size) * -0.5, box_size, box_size - ); + ) } - ctx.globalAlpha = old_alpha; + ctx.globalAlpha = old_alpha //title text if (node.onDrawTitleText) { @@ -5037,72 +4771,68 @@ export class LGraphCanvas { this.ds.scale, this.title_text_font, selected - ); + ) } if (!low_quality) { - ctx.font = this.title_text_font; - var title = String(node.getTitle()) + (node.pinned ? "📌" : ""); + ctx.font = this.title_text_font + const title = String(node.getTitle()) + (node.pinned ? "📌" : "") if (title) { if (selected) { - ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR; + ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR } else { ctx.fillStyle = - node.constructor.title_text_color || - this.node_title_color; + // @ts-expect-error ctor props + node.constructor.title_text_color || this.node_title_color } if (node.flags.collapsed) { - ctx.textAlign = "left"; - var measure = ctx.measureText(title); + ctx.textAlign = "left" + // const measure = ctx.measureText(title) ctx.fillText( title.substr(0, 20), //avoid urls too long title_height, // + measure.width * 0.5, LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); - ctx.textAlign = "left"; + ) + ctx.textAlign = "left" } else { - ctx.textAlign = "left"; + ctx.textAlign = "left" ctx.fillText( title, title_height, LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); + ) } } } //subgraph box if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) { - var w = LiteGraph.NODE_TITLE_HEIGHT; - var x = node.size[0] - w; - var over = LiteGraph.isInsideRectangle(this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x + 2, -w + 2, w - 4, w - 4); - ctx.fillStyle = over ? "#888" : "#555"; - if (shape == LiteGraph.BOX_SHAPE || low_quality) - ctx.fillRect(x + 2, -w + 2, w - 4, w - 4); - + const w = LiteGraph.NODE_TITLE_HEIGHT + const x = node.size[0] - w + const over = LiteGraph.isInsideRectangle(this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x + 2, -w + 2, w - 4, w - 4) + ctx.fillStyle = over ? "#888" : "#555" + if (shape == LiteGraph.BOX_SHAPE || low_quality) { + ctx.fillRect(x + 2, -w + 2, w - 4, w - 4) + } else { - ctx.beginPath(); - ctx.roundRect(x + 2, -w + 2, w - 4, w - 4, [4]); - ctx.fill(); + ctx.beginPath() + ctx.roundRect(x + 2, -w + 2, w - 4, w - 4, [4]) + ctx.fill() } - ctx.fillStyle = "#333"; - ctx.beginPath(); - ctx.moveTo(x + w * 0.2, -w * 0.6); - ctx.lineTo(x + w * 0.8, -w * 0.6); - ctx.lineTo(x + w * 0.5, -w * 0.3); - ctx.fill(); + ctx.fillStyle = "#333" + ctx.beginPath() + ctx.moveTo(x + w * 0.2, -w * 0.6) + ctx.lineTo(x + w * 0.8, -w * 0.6) + ctx.lineTo(x + w * 0.5, -w * 0.3) + ctx.fill() } //custom title render - if (node.onDrawTitle) { - node.onDrawTitle(ctx); - } + node.onDrawTitle?.(ctx) } //render selection marker if (selected) { - if (node.onBounding) { - node.onBounding(area); - } + node.onBounding?.(area) this.drawSelectionBounding( ctx, @@ -5114,12 +4844,12 @@ export class LGraphCanvas { fgcolor, collapsed: node.flags?.collapsed } - ); + ) } // these counter helps in conditioning drawing based on if the node has been executed or an action occurred - if (node.execute_triggered > 0) node.execute_triggered--; - if (node.action_triggered > 0) node.action_triggered--; + if (node.execute_triggered > 0) node.execute_triggered-- + if (node.action_triggered > 0) node.action_triggered-- } /** @@ -5148,131 +4878,123 @@ export class LGraphCanvas { ) { // Adjust area if title is transparent if (title_mode === LiteGraph.TRANSPARENT_TITLE) { - area[1] -= title_height; - area[3] += title_height; + area[1] -= title_height + area[3] += title_height } // Set up context - ctx.lineWidth = 1; - ctx.globalAlpha = 0.8; - ctx.beginPath(); + ctx.lineWidth = 1 + ctx.globalAlpha = 0.8 + ctx.beginPath() // Draw shape based on type - const [x, y, width, height] = area; + const [x, y, width, height] = area switch (shape) { case LiteGraph.BOX_SHAPE: { - ctx.rect(x - padding, y - padding, width + 2 * padding, height + 2 * padding); - break; + ctx.rect(x - padding, y - padding, width + 2 * padding, height + 2 * padding) + break } case LiteGraph.ROUND_SHAPE: case LiteGraph.CARD_SHAPE: { - const radius = this.round_radius * 2; - const isCollapsed = shape === LiteGraph.CARD_SHAPE && collapsed; - const cornerRadii = isCollapsed || shape === LiteGraph.ROUND_SHAPE ? [radius] : [radius, 2, radius, 2]; - ctx.roundRect(x - padding, y - padding, width + 2 * padding, height + 2 * padding, cornerRadii); - break; + const radius = this.round_radius * 2 + const isCollapsed = shape === LiteGraph.CARD_SHAPE && collapsed + const cornerRadii = isCollapsed || shape === LiteGraph.ROUND_SHAPE ? [radius] : [radius, 2, radius, 2] + ctx.roundRect(x - padding, y - padding, width + 2 * padding, height + 2 * padding, cornerRadii) + break } case LiteGraph.CIRCLE_SHAPE: { - const centerX = x + width / 2; - const centerY = y + height / 2; - const radius = Math.max(width, height) / 2 + padding; - ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); - break; + const centerX = x + width / 2 + const centerY = y + height / 2 + const radius = Math.max(width, height) / 2 + padding + ctx.arc(centerX, centerY, radius, 0, Math.PI * 2) + break } } // Stroke the shape - ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR; - ctx.stroke(); + ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR + ctx.stroke() // Reset context - ctx.strokeStyle = fgcolor; - ctx.globalAlpha = 1; + ctx.strokeStyle = fgcolor + ctx.globalAlpha = 1 } drawConnections(ctx: CanvasRenderingContext2D): void { - var now = LiteGraph.getTime(); - var visible_area = this.visible_area; - LGraphCanvas.#margin_area[0] = visible_area[0] - 20; - LGraphCanvas.#margin_area[1] = visible_area[1] - 20; - LGraphCanvas.#margin_area[2] = visible_area[2] + 40; - LGraphCanvas.#margin_area[3] = visible_area[3] + 40; + const now = LiteGraph.getTime() + const visible_area = this.visible_area + LGraphCanvas.#margin_area[0] = visible_area[0] - 20 + LGraphCanvas.#margin_area[1] = visible_area[1] - 20 + LGraphCanvas.#margin_area[2] = visible_area[2] + 40 + LGraphCanvas.#margin_area[3] = visible_area[3] + 40 //draw connections - ctx.lineWidth = this.connections_width; + ctx.lineWidth = this.connections_width - ctx.fillStyle = "#AAA"; - ctx.strokeStyle = "#AAA"; - ctx.globalAlpha = this.editor_alpha; + ctx.fillStyle = "#AAA" + ctx.strokeStyle = "#AAA" + ctx.globalAlpha = this.editor_alpha //for every node - var nodes = this.graph._nodes; - for (var n = 0, l = nodes.length; n < l; ++n) { - var node = nodes[n]; + const nodes = this.graph._nodes + for (let n = 0, l = nodes.length; n < l; ++n) { + const node = nodes[n] //for every input (we render just inputs because it is easier as every slot can only have one input) - if (!node.inputs || !node.inputs.length) { - continue; - } + if (!node.inputs || !node.inputs.length) continue - for (var i = 0; i < node.inputs.length; ++i) { - var input = node.inputs[i]; - if (!input || input.link == null) { - continue; - } - var link_id = input.link; - var link = this.graph.links[link_id]; - if (!link) { - continue; - } + for (let i = 0; i < node.inputs.length; ++i) { + const input = node.inputs[i] + if (!input || input.link == null) continue + + const link_id = input.link + const link = this.graph.links[link_id] + if (!link) continue //find link info - var start_node = this.graph.getNodeById(link.origin_id); - if (start_node == null) { - continue; - } - var start_node_slot = link.origin_slot; - var start_node_slotpos = null; + const start_node = this.graph.getNodeById(link.origin_id) + if (start_node == null) continue + + const start_node_slot = link.origin_slot + let start_node_slotpos: Point = null if (start_node_slot == -1) { start_node_slotpos = [ start_node.pos[0] + 10, start_node.pos[1] + 10 - ]; + ] } else { start_node_slotpos = start_node.getConnectionPos( false, start_node_slot, LGraphCanvas.#tempA - ); + ) } - var end_node_slotpos = node.getConnectionPos(true, i, LGraphCanvas.#tempB); + const end_node_slotpos = node.getConnectionPos(true, i, LGraphCanvas.#tempB) //compute link bounding - LGraphCanvas.#link_bounding[0] = start_node_slotpos[0]; - LGraphCanvas.#link_bounding[1] = start_node_slotpos[1]; - LGraphCanvas.#link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0]; - LGraphCanvas.#link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1]; + LGraphCanvas.#link_bounding[0] = start_node_slotpos[0] + LGraphCanvas.#link_bounding[1] = start_node_slotpos[1] + LGraphCanvas.#link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0] + LGraphCanvas.#link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1] if (LGraphCanvas.#link_bounding[2] < 0) { - LGraphCanvas.#link_bounding[0] += LGraphCanvas.#link_bounding[2]; - LGraphCanvas.#link_bounding[2] = Math.abs(LGraphCanvas.#link_bounding[2]); + LGraphCanvas.#link_bounding[0] += LGraphCanvas.#link_bounding[2] + LGraphCanvas.#link_bounding[2] = Math.abs(LGraphCanvas.#link_bounding[2]) } if (LGraphCanvas.#link_bounding[3] < 0) { - LGraphCanvas.#link_bounding[1] += LGraphCanvas.#link_bounding[3]; - LGraphCanvas.#link_bounding[3] = Math.abs(LGraphCanvas.#link_bounding[3]); + LGraphCanvas.#link_bounding[1] += LGraphCanvas.#link_bounding[3] + LGraphCanvas.#link_bounding[3] = Math.abs(LGraphCanvas.#link_bounding[3]) } //skip links outside of the visible area of the canvas - if (!overlapBounding(LGraphCanvas.#link_bounding, LGraphCanvas.#margin_area)) { - continue; - } - - var start_slot = start_node.outputs[start_node_slot]; - var end_slot = node.inputs[i]; - if (!start_slot || !end_slot) { - continue; - } - var start_dir = start_slot.dir || - (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT); - var end_dir = end_slot.dir || - (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT); + if (!overlapBounding(LGraphCanvas.#link_bounding, LGraphCanvas.#margin_area)) + continue + + const start_slot = start_node.outputs[start_node_slot] + const end_slot = node.inputs[i] + if (!start_slot || !end_slot) + continue + const start_dir = start_slot.dir || + (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT) + const end_dir = end_slot.dir || + (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT) this.renderLink( ctx, @@ -5284,13 +5006,13 @@ export class LGraphCanvas { null, start_dir, end_dir - ); + ) //event triggered rendered on top if (link && link._last_time && now - link._last_time < 1000) { - var f = 2.0 - (now - link._last_time) * 0.002; - var tmp = ctx.globalAlpha; - ctx.globalAlpha = tmp * f; + const f = 2.0 - (now - link._last_time) * 0.002 + const tmp = ctx.globalAlpha + ctx.globalAlpha = tmp * f this.renderLink( ctx, start_node_slotpos, @@ -5301,26 +5023,26 @@ export class LGraphCanvas { "white", start_dir, end_dir - ); - ctx.globalAlpha = tmp; + ) + ctx.globalAlpha = tmp } } } - ctx.globalAlpha = 1; + ctx.globalAlpha = 1 } + /** - * draws a link between two points - * @method renderLink - * @param {vec2} a start pos - * @param {vec2} b end pos - * @param {Object} link the link object with all the link info - * @param {boolean} skip_border ignore the shadow of the link - * @param {boolean} flow show flow animation (for events) - * @param {string} color the color for the link - * @param {number} start_dir the direction enum - * @param {number} end_dir the direction enum - * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb) - **/ + * draws a link between two points + * @param {vec2} a start pos + * @param {vec2} b end pos + * @param {Object} link the link object with all the link info + * @param {boolean} skip_border ignore the shadow of the link + * @param {boolean} flow show flow animation (for events) + * @param {string} color the color for the link + * @param {LinkDirection} start_dir the direction enum + * @param {LinkDirection} end_dir the direction enum + * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb) + **/ renderLink(ctx: CanvasRenderingContext2D, a: Point, b: Point, @@ -5331,77 +5053,79 @@ export class LGraphCanvas { start_dir: LinkDirection, end_dir: LinkDirection, num_sublines?: number): void { + if (link) { - this.visible_links.push(link); + this.visible_links.push(link) } //choose color if (!color && link) { - color = link.color || LGraphCanvas.link_type_colors[link.type]; - } - if (!color) { - color = this.default_link_color; + // TODO: Remove ts-ignore when typescript LLink PR goes in. + // @ts-ignore Already resolved in parallel PR. + color = link.color || LGraphCanvas.link_type_colors[link.type] } + color ||= this.default_link_color if (link != null && this.highlighted_links[link.id]) { - color = "#FFF"; + color = "#FFF" } - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; + start_dir = start_dir || LiteGraph.RIGHT + end_dir = end_dir || LiteGraph.LEFT - var dist = distance(a, b); + const dist = distance(a, b) + // TODO: Subline code below was inserted in the wrong place - should be before this statement if (this.render_connections_border && this.ds.scale > 0.6) { - ctx.lineWidth = this.connections_width + 4; + ctx.lineWidth = this.connections_width + 4 } - ctx.lineJoin = "round"; - num_sublines = num_sublines || 1; + ctx.lineJoin = "round" + num_sublines ||= 1 if (num_sublines > 1) { - ctx.lineWidth = 0.5; + ctx.lineWidth = 0.5 } //begin line shape - const path = new Path2D(); + const path = new Path2D() if (link) { // Store the path on the link for hittests - link.path = path; + link.path = path } - for (var i = 0; i < num_sublines; i += 1) { - var offsety = (i - (num_sublines - 1) * 0.5) * 5; + for (let i = 0; i < num_sublines; i += 1) { + const offsety = (i - (num_sublines - 1) * 0.5) * 5 if (this.links_render_mode == LiteGraph.SPLINE_LINK) { - path.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; + path.moveTo(a[0], a[1] + offsety) + let start_offset_x = 0 + let start_offset_y = 0 + let end_offset_x = 0 + let end_offset_y = 0 switch (start_dir) { case LiteGraph.LEFT: - start_offset_x = dist * -0.25; - break; + start_offset_x = dist * -0.25 + break case LiteGraph.RIGHT: - start_offset_x = dist * 0.25; - break; + start_offset_x = dist * 0.25 + break case LiteGraph.UP: - start_offset_y = dist * -0.25; - break; + start_offset_y = dist * -0.25 + break case LiteGraph.DOWN: - start_offset_y = dist * 0.25; - break; + start_offset_y = dist * 0.25 + break } switch (end_dir) { case LiteGraph.LEFT: - end_offset_x = dist * -0.25; - break; + end_offset_x = dist * -0.25 + break case LiteGraph.RIGHT: - end_offset_x = dist * 0.25; - break; + end_offset_x = dist * 0.25 + break case LiteGraph.UP: - end_offset_y = dist * -0.25; - break; + end_offset_y = dist * -0.25 + break case LiteGraph.DOWN: - end_offset_y = dist * 0.25; - break; + end_offset_y = dist * 0.25 + break } path.bezierCurveTo( a[0] + start_offset_x, @@ -5410,74 +5134,74 @@ export class LGraphCanvas { b[1] + end_offset_y + offsety, b[0], b[1] + offsety - ); + ) } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { - path.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; + path.moveTo(a[0], a[1] + offsety) + let start_offset_x = 0 + let start_offset_y = 0 + let end_offset_x = 0 + let end_offset_y = 0 switch (start_dir) { case LiteGraph.LEFT: - start_offset_x = -1; - break; + start_offset_x = -1 + break case LiteGraph.RIGHT: - start_offset_x = 1; - break; + start_offset_x = 1 + break case LiteGraph.UP: - start_offset_y = -1; - break; + start_offset_y = -1 + break case LiteGraph.DOWN: - start_offset_y = 1; - break; + start_offset_y = 1 + break } switch (end_dir) { case LiteGraph.LEFT: - end_offset_x = -1; - break; + end_offset_x = -1 + break case LiteGraph.RIGHT: - end_offset_x = 1; - break; + end_offset_x = 1 + break case LiteGraph.UP: - end_offset_y = -1; - break; + end_offset_y = -1 + break case LiteGraph.DOWN: - end_offset_y = 1; - break; + end_offset_y = 1 + break } - var l = 15; + const l = 15 path.lineTo( a[0] + start_offset_x * l, a[1] + start_offset_y * l + offsety - ); + ) path.lineTo( b[0] + end_offset_x * l, b[1] + end_offset_y * l + offsety - ); - path.lineTo(b[0], b[1] + offsety); + ) + path.lineTo(b[0], b[1] + offsety) } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) { - path.moveTo(a[0], a[1]); - var start_x = a[0]; - var start_y = a[1]; - var end_x = b[0]; - var end_y = b[1]; + path.moveTo(a[0], a[1]) + let start_x = a[0] + let start_y = a[1] + let end_x = b[0] + let end_y = b[1] if (start_dir == LiteGraph.RIGHT) { - start_x += 10; + start_x += 10 } else { - start_y += 10; + start_y += 10 } if (end_dir == LiteGraph.LEFT) { - end_x -= 10; + end_x -= 10 } else { - end_y -= 10; + end_y -= 10 } - path.lineTo(start_x, start_y); - path.lineTo((start_x + end_x) * 0.5, start_y); - path.lineTo((start_x + end_x) * 0.5, end_y); - path.lineTo(end_x, end_y); - path.lineTo(b[0], b[1]); + path.lineTo(start_x, start_y) + path.lineTo((start_x + end_x) * 0.5, start_y) + path.lineTo((start_x + end_x) * 0.5, end_y) + path.lineTo(end_x, end_y) + path.lineTo(b[0], b[1]) } else { - return; + return } //unknown } @@ -5485,18 +5209,18 @@ export class LGraphCanvas { if (this.render_connections_border && this.ds.scale > 0.6 && !skip_border) { - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(path); + ctx.strokeStyle = "rgba(0,0,0,0.5)" + ctx.stroke(path) } - ctx.lineWidth = this.connections_width; - ctx.fillStyle = ctx.strokeStyle = color; - ctx.stroke(path); + ctx.lineWidth = this.connections_width + ctx.fillStyle = ctx.strokeStyle = color + ctx.stroke(path) //end line shape - var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir); - if (link && link._pos) { - link._pos[0] = pos[0]; - link._pos[1] = pos[1]; + const pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir) + if (link?._pos) { + link._pos[0] = pos[0] + link._pos[1] = pos[1] } //render arrow in the middle @@ -5506,87 +5230,87 @@ export class LGraphCanvas { //render arrow if (this.render_connection_arrows) { //compute two points in the connection - var posA = this.computeConnectionPoint( + const posA = this.computeConnectionPoint( a, b, 0.25, start_dir, end_dir - ); - var posB = this.computeConnectionPoint( + ) + const posB = this.computeConnectionPoint( a, b, 0.26, start_dir, end_dir - ); - var posC = this.computeConnectionPoint( + ) + const posC = this.computeConnectionPoint( a, b, 0.75, start_dir, end_dir - ); - var posD = this.computeConnectionPoint( + ) + const posD = this.computeConnectionPoint( a, b, 0.76, start_dir, end_dir - ); + ) //compute the angle between them so the arrow points in the right direction - var angleA = 0; - var angleB = 0; + let angleA = 0 + let angleB = 0 if (this.render_curved_connections) { - angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]); - angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]); + angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]) + angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]) } else { - angleB = angleA = b[1] > a[1] ? 0 : Math.PI; + angleB = angleA = b[1] > a[1] ? 0 : Math.PI } //render arrow - ctx.save(); - ctx.translate(posA[0], posA[1]); - ctx.rotate(angleA); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); - ctx.save(); - ctx.translate(posC[0], posC[1]); - ctx.rotate(angleB); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); + ctx.save() + ctx.translate(posA[0], posA[1]) + ctx.rotate(angleA) + ctx.beginPath() + ctx.moveTo(-5, -3) + ctx.lineTo(0, +7) + ctx.lineTo(+5, -3) + ctx.fill() + ctx.restore() + ctx.save() + ctx.translate(posC[0], posC[1]) + ctx.rotate(angleB) + ctx.beginPath() + ctx.moveTo(-5, -3) + ctx.lineTo(0, +7) + ctx.lineTo(+5, -3) + ctx.fill() + ctx.restore() } //circle - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2); - ctx.fill(); + ctx.beginPath() + ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2) + ctx.fill() } //render flowing points if (flow) { - ctx.fillStyle = color; - for (var i = 0; i < 5; ++i) { - var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1; - var pos = this.computeConnectionPoint( + ctx.fillStyle = color + for (let i = 0; i < 5; ++i) { + const f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1 + const flowPos = this.computeConnectionPoint( a, b, f, start_dir, end_dir - ); - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI); - ctx.fill(); + ) + ctx.beginPath() + ctx.arc(flowPos[0], flowPos[1], 5, 0, 2 * Math.PI) + ctx.fill() } } } @@ -5596,209 +5320,203 @@ export class LGraphCanvas { t: number, start_dir: number, end_dir: number): number[] { - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; + start_dir ||= LiteGraph.RIGHT + end_dir ||= LiteGraph.LEFT - var dist = distance(a, b); - var p0 = a; - var p1 = [a[0], a[1]]; - var p2 = [b[0], b[1]]; - var p3 = b; + const dist = distance(a, b) + const p0 = a + const p1 = [a[0], a[1]] + const p2 = [b[0], b[1]] + const p3 = b switch (start_dir) { case LiteGraph.LEFT: - p1[0] += dist * -0.25; - break; + p1[0] += dist * -0.25 + break case LiteGraph.RIGHT: - p1[0] += dist * 0.25; - break; + p1[0] += dist * 0.25 + break case LiteGraph.UP: - p1[1] += dist * -0.25; - break; + p1[1] += dist * -0.25 + break case LiteGraph.DOWN: - p1[1] += dist * 0.25; - break; + p1[1] += dist * 0.25 + break } switch (end_dir) { case LiteGraph.LEFT: - p2[0] += dist * -0.25; - break; + p2[0] += dist * -0.25 + break case LiteGraph.RIGHT: - p2[0] += dist * 0.25; - break; + p2[0] += dist * 0.25 + break case LiteGraph.UP: - p2[1] += dist * -0.25; - break; + p2[1] += dist * -0.25 + break case LiteGraph.DOWN: - p2[1] += dist * 0.25; - break; + p2[1] += dist * 0.25 + break } - var c1 = (1 - t) * (1 - t) * (1 - t); - var c2 = 3 * ((1 - t) * (1 - t)) * t; - var c3 = 3 * (1 - t) * (t * t); - var c4 = t * t * t; + const c1 = (1 - t) * (1 - t) * (1 - t) + const c2 = 3 * ((1 - t) * (1 - t)) * t + const c3 = 3 * (1 - t) * (t * t) + const c4 = t * t * t - var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0]; - var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1]; - return [x, y]; + const x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0] + const y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1] + return [x, y] } drawExecutionOrder(ctx: CanvasRenderingContext2D): void { - ctx.shadowColor = "transparent"; - ctx.globalAlpha = 0.25; + ctx.shadowColor = "transparent" + ctx.globalAlpha = 0.25 - ctx.textAlign = "center"; - ctx.strokeStyle = "white"; - ctx.globalAlpha = 0.75; + ctx.textAlign = "center" + ctx.strokeStyle = "white" + ctx.globalAlpha = 0.75 - var visible_nodes = this.visible_nodes; - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - ctx.fillStyle = "black"; + const visible_nodes = this.visible_nodes + for (let i = 0; i < visible_nodes.length; ++i) { + const node = visible_nodes[i] + ctx.fillStyle = "black" ctx.fillRect( node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT, node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT - ); + ) if (node.order == 0) { ctx.strokeRect( node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT - ); + ) } - ctx.fillStyle = "#FFF"; + ctx.fillStyle = "#FFF" ctx.fillText( + // @ts-expect-error type coercion node.order, node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5, node.pos[1] - 6 - ); + ) } - ctx.globalAlpha = 1; + ctx.globalAlpha = 1 } /** - * draws the widgets stored inside a node - * @method drawNodeWidgets - **/ + * draws the widgets stored inside a node + **/ drawNodeWidgets(node: LGraphNode, posY: number, ctx: CanvasRenderingContext2D, active_widget: IWidget) { - if (!node.widgets || !node.widgets.length) { - return 0; - } - var width = node.size[0]; - var widgets = node.widgets; - posY += 2; - var H = LiteGraph.NODE_WIDGET_HEIGHT; - var show_text = this.ds.scale > 0.5; - ctx.save(); - ctx.globalAlpha = this.editor_alpha; - var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR; - var background_color = LiteGraph.WIDGET_BGCOLOR; - var text_color = LiteGraph.WIDGET_TEXT_COLOR; - var secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - var margin = 15; - - for (var i = 0; i < widgets.length; ++i) { - var w = widgets[i]; - var y = posY; - if (w.y) { - y = w.y; - } + if (!node.widgets || !node.widgets.length) return 0 + const width = node.size[0] + const widgets = node.widgets + posY += 2 + const H = LiteGraph.NODE_WIDGET_HEIGHT + const show_text = this.ds.scale > 0.5 + ctx.save() + ctx.globalAlpha = this.editor_alpha + const outline_color = LiteGraph.WIDGET_OUTLINE_COLOR + const background_color = LiteGraph.WIDGET_BGCOLOR + const text_color = LiteGraph.WIDGET_TEXT_COLOR + const secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR + const margin = 15 + + for (let i = 0; i < widgets.length; ++i) { + const w = widgets[i] + const y = w.y || posY if (w === this.link_over_widget) { ctx.fillStyle = this.default_connection_color_byType[this.link_over_widget_type] || - this.default_connection_color.input_on; - + this.default_connection_color.input_on + // Manually draw a slot next to the widget simulating an input drawSlot(ctx, {}, [10, y + 10], {}) } - w.last_y = y; - ctx.strokeStyle = outline_color; - ctx.fillStyle = "#222"; - ctx.textAlign = "left"; + w.last_y = y + ctx.strokeStyle = outline_color + ctx.fillStyle = "#222" + ctx.textAlign = "left" //ctx.lineWidth = 2; if (w.disabled) - ctx.globalAlpha *= 0.5; - var widget_width = w.width || width; + ctx.globalAlpha *= 0.5 + const widget_width = w.width || width switch (w.type) { case "button": - ctx.fillStyle = background_color; + ctx.fillStyle = background_color if (w.clicked) { - ctx.fillStyle = "#AAA"; - w.clicked = false; - this.dirty_canvas = true; + ctx.fillStyle = "#AAA" + w.clicked = false + this.dirty_canvas = true } - ctx.fillRect(margin, y, widget_width - margin * 2, H); + ctx.fillRect(margin, y, widget_width - margin * 2, H) if (show_text && !w.disabled) - ctx.strokeRect(margin, y, widget_width - margin * 2, H); + ctx.strokeRect(margin, y, widget_width - margin * 2, H) if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7); + ctx.textAlign = "center" + ctx.fillStyle = text_color + ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7) } - break; + break case "toggle": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); + ctx.textAlign = "left" + ctx.strokeStyle = outline_color + ctx.fillStyle = background_color + ctx.beginPath() if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); - + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]) else - ctx.rect(margin, y, widget_width - margin * 2, H); - ctx.fill(); + ctx.rect(margin, y, widget_width - margin * 2, H) + ctx.fill() if (show_text && !w.disabled) - ctx.stroke(); - ctx.fillStyle = w.value ? "#89A" : "#333"; - ctx.beginPath(); - ctx.arc(widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2); - ctx.fill(); + ctx.stroke() + ctx.fillStyle = w.value ? "#89A" : "#333" + ctx.beginPath() + ctx.arc(widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2) + ctx.fill() if (show_text) { - ctx.fillStyle = secondary_text_color; - const label = w.label || w.name; + ctx.fillStyle = secondary_text_color + const label = w.label || w.name if (label != null) { - ctx.fillText(label, margin * 2, y + H * 0.7); + ctx.fillText(label, margin * 2, y + H * 0.7) } - ctx.fillStyle = w.value ? text_color : secondary_text_color; - ctx.textAlign = "right"; + ctx.fillStyle = w.value ? text_color : secondary_text_color + ctx.textAlign = "right" ctx.fillText( w.value ? w.options.on || "true" : w.options.off || "false", widget_width - 40, y + H * 0.7 - ); + ) } - break; - case "slider": - ctx.fillStyle = background_color; - ctx.fillRect(margin, y, widget_width - margin * 2, H); - var range = w.options.max - w.options.min; - var nvalue = (w.value - w.options.min) / range; - if (nvalue < 0.0) nvalue = 0.0; - if (nvalue > 1.0) nvalue = 1.0; - ctx.fillStyle = w.options.hasOwnProperty("slider_color") ? w.options.slider_color : (active_widget == w ? "#89A" : "#678"); - ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H); + break + case "slider": { + ctx.fillStyle = background_color + ctx.fillRect(margin, y, widget_width - margin * 2, H) + const range = w.options.max - w.options.min + let nvalue = (w.value - w.options.min) / range + if (nvalue < 0.0) nvalue = 0.0 + if (nvalue > 1.0) nvalue = 1.0 + ctx.fillStyle = w.options.hasOwnProperty("slider_color") ? w.options.slider_color : (active_widget == w ? "#89A" : "#678") + ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H) if (show_text && !w.disabled) - ctx.strokeRect(margin, y, widget_width - margin * 2, H); + ctx.strokeRect(margin, y, widget_width - margin * 2, H) if (w.marker) { - var marker_nvalue = (w.marker - w.options.min) / range; - if (marker_nvalue < 0.0) marker_nvalue = 0.0; - if (marker_nvalue > 1.0) marker_nvalue = 1.0; - ctx.fillStyle = w.options.hasOwnProperty("marker_color") ? w.options.marker_color : "#AA9"; - ctx.fillRect(margin + marker_nvalue * (widget_width - margin * 2), y, 2, H); + let marker_nvalue = (w.marker - w.options.min) / range + if (marker_nvalue < 0.0) marker_nvalue = 0.0 + if (marker_nvalue > 1.0) marker_nvalue = 1.0 + ctx.fillStyle = w.options.hasOwnProperty("marker_color") ? w.options.marker_color : "#AA9" + ctx.fillRect(margin + marker_nvalue * (widget_width - margin * 2), y, 2, H) } if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; + ctx.textAlign = "center" + ctx.fillStyle = text_color ctx.fillText( w.label || w.name + " " + Number(w.value).toFixed( w.options.precision != null @@ -5807,42 +5525,42 @@ export class LGraphCanvas { ), widget_width * 0.5, y + H * 0.7 - ); + ) } - break; + break + } case "number": case "combo": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); + ctx.textAlign = "left" + ctx.strokeStyle = outline_color + ctx.fillStyle = background_color + ctx.beginPath() if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); - + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]) else - ctx.rect(margin, y, widget_width - margin * 2, H); - ctx.fill(); + ctx.rect(margin, y, widget_width - margin * 2, H) + ctx.fill() if (show_text) { if (!w.disabled) - ctx.stroke(); - ctx.fillStyle = text_color; + ctx.stroke() + ctx.fillStyle = text_color if (!w.disabled) { - ctx.beginPath(); - ctx.moveTo(margin + 16, y + 5); - ctx.lineTo(margin + 6, y + H * 0.5); - ctx.lineTo(margin + 16, y + H - 5); - ctx.fill(); - ctx.beginPath(); - ctx.moveTo(widget_width - margin - 16, y + 5); - ctx.lineTo(widget_width - margin - 6, y + H * 0.5); - ctx.lineTo(widget_width - margin - 16, y + H - 5); - ctx.fill(); + ctx.beginPath() + ctx.moveTo(margin + 16, y + 5) + ctx.lineTo(margin + 6, y + H * 0.5) + ctx.lineTo(margin + 16, y + H - 5) + ctx.fill() + ctx.beginPath() + ctx.moveTo(widget_width - margin - 16, y + 5) + ctx.lineTo(widget_width - margin - 6, y + H * 0.5) + ctx.lineTo(widget_width - margin - 16, y + H - 5) + ctx.fill() } - ctx.fillStyle = secondary_text_color; - ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7); - ctx.fillStyle = text_color; - ctx.textAlign = "right"; + ctx.fillStyle = secondary_text_color + ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7) + ctx.fillStyle = text_color + ctx.textAlign = "right" if (w.type == "number") { ctx.fillText( Number(w.value).toFixed( @@ -5852,463 +5570,437 @@ export class LGraphCanvas { ), widget_width - margin * 2 - 20, y + H * 0.7 - ); + ) } else { - var v = w.value; + let v = typeof w.value === "number" ? String(w.value) : w.value if (w.options.values) { - var values = w.options.values; + let values = w.options.values if (values.constructor === Function) - values = values(); + // @ts-expect-error + values = values() if (values && values.constructor !== Array) - v = values[w.value]; + v = values[w.value] } - const labelWidth = ctx.measureText(w.label || w.name).width + margin * 2; - const inputWidth = widget_width - margin * 4; - const availableWidth = inputWidth - labelWidth; - const textWidth = ctx.measureText(v).width; + const labelWidth = ctx.measureText(w.label || w.name).width + margin * 2 + const inputWidth = widget_width - margin * 4 + const availableWidth = inputWidth - labelWidth + const textWidth = ctx.measureText(v).width if (textWidth > availableWidth) { - const ELLIPSIS = "\u2026"; - const ellipsisWidth = ctx.measureText(ELLIPSIS).width; - const charWidthAvg = ctx.measureText("a").width; + const ELLIPSIS = "\u2026" + const ellipsisWidth = ctx.measureText(ELLIPSIS).width + const charWidthAvg = ctx.measureText("a").width if (availableWidth <= ellipsisWidth) { - v = "\u2024"; // One dot leader + v = "\u2024" // One dot leader } else { - v = `${v}`; - const overflowWidth = (textWidth + ellipsisWidth) - availableWidth; + v = `${v}` + const overflowWidth = (textWidth + ellipsisWidth) - availableWidth // Only first 3 characters need to be measured precisely if (overflowWidth + charWidthAvg * 3 > availableWidth) { - const preciseRange = availableWidth + charWidthAvg * 3; - const preTruncateCt = Math.floor((preciseRange - ellipsisWidth) / charWidthAvg); - v = v.substr(0, preTruncateCt); + const preciseRange = availableWidth + charWidthAvg * 3 + const preTruncateCt = Math.floor((preciseRange - ellipsisWidth) / charWidthAvg) + v = v.substr(0, preTruncateCt) } while (ctx.measureText(v).width + ellipsisWidth > availableWidth) { - v = v.substr(0, v.length - 1); + v = v.substr(0, v.length - 1) } - v += ELLIPSIS; + v += ELLIPSIS } } ctx.fillText( v, widget_width - margin * 2 - 20, y + H * 0.7 - ); + ) } } - break; + break case "string": case "text": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); - + ctx.textAlign = "left" + ctx.strokeStyle = outline_color + ctx.fillStyle = background_color + ctx.beginPath() + if (show_text) + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]) else - ctx.rect(margin, y, widget_width - margin * 2, H); - ctx.fill(); + ctx.rect(margin, y, widget_width - margin * 2, H) + ctx.fill() if (show_text) { if (!w.disabled) - ctx.stroke(); - ctx.save(); - ctx.beginPath(); - ctx.rect(margin, y, widget_width - margin * 2, H); - ctx.clip(); + ctx.stroke() + ctx.save() + ctx.beginPath() + ctx.rect(margin, y, widget_width - margin * 2, H) + ctx.clip() //ctx.stroke(); - ctx.fillStyle = secondary_text_color; - const label = w.label || w.name; - if (label != null) { - ctx.fillText(label, margin * 2, y + H * 0.7); - } - ctx.fillStyle = text_color; - ctx.textAlign = "right"; - ctx.fillText(String(w.value).substr(0, 30), widget_width - margin * 2, y + H * 0.7); //30 chars max - ctx.restore(); + ctx.fillStyle = secondary_text_color + const label = w.label || w.name + if (label != null) + ctx.fillText(label, margin * 2, y + H * 0.7) + ctx.fillStyle = text_color + ctx.textAlign = "right" + ctx.fillText(String(w.value).substr(0, 30), widget_width - margin * 2, y + H * 0.7) //30 chars max + ctx.restore() } - break; + break + // Custom widgets default: - if (w.draw) { - w.draw(ctx, node, widget_width, y, H); - } - break; + w.draw?.(ctx, node, widget_width, y, H) + break } - posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4; - ctx.globalAlpha = this.editor_alpha; + posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4 + ctx.globalAlpha = this.editor_alpha } - ctx.restore(); - ctx.textAlign = "left"; + ctx.restore() + ctx.textAlign = "left" } /** - * process an event on widgets - * @method processNodeWidgets - **/ + * process an event on widgets + **/ processNodeWidgets(node: LGraphNode, // TODO: Hitting enter does not trigger onWidgetChanged - may require a separate value processor for processKey pos: Point, event: CanvasMouseEvent, active_widget?: IWidget): IWidget { if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { - return null; + return null } - var x = pos[0] - node.pos[0]; - var y = pos[1] - node.pos[1]; - var width = node.size[0]; - var that = this; - var ref_window = this.getCanvasWindow(); + const x = pos[0] - node.pos[0] + const y = pos[1] - node.pos[1] + const width = node.size[0] + const that = this + const ref_window = this.getCanvasWindow() - for (var i = 0; i < node.widgets.length; ++i) { - var w = node.widgets[i]; + let values + let values_list + for (let i = 0; i < node.widgets.length; ++i) { + const w = node.widgets[i] if (!w || w.disabled) - continue; - var widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT; - var widget_width = w.width || width; + continue + const widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT + const widget_width = w.width || width //outside if (w != active_widget && (x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined)) - continue; + continue - var old_value = w.value; + const old_value = w.value //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) { //inside widget switch (w.type) { - case "button": + case "button": { + // FIXME: This one-function-to-rule-them-all pattern is nuts. Split events into manageable chunks. if (event.type === LiteGraph.pointerevents_method + "down") { if (w.callback) { setTimeout(function () { - w.callback(w, that, node, pos, event); - }, 20); + w.callback(w, that, node, pos, event) + }, 20) } - w.clicked = true; - this.dirty_canvas = true; + w.clicked = true + this.dirty_canvas = true } - break; - case "slider": - var old_value = w.value; - var nvalue = clamp((x - 15) / (widget_width - 30), 0, 1); - if (w.options.read_only) break; - w.value = w.options.min + (w.options.max - w.options.min) * nvalue; + break + } + case "slider": { + const nvalue = clamp((x - 15) / (widget_width - 30), 0, 1) + if (w.options.read_only) break + w.value = w.options.min + (w.options.max - w.options.min) * nvalue if (old_value != w.value) { setTimeout(function () { - inner_value_change(w, w.value); - }, 20); + inner_value_change(w, w.value) + }, 20) } - this.dirty_canvas = true; - break; + this.dirty_canvas = true + break + } case "number": - case "combo": - var old_value = w.value; - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; - var allow_scroll = true; - if (delta) { - if (x > -3 && x < widget_width + 3) { - allow_scroll = false; - } - } + case "combo": { + let delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0 + const allow_scroll = delta && (x > -3 && x < widget_width + 3) + ? false + : true + // TODO: Type checks on widget values if (allow_scroll && event.type == LiteGraph.pointerevents_method + "move" && w.type == "number") { if (event.deltaX) - w.value += event.deltaX * 0.1 * (w.options.step || 1); + w.value += event.deltaX * 0.1 * (w.options.step || 1) if (w.options.min != null && w.value < w.options.min) { - w.value = w.options.min; + w.value = w.options.min } if (w.options.max != null && w.value > w.options.max) { - w.value = w.options.max; + w.value = w.options.max } } else if (event.type == LiteGraph.pointerevents_method + "down") { - var values = w.options.values; + values = w.options.values if (values && values.constructor === Function) { - values = w.options.values(w, node); + // @ts-expect-error + values = w.options.values(w, node) } - var values_list = null; + values_list = null if (w.type != "number") - values_list = values.constructor === Array ? values : Object.keys(values); + values_list = values.constructor === Array ? values : Object.keys(values) - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; + delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0 if (w.type == "number") { - w.value += delta * 0.1 * (w.options.step || 1); + w.value += delta * 0.1 * (w.options.step || 1) if (w.options.min != null && w.value < w.options.min) { - w.value = w.options.min; + w.value = w.options.min } if (w.options.max != null && w.value > w.options.max) { - w.value = w.options.max; + w.value = w.options.max } } else if (delta) { //clicked in arrow, used for combos - var index = -1; - this.last_mouseclick = 0; //avoids dobl click event - if (values.constructor === Object) - index = values_list.indexOf(String(w.value)) + delta; - - - else - index = values_list.indexOf(w.value) + delta; - if (index >= values_list.length) { - index = values_list.length - 1; - } - if (index < 0) { - index = 0; - } - if (values.constructor === Array) - w.value = values[index]; - - - else - w.value = index; + let index = -1 + this.last_mouseclick = 0 //avoids dobl click event + index = values.constructor === Object + ? values_list.indexOf(String(w.value)) + delta + : values_list.indexOf(w.value) + delta + + if (index >= values_list.length) index = values_list.length - 1 + if (index < 0) index = 0 + + w.value = values.constructor === Array + ? values[index] + : index } else { //combo clicked - var text_values = values != values_list ? Object.values(values) : values; - var menu = new LiteGraph.ContextMenu(text_values, { + const text_values = values != values_list ? Object.values(values) : values + new LiteGraph.ContextMenu(text_values, { scale: Math.max(1, this.ds.scale), event: event, className: "dark", callback: inner_clicked.bind(w) }, - ref_window); - function inner_clicked(v, option, event) { + // @ts-expect-error Not impl - harmless + ref_window) + function inner_clicked(v) { if (values != values_list) - v = text_values.indexOf(v); - this.value = v; - inner_value_change(this, v); - that.dirty_canvas = true; - return false; + v = text_values.indexOf(v) + this.value = v + inner_value_change(this, v) + that.dirty_canvas = true + return false } } } //end mousedown else if (event.type == LiteGraph.pointerevents_method + "up" && w.type == "number") { - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; + delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0 if (event.click_time < 200 && delta == 0) { this.prompt("Value", w.value, function (v) { // check if v is a valid equation or a number if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) { try { //solve the equation if possible - v = eval(v); - } catch (e) { } + v = eval(v) + } catch { } } - this.value = Number(v); - inner_value_change(this, this.value); + this.value = Number(v) + inner_value_change(this, this.value) }.bind(w), - event); + event) } } if (old_value != w.value) setTimeout( function () { - inner_value_change(this, this.value); + inner_value_change(this, this.value) }.bind(w), 20 - ); - this.dirty_canvas = true; - break; + ) + this.dirty_canvas = true + break + } case "toggle": if (event.type == LiteGraph.pointerevents_method + "down") { - w.value = !w.value; + w.value = !w.value setTimeout(function () { - inner_value_change(w, w.value); - }, 20); + inner_value_change(w, w.value) + }, 20) } - break; + break case "string": case "text": if (event.type == LiteGraph.pointerevents_method + "down") { - this.prompt("Value", w.value, function (v) { - inner_value_change(this, v); + this.prompt("Value", w.value, function (v: any) { + inner_value_change(this, v) }.bind(w), - event, w.options ? w.options.multiline : false); + event, w.options ? w.options.multiline : false) } - break; + break default: - if (w.mouse) { - this.dirty_canvas = w.mouse(event, [x, y], node); - } - break; + if (w.mouse) this.dirty_canvas = w.mouse(event, [x, y], node) + break } //end switch - - //value changed if (old_value != w.value) { - if (node.onWidgetChanged) - node.onWidgetChanged(w.name, w.value, old_value, w); - node.graph._version++; + node.onWidgetChanged?.(w.name, w.value, old_value, w) + node.graph._version++ } - return w; + return w } //end for - function inner_value_change(widget, value) { - if (widget.type == "number") { - value = Number(value); - } - widget.value = value; - if (widget.options && widget.options.property && node.properties[widget.options.property] !== undefined) { - node.setProperty(widget.options.property, value); - } - if (widget.callback) { - widget.callback(widget.value, that, node, pos, event); + function inner_value_change(widget: IWidget, value: TWidgetValue) { + const v = widget.type === "number" ? Number(value) : value + widget.value = v + if (widget.options?.property && node.properties[widget.options.property] !== undefined) { + node.setProperty(widget.options.property, v) } + widget.callback?.(widget.value, that, node, pos, event) } - return null; + return null } + /** * draws every group area in the background - * @method drawGroups **/ drawGroups(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): void { - if (!this.graph) { - return; - } + if (!this.graph) return - var groups = this.graph._groups; + const groups = this.graph._groups - ctx.save(); - ctx.globalAlpha = 0.5 * this.editor_alpha; + ctx.save() + ctx.globalAlpha = 0.5 * this.editor_alpha - for (var i = 0; i < groups.length; ++i) { - var group = groups[i]; + for (let i = 0; i < groups.length; ++i) { + const group = groups[i] if (!overlapBounding(this.visible_area, group._bounding)) { - continue; + continue } //out of the visible area - group.draw(this, ctx); + group.draw(this, ctx) } - ctx.restore(); + ctx.restore() } adjustNodesSize(): void { - var nodes = this.graph._nodes; - for (var i = 0; i < nodes.length; ++i) { - nodes[i].size = nodes[i].computeSize(); + const nodes = this.graph._nodes + for (let i = 0; i < nodes.length; ++i) { + nodes[i].size = nodes[i].computeSize() } - this.setDirty(true, true); + this.setDirty(true, true) } /** - * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode - * @method resize - **/ + * 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 { if (!width && !height) { - var parent = this.canvas.parentNode; - width = parent.offsetWidth; - height = parent.offsetHeight; - } - - if (this.canvas.width == width && this.canvas.height == height) { - return; - } - - this.canvas.width = width; - this.canvas.height = height; - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - this.setDirty(true, true); + // TODO: Type-check parentNode + const parent = this.canvas.parentNode + // @ts-expect-error + width = parent.offsetWidth + // @ts-expect-error + height = parent.offsetHeight + } + + if (this.canvas.width == width && this.canvas.height == height) + return + + this.canvas.width = width + this.canvas.height = height + this.bgcanvas.width = this.canvas.width + this.bgcanvas.height = this.canvas.height + this.setDirty(true, true) } /** - * switches to live mode (node shapes are not rendered, only the content) - * this feature was designed when graphs where meant to create user interfaces - * @method switchLiveMode - **/ + * 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 { if (!transition) { - this.live_mode = !this.live_mode; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - return; + this.live_mode = !this.live_mode + this.dirty_canvas = true + this.dirty_bgcanvas = true + return } - var self = this; - var delta = this.live_mode ? 1.1 : 0.9; + const self = this + const delta = this.live_mode ? 1.1 : 0.9 if (this.live_mode) { - this.live_mode = false; - this.editor_alpha = 0.1; + this.live_mode = false + this.editor_alpha = 0.1 } - var t = setInterval(function () { - self.editor_alpha *= delta; - self.dirty_canvas = true; - self.dirty_bgcanvas = true; + const t = setInterval(function () { + self.editor_alpha *= delta + self.dirty_canvas = true + self.dirty_bgcanvas = true if (delta < 1 && self.editor_alpha < 0.01) { - clearInterval(t); - if (delta < 1) { - self.live_mode = true; - } + clearInterval(t) + if (delta < 1) self.live_mode = true } if (delta > 1 && self.editor_alpha > 0.99) { - clearInterval(t); - self.editor_alpha = 1; + clearInterval(t) + self.editor_alpha = 1 } - }, 1); + }, 1) } - // TODO: Not called. Confirm no consumers, then remove. + onNodeSelectionChange(): void { } + /** - * Determines the furthest nodes in each direction for the currently selected nodes - * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} - */ + * Determines the furthest nodes in each direction for the currently selected nodes + * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} + */ boundaryNodesForSelection(): IBoundaryNodes { - return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)); + return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)) } showLinkMenu(link: LLink, e: CanvasMouseEvent): boolean { - var that = this; - // console.log(link); - var node_left = that.graph.getNodeById(link.origin_id); - var node_right = that.graph.getNodeById(link.target_id); - var fromType = false; - if (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type; - var destType = false; - if (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type; - - var options = ["Add Node", null, "Delete", null]; - - - var menu = new LiteGraph.ContextMenu(options, { + const graph = this.graph + const node_left = graph.getNodeById(link.origin_id) + const node_right = graph.getNodeById(link.target_id) + // TODO: Replace ternary with ?? "" + const fromType = node_left?.outputs?.[link.origin_slot] + ? node_left.outputs[link.origin_slot].type + : false + const destType = node_right?.outputs?.[link.target_slot] + ? node_right.inputs[link.target_slot].type + : false + + const options = ["Add Node", null, "Delete", null] + + const menu = new LiteGraph.ContextMenu(options, { event: e, title: link.data != null ? link.data.constructor.name : null, callback: inner_clicked - }); + }) - function inner_clicked(v, options, e) { + function inner_clicked(v: string, options: unknown, e: MouseEvent) { switch (v) { case "Add Node": LGraphCanvas.onMenuAdd(null, null, e, menu, function (node) { - // console.debug("node autoconnect"); - if (!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length) { - return; - } + if (!node.inputs?.length || !node.outputs?.length) return + // leave the connection type checking inside connectByType + // @ts-expect-error Assigning from check to false results in the type being treated as "*". This should fail. if (node_left.connectByType(link.origin_slot, node, fromType)) { - node.connectByType(link.target_slot, node_right, destType); - node.pos[0] -= node.size[0] * 0.5; + // @ts-expect-error Assigning from check to false results in the type being treated as "*". This should fail. + node.connectByType(link.target_slot, node_right, destType) + node.pos[0] -= node.size[0] * 0.5 } - }); - break; + }) + break case "Delete": - that.graph.removeLink(link.id); - break; + graph.removeLink(link.id) + break default: - /*var nodeCreated = createDefaultNodeForSlot({ nodeFrom: node_left - ,slotFrom: link.origin_slot - ,nodeTo: node - ,slotTo: link.target_slot - ,e: e - ,nodeType: "AUTO" - }); - if(nodeCreated) console.log("new node in beetween "+v+" created");*/ } } - return false; + return false } createDefaultNodeForSlot(optPass: ICreateNodeOptions): boolean { - var optPass = optPass || {}; - var opts = Object.assign({ + optPass = optPass || {} + const opts = Object.assign({ nodeFrom: null // input , @@ -6333,131 +6025,123 @@ export class LGraphCanvas { posSizeFix: [0, 0] // alpha, adjust the position x,y based on the new node size w,h }, optPass - ); - var that = this; + ) + const that = this - var isFrom = opts.nodeFrom && opts.slotFrom !== null; - var isTo = !isFrom && opts.nodeTo && opts.slotTo !== null; + const isFrom = opts.nodeFrom && opts.slotFrom !== null + const isTo = !isFrom && opts.nodeTo && opts.slotTo !== null if (!isFrom && !isTo) { - console.warn("No data passed to createDefaultNodeForSlot " + opts.nodeFrom + " " + opts.slotFrom + " " + opts.nodeTo + " " + opts.slotTo); - return false; + console.warn("No data passed to createDefaultNodeForSlot " + opts.nodeFrom + " " + opts.slotFrom + " " + opts.nodeTo + " " + opts.slotTo) + return false } if (!opts.nodeType) { - console.warn("No type to createDefaultNodeForSlot"); - return false; + console.warn("No type to createDefaultNodeForSlot") + return false } - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; + const nodeX = isFrom ? opts.nodeFrom : opts.nodeTo + let slotX = isFrom ? opts.slotFrom : opts.slotTo - var iSlotConn = false; + let iSlotConn: number | false = false switch (typeof slotX) { case "string": - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX, false) : nodeX.findInputSlot(slotX, false); - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX, false) : nodeX.findInputSlot(slotX, false) + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX] + break case "object": // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name) + break case "number": - iSlotConn = slotX; - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; + iSlotConn = slotX + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX] + break case "undefined": default: // bad ? //iSlotConn = 0; - console.warn("Cant get slot information " + slotX); - return false; + console.warn("Cant get slot information " + slotX) + return false } if (slotX === false || iSlotConn === false) { - console.warn("createDefaultNodeForSlot bad slotX " + slotX + " " + iSlotConn); + console.warn("createDefaultNodeForSlot bad slotX " + slotX + " " + iSlotConn) } // check for defaults nodes for this slottype - var fromSlotType = slotX.type == LiteGraph.EVENT ? "_event_" : slotX.type; - var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; - if (slotTypesDefault && slotTypesDefault[fromSlotType]) { - if (slotX.link !== null) { - // is connected - } else { - // is not not connected - } - let nodeNewType = false; - if (typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array") { - for (var typeX in slotTypesDefault[fromSlotType]) { + const fromSlotType = slotX.type == LiteGraph.EVENT ? "_event_" : slotX.type + const slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in + if (slotTypesDefault?.[fromSlotType]) { + // TODO: Remove "any" kludge + let nodeNewType: any = false + if (typeof slotTypesDefault[fromSlotType] == "object") { + for (const typeX in slotTypesDefault[fromSlotType]) { if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO") { - nodeNewType = slotTypesDefault[fromSlotType][typeX]; + nodeNewType = slotTypesDefault[fromSlotType][typeX] // console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType); - break; // -------- + break // -------- } } } else { - if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType]; + if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType] } if (nodeNewType) { - var nodeNewOpts = false; + // TODO: Remove "any" kludge + let nodeNewOpts: any = false if (typeof nodeNewType == "object" && nodeNewType.node) { - nodeNewOpts = nodeNewType; - nodeNewType = nodeNewType.node; + nodeNewOpts = nodeNewType + nodeNewType = nodeNewType.node } //that.graph.beforeChange(); - var newNode = LiteGraph.createNode(nodeNewType); + const newNode = LiteGraph.createNode(nodeNewType) if (newNode) { // if is object pass options if (nodeNewOpts) { if (nodeNewOpts.properties) { - for (var i in nodeNewOpts.properties) { - newNode.addProperty(i, nodeNewOpts.properties[i]); + for (const i in nodeNewOpts.properties) { + newNode.addProperty(i, nodeNewOpts.properties[i]) } } if (nodeNewOpts.inputs) { - newNode.inputs = []; - for (var i in nodeNewOpts.inputs) { + newNode.inputs = [] + for (const i in nodeNewOpts.inputs) { newNode.addOutput( nodeNewOpts.inputs[i][0], nodeNewOpts.inputs[i][1] - ); + ) } } if (nodeNewOpts.outputs) { - newNode.outputs = []; - for (var i in nodeNewOpts.outputs) { + newNode.outputs = [] + for (const i in nodeNewOpts.outputs) { newNode.addOutput( nodeNewOpts.outputs[i][0], nodeNewOpts.outputs[i][1] - ); + ) } } if (nodeNewOpts.title) { - newNode.title = nodeNewOpts.title; + newNode.title = nodeNewOpts.title } if (nodeNewOpts.json) { - newNode.configure(nodeNewOpts.json); + newNode.configure(nodeNewOpts.json) } } // add the node - that.graph.add(newNode); + that.graph.add(newNode) newNode.pos = [opts.position[0] + opts.posAdd[0] + (opts.posSizeFix[0] ? opts.posSizeFix[0] * newNode.size[0] : 0), - opts.position[1] + opts.posAdd[1] + (opts.posSizeFix[1] ? opts.posSizeFix[1] * newNode.size[1] : 0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/ - - - - - + opts.position[1] + opts.posAdd[1] + (opts.posSizeFix[1] ? opts.posSizeFix[1] * newNode.size[1] : 0)] //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/ //that.graph.afterChange(); // connect the two! if (isFrom) { - opts.nodeFrom.connectByType(iSlotConn, newNode, fromSlotType); + opts.nodeFrom.connectByType(iSlotConn, newNode, fromSlotType) } else { - opts.nodeTo.connectByTypeOutput(iSlotConn, newNode, fromSlotType); + opts.nodeTo.connectByTypeOutput(iSlotConn, newNode, fromSlotType) } // if connecting in between @@ -6465,18 +6149,17 @@ export class LGraphCanvas { // TODO } - return true; + return true - } else { - console.log("failed creating " + nodeNewType); } + console.log("failed creating " + nodeNewType) } } - return false; + return false } - showConnectionMenu(optPass: ICreateNodeOptions & { e: MouseEvent }): void { - var optPass = optPass || {}; - var opts = Object.assign({ + showConnectionMenu(optPass: Partial): void { + optPass ||= {} + const opts = Object.assign({ nodeFrom: null // input , @@ -6494,67 +6177,68 @@ export class LGraphCanvas { showSearchBox: this.showSearchBox, }, optPass - ); - var that = this; + ) + const that = this - var isFrom = opts.nodeFrom && opts.slotFrom; - var isTo = !isFrom && opts.nodeTo && opts.slotTo; + const isFrom = opts.nodeFrom && opts.slotFrom + const isTo = !isFrom && opts.nodeTo && opts.slotTo if (!isFrom && !isTo) { - console.warn("No data passed to showConnectionMenu"); - return; + console.warn("No data passed to showConnectionMenu") + return } - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; + const nodeX = isFrom ? opts.nodeFrom : opts.nodeTo + let slotX = isFrom ? opts.slotFrom : opts.slotTo - var iSlotConn = false; + // TODO: Remove "any" kludge + let iSlotConn: any = false switch (typeof slotX) { case "string": - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX, false) : nodeX.findInputSlot(slotX, false); - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX, false) : nodeX.findInputSlot(slotX, false) + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX] + break case "object": // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name) + break case "number": - iSlotConn = slotX; - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; + iSlotConn = slotX + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX] + break default: // bad ? //iSlotConn = 0; - console.warn("Cant get slot information " + slotX); - return; + console.warn("Cant get slot information " + slotX) + return } - var options = ["Add Node", null]; + const options = ["Add Node", null] if (opts.allow_searchbox) { - options.push("Search"); - options.push(null); + options.push("Search") + options.push(null) } // get defaults nodes for this slottype - var fromSlotType = slotX.type == LiteGraph.EVENT ? "_event_" : slotX.type; - var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; - if (slotTypesDefault && slotTypesDefault[fromSlotType]) { - if (typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array") { - for (var typeX in slotTypesDefault[fromSlotType]) { - options.push(slotTypesDefault[fromSlotType][typeX]); + const fromSlotType = slotX.type == LiteGraph.EVENT ? "_event_" : slotX.type + const slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in + if (slotTypesDefault?.[fromSlotType]) { + if (typeof slotTypesDefault[fromSlotType] == "object") { + for (const typeX in slotTypesDefault[fromSlotType]) { + options.push(slotTypesDefault[fromSlotType][typeX]) } } else { - options.push(slotTypesDefault[fromSlotType]); + options.push(slotTypesDefault[fromSlotType]) } } // build menu - var menu = new LiteGraph.ContextMenu(options, { + const menu = new LiteGraph.ContextMenu(options, { event: opts.e, title: (slotX && slotX.name != "" ? (slotX.name + (fromSlotType ? " | " : "")) : "") + (slotX && fromSlotType ? fromSlotType : ""), callback: inner_clicked - }); + }) // callback function inner_clicked(v, options, e) { @@ -6563,389 +6247,369 @@ export class LGraphCanvas { case "Add Node": LGraphCanvas.onMenuAdd(null, null, e, menu, function (node) { if (isFrom) { - opts.nodeFrom.connectByType(iSlotConn, node, fromSlotType); + opts.nodeFrom.connectByType(iSlotConn, node, fromSlotType) } else { - opts.nodeTo.connectByTypeOutput(iSlotConn, node, fromSlotType); + opts.nodeTo.connectByTypeOutput(iSlotConn, node, fromSlotType) } - }); - break; + }) + break case "Search": if (isFrom) { - opts.showSearchBox(e, { node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType }); + opts.showSearchBox(e, { node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType }) } else { - opts.showSearchBox(e, { node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType }); + opts.showSearchBox(e, { node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType }) } - break; - default: + break + default: { // check for defaults nodes for this slottype - var nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts, { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts, { position: [opts.e.canvasX, opts.e.canvasY], nodeType: v - })); - if (nodeCreated) { - // new node created - //console.log("node "+v+" created") - } else { - // failed or v is not in defaults - } - break; + })) + break + } } } } // refactor: there are different dialogs, some uses createDialog some dont prompt(title: string, value: any, callback: (arg0: any) => void, event: CanvasMouseEvent, multiline?: boolean): HTMLDivElement { - var that = this; - var input_html = ""; - title = title || ""; - - var dialog = document.createElement("div"); - dialog.is_modified = false; - dialog.className = "graphdialog rounded"; - if (multiline) - dialog.innerHTML = " "; - - - else - dialog.innerHTML = " "; + const that = this + title = title || "" + + const dialog: IDialog = document.createElement("div") + dialog.is_modified = false + dialog.className = "graphdialog rounded" + dialog.innerHTML = multiline + ? " " + : " " dialog.close = function () { - that.prompt_box = null; + that.prompt_box = null if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); + dialog.parentNode.removeChild(dialog) } - }; + } - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - canvas.parentNode.appendChild(dialog); + const graphcanvas = LGraphCanvas.active_canvas + const canvas = graphcanvas.canvas + canvas.parentNode.appendChild(dialog) - if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; - } + if (this.ds.scale > 1) dialog.style.transform = "scale(" + this.ds.scale + ")" - var dialogCloseTimer = null; - var prevent_timeout = false; - LiteGraph.pointerListenerAdd(dialog, "leave", function (e) { + let dialogCloseTimer = null + let prevent_timeout = 0 + LiteGraph.pointerListenerAdd(dialog, "leave", function () { if (prevent_timeout) - return; + return if (LiteGraph.dialog_close_on_mouse_leave) if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - LiteGraph.pointerListenerAdd(dialog, "enter", function (e) { - if (LiteGraph.dialog_close_on_mouse_leave) - if (dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - var selInDia = dialog.querySelectorAll("select"); + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay) //dialog.close(); + }) + LiteGraph.pointerListenerAdd(dialog, "enter", function () { + if (LiteGraph.dialog_close_on_mouse_leave && dialogCloseTimer) + clearTimeout(dialogCloseTimer) + }) + const selInDia = dialog.querySelectorAll("select") if (selInDia) { // if filtering, check focus changed to comboboxes and prevent closing - selInDia.forEach(function (selIn) { - selIn.addEventListener("click", function (e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function (e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function (e) { - prevent_timeout = -1; - }); - }); - } - - if (that.prompt_box) { - that.prompt_box.close(); + for (const selIn of selInDia) { + selIn.addEventListener("click", function () { prevent_timeout++ }) + selIn.addEventListener("blur", function () { prevent_timeout = 0 }) + selIn.addEventListener("change", function () { prevent_timeout = -1 }) + } } - that.prompt_box = dialog; - - var first = null; - var timeout = null; - var selected = null; + this.prompt_box?.close() + this.prompt_box = dialog - var name_element = dialog.querySelector(".name"); - name_element.innerText = title; - var value_element = dialog.querySelector(".value"); - value_element.value = value; - value_element.select(); + const name_element: HTMLSpanElement = dialog.querySelector(".name") + name_element.innerText = title + const value_element: HTMLTextAreaElement | HTMLInputElement = dialog.querySelector(".value") + value_element.value = value + value_element.select() - var input = value_element; - input.addEventListener("keydown", function (e) { - dialog.is_modified = true; + const input = value_element + input.addEventListener("keydown", function (e: KeyboardEvent) { + dialog.is_modified = true if (e.keyCode == 27) { //ESC - dialog.close(); - } else if (e.keyCode == 13 && e.target.localName != "textarea") { + dialog.close() + } else if (e.keyCode == 13 && (e.target as Element).localName != "textarea") { if (callback) { - callback(this.value); + callback(this.value) } - dialog.close(); + dialog.close() } else { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - - var button = dialog.querySelector("button"); - button.addEventListener("click", function (e) { - if (callback) { - callback(input.value); - } - that.setDirty(true); - dialog.close(); - }); - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; + return + } + e.preventDefault() + e.stopPropagation() + }) + + const button = dialog.querySelector("button") + button.addEventListener("click", function () { + callback?.(input.value) + that.setDirty(true) + dialog.close() + }) + + const rect = canvas.getBoundingClientRect() + let offsetx = -20 + let offsety = -20 if (rect) { - offsetx -= rect.left; - offsety -= rect.top; + offsetx -= rect.left + offsety -= rect.top } if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; + dialog.style.left = event.clientX + offsetx + "px" + dialog.style.top = event.clientY + offsety + "px" } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; + dialog.style.left = canvas.width * 0.5 + offsetx + "px" + dialog.style.top = canvas.height * 0.5 + offsety + "px" } setTimeout(function () { - input.focus(); - const clickTime = Date.now(); + input.focus() + const clickTime = Date.now() function handleOutsideClick(e) { if (e.target === canvas && Date.now() - clickTime > 256) { - dialog.close(); - canvas.parentNode.removeEventListener("click", handleOutsideClick); - canvas.parentNode.removeEventListener("touchend", handleOutsideClick); + dialog.close() + canvas.parentNode.removeEventListener("click", handleOutsideClick) + canvas.parentNode.removeEventListener("touchend", handleOutsideClick) } } - canvas.parentNode.addEventListener("click", handleOutsideClick); - canvas.parentNode.addEventListener("touchend", handleOutsideClick); - }, 10); + canvas.parentNode.addEventListener("click", handleOutsideClick) + canvas.parentNode.addEventListener("touchend", handleOutsideClick) + }, 10) - return dialog; + return dialog } showSearchBox(event: CanvasMouseEvent, options?: IShowSearchOptions): HTMLDivElement { // proposed defaults - var def_options = { + const def_options: IShowSearchOptions = { slot_from: null, node_from: null, node_to: null, do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out , + // @ts-expect-error type_filter_in: false // these are default: pass to set initially set values , + // @ts-expect-error type_filter_out: false, show_general_if_none_on_typefilter: true, show_general_after_typefiltered: true, hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave, show_all_if_empty: true, show_all_on_open: LiteGraph.search_show_all_on_open - }; - options = Object.assign(def_options, options || {}); + } + options = Object.assign(def_options, options || {}) //console.log(options); - var that = this; - var input_html = ""; - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - var root_document = canvas.ownerDocument || document; - - var dialog = document.createElement("div"); - dialog.className = "litegraph litesearchbox graphdialog rounded"; - dialog.innerHTML = "Search "; + const that = this + const graphcanvas = LGraphCanvas.active_canvas + const canvas = graphcanvas.canvas + const root_document = canvas.ownerDocument || document + + const dialog = document.createElement("div") + dialog.className = "litegraph litesearchbox graphdialog rounded" + dialog.innerHTML = "Search " if (options.do_type_filter) { - dialog.innerHTML += ""; - dialog.innerHTML += ""; + dialog.innerHTML += "" + dialog.innerHTML += "" } - dialog.innerHTML += "
"; + dialog.innerHTML += "
" if (root_document.fullscreenElement) - root_document.fullscreenElement.appendChild(dialog); + root_document.fullscreenElement.appendChild(dialog) else { - root_document.body.appendChild(dialog); - root_document.body.style.overflow = "hidden"; + root_document.body.appendChild(dialog) + root_document.body.style.overflow = "hidden" } // dialog element has been appended + let selIn + let selOut if (options.do_type_filter) { - var selIn = dialog.querySelector(".slot_in_type_filter"); - var selOut = dialog.querySelector(".slot_out_type_filter"); + selIn = dialog.querySelector(".slot_in_type_filter") + selOut = dialog.querySelector(".slot_out_type_filter") } + // @ts-expect-error Panel? dialog.close = function () { - that.search_box = null; - this.blur(); - canvas.focus(); - root_document.body.style.overflow = ""; + that.search_box = null + this.blur() + canvas.focus() + root_document.body.style.overflow = "" setTimeout(function () { - that.canvas.focus(); - }, 20); //important, if canvas loses focus keys wont be captured - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; + that.canvas.focus() + }, 20) //important, if canvas loses focus keys wont be captured + dialog.parentNode?.removeChild(dialog) + } if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; + dialog.style.transform = "scale(" + this.ds.scale + ")" } // hide on mouse leave if (options.hide_on_mouse_leave) { - var prevent_timeout = false; - var timeout_close = null; - LiteGraph.pointerListenerAdd(dialog, "enter", function (e) { + // FIXME: Remove "any" kludge + let prevent_timeout: any = false + let timeout_close = null + LiteGraph.pointerListenerAdd(dialog, "enter", function () { if (timeout_close) { - clearTimeout(timeout_close); - timeout_close = null; - } - }); - LiteGraph.pointerListenerAdd(dialog, "leave", function (e) { - if (prevent_timeout) { - return; + clearTimeout(timeout_close) + timeout_close = null } + }) + LiteGraph.pointerListenerAdd(dialog, "leave", function () { + if (prevent_timeout) + return timeout_close = setTimeout(function () { - dialog.close(); - }, typeof options.hide_on_mouse_leave === "number" ? options.hide_on_mouse_leave : 500); - }); + // @ts-expect-error Panel? + dialog.close() + }, typeof options.hide_on_mouse_leave === "number" ? options.hide_on_mouse_leave : 500) + }) // if filtering, check focus changed to comboboxes and prevent closing if (options.do_type_filter) { - selIn.addEventListener("click", function (e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function (e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function (e) { - prevent_timeout = -1; - }); - selOut.addEventListener("click", function (e) { - prevent_timeout++; - }); - selOut.addEventListener("blur", function (e) { - prevent_timeout = 0; - }); - selOut.addEventListener("change", function (e) { - prevent_timeout = -1; - }); + selIn.addEventListener("click", function () { + prevent_timeout++ + }) + selIn.addEventListener("blur", function () { + prevent_timeout = 0 + }) + selIn.addEventListener("change", function () { + prevent_timeout = -1 + }) + selOut.addEventListener("click", function () { + prevent_timeout++ + }) + selOut.addEventListener("blur", function () { + prevent_timeout = 0 + }) + selOut.addEventListener("change", function () { + prevent_timeout = -1 + }) } } - if (that.search_box) { - that.search_box.close(); - } - that.search_box = dialog; + // @ts-expect-error Panel? + that.search_box?.close() + that.search_box = dialog - var helper = dialog.querySelector(".helper"); + const helper = dialog.querySelector(".helper") - var first = null; - var timeout = null; - var selected = null; + let first = null + let timeout = null + let selected = null - var input = dialog.querySelector("input"); + const input = dialog.querySelector("input") if (input) { - input.addEventListener("blur", function (e) { - this.focus(); - }); + input.addEventListener("blur", function () { + this.focus() + }) input.addEventListener("keydown", function (e) { if (e.keyCode == 38) { //UP - changeSelection(false); + changeSelection(false) } else if (e.keyCode == 40) { //DOWN - changeSelection(true); + changeSelection(true) } else if (e.keyCode == 27) { //ESC - dialog.close(); + // @ts-expect-error Panel? + dialog.close() } else if (e.keyCode == 13) { if (selected) { - select(unescape(selected.dataset["type"])); + select(unescape(selected.dataset["type"])) } else if (first) { - select(first); + select(first) } else { - dialog.close(); + // @ts-expect-error Panel? + dialog.close() } } else { if (timeout) { - clearInterval(timeout); + clearInterval(timeout) } - timeout = setTimeout(refreshHelper, 10); - return; + timeout = setTimeout(refreshHelper, 10) + return } - e.preventDefault(); - e.stopPropagation(); - e.stopImmediatePropagation(); - return true; - }); + e.preventDefault() + e.stopPropagation() + e.stopImmediatePropagation() + return true + }) } // if should filter on type, load and fill selected and choose elements if passed if (options.do_type_filter) { if (selIn) { - var aSlots = LiteGraph.slot_types_in; - var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; + const aSlots = LiteGraph.slot_types_in + const nSlots = aSlots.length // this for object :: Object.keys(aSlots).length; if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION) - options.type_filter_in = "_event_"; + options.type_filter_in = "_event_" /* this will filter on * .. but better do it manually in case else if(options.type_filter_in === "" || options.type_filter_in === 0) options.type_filter_in = "*";*/ - for (var iK = 0; iK < nSlots; iK++) { - var opt = document.createElement('option'); - opt.value = aSlots[iK]; - opt.innerHTML = aSlots[iK]; - selIn.appendChild(opt); + for (let iK = 0; iK < nSlots; iK++) { + const opt = document.createElement('option') + opt.value = aSlots[iK] + opt.innerHTML = aSlots[iK] + selIn.appendChild(opt) + // @ts-expect-error if (options.type_filter_in !== false && (options.type_filter_in + "").toLowerCase() == (aSlots[iK] + "").toLowerCase()) { //selIn.selectedIndex .. - opt.selected = true; + opt.selected = true //console.log("comparing IN "+options.type_filter_in+" :: "+aSlots[iK]); } else { //console.log("comparing OUT "+options.type_filter_in+" :: "+aSlots[iK]); } } selIn.addEventListener("change", function () { - refreshHelper(); - }); + refreshHelper() + }) } if (selOut) { - var aSlots = LiteGraph.slot_types_out; - var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; + const aSlots = LiteGraph.slot_types_out + const nSlots = aSlots.length // this for object :: Object.keys(aSlots).length; if (options.type_filter_out == LiteGraph.EVENT || options.type_filter_out == LiteGraph.ACTION) - options.type_filter_out = "_event_"; + options.type_filter_out = "_event_" /* this will filter on * .. but better do it manually in case else if(options.type_filter_out === "" || options.type_filter_out === 0) options.type_filter_out = "*";*/ - for (var iK = 0; iK < nSlots; iK++) { - var opt = document.createElement('option'); - opt.value = aSlots[iK]; - opt.innerHTML = aSlots[iK]; - selOut.appendChild(opt); - if (options.type_filter_out !== false && (options.type_filter_out + "").toLowerCase() == (aSlots[iK] + "").toLowerCase()) { - //selOut.selectedIndex .. - opt.selected = true; - } + for (let iK = 0; iK < nSlots; iK++) { + const opt = document.createElement('option') + opt.value = aSlots[iK] + opt.innerHTML = aSlots[iK] + selOut.appendChild(opt) + // @ts-expect-error + if (options.type_filter_out !== false && (options.type_filter_out + "").toLowerCase() == (aSlots[iK] + "").toLowerCase()) + opt.selected = true } selOut.addEventListener("change", function () { - refreshHelper(); - }); + refreshHelper() + }) } } //compute best position - var rect = canvas.getBoundingClientRect(); + const rect = canvas.getBoundingClientRect() - var left = (event ? event.clientX : (rect.left + rect.width * 0.5)) - 80; - var top = (event ? event.clientY : (rect.top + rect.height * 0.5)) - 20; - dialog.style.left = left + "px"; - dialog.style.top = top + "px"; + const left = (event ? event.clientX : (rect.left + rect.width * 0.5)) - 80 + const top = (event ? event.clientY : (rect.top + rect.height * 0.5)) - 20 + dialog.style.left = left + "px" + dialog.style.top = top + "px" //To avoid out of screen problems if (event.layerY > (rect.height - 200)) - helper.style.maxHeight = (rect.height - event.layerY - 20) + "px"; + // @ts-expect-error + helper.style.maxHeight = (rect.height - event.layerY - 20) + "px" /* var offsetx = -20; @@ -6965,360 +6629,354 @@ export class LGraphCanvas { canvas.parentNode.appendChild(dialog); */ requestAnimationFrame(function () { - input.focus(); - }); - if (options.show_all_on_open) refreshHelper(); + input.focus() + }) + if (options.show_all_on_open) refreshHelper() function select(name) { if (name) { if (that.onSearchBoxSelection) { - that.onSearchBoxSelection(name, event, graphcanvas); + that.onSearchBoxSelection(name, event, graphcanvas) } else { - var extra = LiteGraph.searchbox_extras[name.toLowerCase()]; - if (extra) { - name = extra.type; - } + const extra = LiteGraph.searchbox_extras[name.toLowerCase()] + if (extra) + name = extra.type - graphcanvas.graph.beforeChange(); - var node = LiteGraph.createNode(name); + graphcanvas.graph.beforeChange() + const node = LiteGraph.createNode(name) if (node) { node.pos = graphcanvas.convertEventToCanvasOffset( event - ); - graphcanvas.graph.add(node, false); + ) + graphcanvas.graph.add(node, false) } - if (extra && extra.data) { + if (extra?.data) { if (extra.data.properties) { - for (var i in extra.data.properties) { - node.addProperty(i, extra.data.properties[i]); + for (const i in extra.data.properties) { + node.addProperty(i, extra.data.properties[i]) } } if (extra.data.inputs) { - node.inputs = []; - for (var i in extra.data.inputs) { + node.inputs = [] + for (const i in extra.data.inputs) { node.addOutput( extra.data.inputs[i][0], extra.data.inputs[i][1] - ); + ) } } if (extra.data.outputs) { - node.outputs = []; - for (var i in extra.data.outputs) { + node.outputs = [] + for (const i in extra.data.outputs) { node.addOutput( extra.data.outputs[i][0], extra.data.outputs[i][1] - ); + ) } } if (extra.data.title) { - node.title = extra.data.title; + node.title = extra.data.title } if (extra.data.json) { - node.configure(extra.data.json); + node.configure(extra.data.json) } } // join node after inserting if (options.node_from) { - var iS = false; + // FIXME: any + let iS: any = false switch (typeof options.slot_from) { case "string": - iS = options.node_from.findOutputSlot(options.slot_from); - break; + iS = options.node_from.findOutputSlot(options.slot_from) + break case "object": - if (options.slot_from.name) { - iS = options.node_from.findOutputSlot(options.slot_from.name); - } else { - iS = -1; - } - if (iS == -1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; - break; + iS = options.slot_from.name + ? options.node_from.findOutputSlot(options.slot_from.name) + : -1 + // @ts-expect-error change interface check + if (iS == -1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index + break case "number": - iS = options.slot_from; - break; + iS = options.slot_from + break default: - iS = 0; // try with first if no name set + iS = 0 // try with first if no name set } if (typeof options.node_from.outputs[iS] !== "undefined") { if (iS !== false && iS > -1) { - options.node_from.connectByType(iS, node, options.node_from.outputs[iS].type); + options.node_from.connectByType(iS, node, options.node_from.outputs[iS].type) } } else { // console.warn("cant find slot " + options.slot_from); } } if (options.node_to) { - var iS = false; + // FIXME: any + let iS: any = false switch (typeof options.slot_from) { case "string": - iS = options.node_to.findInputSlot(options.slot_from); - break; + iS = options.node_to.findInputSlot(options.slot_from) + break case "object": - if (options.slot_from.name) { - iS = options.node_to.findInputSlot(options.slot_from.name); - } else { - iS = -1; - } - if (iS == -1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; - break; + iS = options.slot_from.name + ? options.node_to.findInputSlot(options.slot_from.name) + : -1 + // @ts-expect-error change interface check + if (iS == -1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index + break case "number": - iS = options.slot_from; - break; + iS = options.slot_from + break default: - iS = 0; // try with first if no name set + iS = 0 // try with first if no name set } if (typeof options.node_to.inputs[iS] !== "undefined") { if (iS !== false && iS > -1) { // try connection - options.node_to.connectByTypeOutput(iS, node, options.node_to.inputs[iS].type); + options.node_to.connectByTypeOutput(iS, node, options.node_to.inputs[iS].type) } } else { // console.warn("cant find slot_nodeTO " + options.slot_from); } } - graphcanvas.graph.afterChange(); + graphcanvas.graph.afterChange() } } - dialog.close(); + // @ts-expect-error Panel? + dialog.close() } function changeSelection(forward) { - var prev = selected; - if (selected) { - selected.classList.remove("selected"); - } + const prev = selected if (!selected) { selected = forward ? helper.childNodes[0] - : helper.childNodes[helper.childNodes.length]; + : helper.childNodes[helper.childNodes.length] } else { + selected.classList.remove("selected") selected = forward ? selected.nextSibling - : selected.previousSibling; - if (!selected) { - selected = prev; - } - } - if (!selected) { - return; + : selected.previousSibling + selected ||= prev } - selected.classList.add("selected"); - selected.scrollIntoView({ block: "end", behavior: "smooth" }); + if (!selected) return + + selected.classList.add("selected") + selected.scrollIntoView({ block: "end", behavior: "smooth" }) } function refreshHelper() { - timeout = null; - var str = input.value; - first = null; - helper.innerHTML = ""; - if (!str && !options.show_all_if_empty) { - return; - } + timeout = null + let str = input.value + first = null + helper.innerHTML = "" + if (!str && !options.show_all_if_empty) return if (that.onSearchBox) { - var list = that.onSearchBox(helper, str, graphcanvas); + const list = that.onSearchBox(helper, str, graphcanvas) if (list) { - for (var i = 0; i < list.length; ++i) { - addResult(list[i]); + for (let i = 0; i < list.length; ++i) { + addResult(list[i]) } } } else { - var c = 0; - str = str.toLowerCase(); - var filter = graphcanvas.filter || graphcanvas.graph.filter; + let c = 0 + str = str.toLowerCase() + const filter = graphcanvas.filter || graphcanvas.graph.filter + // FIXME: any // filter by type preprocess + let sIn: any = false + let sOut: any = false if (options.do_type_filter && that.search_box) { - var sIn = that.search_box.querySelector(".slot_in_type_filter"); - var sOut = that.search_box.querySelector(".slot_out_type_filter"); - } else { - var sIn = false; - var sOut = false; + sIn = that.search_box.querySelector(".slot_in_type_filter") + sOut = that.search_box.querySelector(".slot_out_type_filter") } //extras - for (var i in LiteGraph.searchbox_extras) { - var extra = LiteGraph.searchbox_extras[i]; - if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) { - continue; - } - var ctor = LiteGraph.registered_node_types[extra.type]; + for (const i in LiteGraph.searchbox_extras) { + const extra = LiteGraph.searchbox_extras[i] + if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) + continue + const ctor = LiteGraph.registered_node_types[extra.type] if (ctor && ctor.filter != filter) - continue; + continue if (!inner_test_filter(extra.type)) - continue; - addResult(extra.desc, "searchbox_extra"); + continue + addResult(extra.desc, "searchbox_extra") if (LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit) { - break; + break } } - var filtered = null; + let filtered = null if (Array.prototype.filter) { //filter supported - var keys = Object.keys(LiteGraph.registered_node_types); //types - var filtered = keys.filter(inner_test_filter); + const keys = Object.keys(LiteGraph.registered_node_types) //types + filtered = keys.filter(inner_test_filter) } else { - filtered = []; - for (var i in LiteGraph.registered_node_types) { + filtered = [] + for (const i in LiteGraph.registered_node_types) { if (inner_test_filter(i)) - filtered.push(i); + filtered.push(i) } } - for (var i = 0; i < filtered.length; i++) { - addResult(filtered[i]); - if (LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit) { - break; - } + for (let i = 0; i < filtered.length; i++) { + addResult(filtered[i]) + if (LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit) + break } // add general type if filtering if (options.show_general_after_typefiltered && (sIn.value || sOut.value)) { - filtered_extra = []; - for (var i in LiteGraph.registered_node_types) { + // FIXME: Undeclared variable again + // @ts-expect-error + filtered_extra = [] + for (const i in LiteGraph.registered_node_types) { if (inner_test_filter(i, { inTypeOverride: sIn && sIn.value ? "*" : false, outTypeOverride: sOut && sOut.value ? "*" : false })) - filtered_extra.push(i); + // @ts-expect-error + filtered_extra.push(i) } - for (var i = 0; i < filtered_extra.length; i++) { - addResult(filtered_extra[i], "generic_type"); - if (LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit) { - break; - } + // @ts-expect-error + for (let i = 0; i < filtered_extra.length; i++) { + // @ts-expect-error + addResult(filtered_extra[i], "generic_type") + if (LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit) + break } } // check il filtering gave no results if ((sIn.value || sOut.value) && ((helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter))) { - filtered_extra = []; - for (var i in LiteGraph.registered_node_types) { + // @ts-expect-error + filtered_extra = [] + for (const i in LiteGraph.registered_node_types) { if (inner_test_filter(i, { skipFilter: true })) - filtered_extra.push(i); + // @ts-expect-error + filtered_extra.push(i) } - for (var i = 0; i < filtered_extra.length; i++) { - addResult(filtered_extra[i], "not_in_filter"); - if (LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit) { - break; - } + // @ts-expect-error + for (let i = 0; i < filtered_extra.length; i++) { + // @ts-expect-error + addResult(filtered_extra[i], "not_in_filter") + if (LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit) + break } } - function inner_test_filter(type, optsIn) { - var optsIn = optsIn || {}; - var optsDef = { + function inner_test_filter(type: string, optsIn?: number | { inTypeOverride?: string | boolean; outTypeOverride?: string | boolean; skipFilter?: boolean }): boolean { + optsIn = optsIn || {} + const optsDef = { skipFilter: false, inTypeOverride: false, outTypeOverride: false - }; - var opts = Object.assign(optsDef, optsIn); - var ctor = LiteGraph.registered_node_types[type]; + } + const opts = Object.assign(optsDef, optsIn) + const ctor = LiteGraph.registered_node_types[type] if (filter && ctor.filter != filter) - return false; + return false if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1 && (!ctor.title || ctor.title.toLowerCase().indexOf(str) === -1)) - return false; + return false // filter by slot IN, OUT types if (options.do_type_filter && !opts.skipFilter) { - var sType = type; + const sType = type - var sV = sIn.value; - if (opts.inTypeOverride !== false) sV = opts.inTypeOverride; + let sV = opts.inTypeOverride !== false + ? opts.inTypeOverride + : sIn.value //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 if (sIn && sV) { //console.log("will check filter against "+sV); - if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes) { // type is stored + if (LiteGraph.registered_slot_in_types[sV]?.nodes) { // type is stored //console.debug("check "+sType+" in "+LiteGraph.registered_slot_in_types[sV].nodes); - var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType); + const doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType) if (doesInc !== false) { //console.log(sType+" HAS "+sV); } else { /*console.debug(LiteGraph.registered_slot_in_types[sV]); console.log(+" DONT includes "+type);*/ - return false; + return false } } } - var sV = sOut.value; - if (opts.outTypeOverride !== false) sV = opts.outTypeOverride; + sV = sOut.value + if (opts.outTypeOverride !== false) sV = opts.outTypeOverride //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 if (sOut && sV) { //console.log("search will check filter against "+sV); - if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes) { // type is stored + if (LiteGraph.registered_slot_out_types[sV]?.nodes) { // type is stored //console.debug("check "+sType+" in "+LiteGraph.registered_slot_out_types[sV].nodes); - var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType); + const doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType) if (doesInc !== false) { //console.log(sType+" HAS "+sV); } else { /*console.debug(LiteGraph.registered_slot_out_types[sV]); console.log(+" DONT includes "+type);*/ - return false; + return false } } } } - return true; + return true } } - function addResult(type, className) { - var help = document.createElement("div"); - if (!first) { - first = type; - } + function addResult(type: string, className?: string): void { + const help = document.createElement("div") + first ||= type - const nodeType = LiteGraph.registered_node_types[type]; + const nodeType = LiteGraph.registered_node_types[type] if (nodeType?.title) { - help.innerText = nodeType?.title; - const typeEl = document.createElement("span"); - typeEl.className = "litegraph lite-search-item-type"; - typeEl.textContent = type; - help.append(typeEl); + help.innerText = nodeType?.title + const typeEl = document.createElement("span") + typeEl.className = "litegraph lite-search-item-type" + typeEl.textContent = type + help.append(typeEl) } else { - help.innerText = type; + help.innerText = type } - help.dataset["type"] = escape(type); - help.className = "litegraph lite-search-item"; + help.dataset["type"] = escape(type) + help.className = "litegraph lite-search-item" if (className) { - help.className += " " + className; + help.className += " " + className } - help.addEventListener("click", function (e) { - select(unescape(this.dataset["type"])); - }); - helper.appendChild(help); + help.addEventListener("click", function () { + select(unescape(this.dataset["type"])) + }) + helper.appendChild(help) } } - return dialog; + return dialog } showEditPropertyValue(node: LGraphNode, property: string, options: IDialogOptions): IDialog { - if (!node || node.properties[property] === undefined) { - return; - } + if (!node || node.properties[property] === undefined) return - options = options || {}; - var that = this; + options = options || {} - var info = node.getPropertyInfo(property); - var type = info.type; + const info = node.getPropertyInfo(property) + const type = info.type - var input_html = ""; + let input_html = "" if (type == "string" || type == "number" || type == "array" || type == "object") { - input_html = ""; + input_html = "" } else if ((type == "enum" || type == "combo") && info.values) { - input_html = "" + for (const i in info.values) { + const v = info.values.constructor === Array + ? info.values[i] + : i input_html += "