diff --git a/src/LiteGraphGlobal.ts b/src/LiteGraphGlobal.ts index 838950530..986db7d01 100644 --- a/src/LiteGraphGlobal.ts +++ b/src/LiteGraphGlobal.ts @@ -5,17 +5,15 @@ import type { DragAndScale } from "./DragAndScale" import type { LGraphCanvas } from "./LGraphCanvas" import type { ContextMenu } from "./ContextMenu" import type { CurveEditor } from "./CurveEditor" +import { LGraphEventMode, LinkDirection, LinkRenderType, NodeSlotType, RenderShape, TitleMode } from "./types/globalEnums" import { LiteGraph } from "./litegraph" import { LGraphNode } from "./LGraphNode" -import { drawSlot, SlotShape, SlotDirection, SlotType, LabelPosition } from "./draw" +import { SlotShape, SlotDirection, SlotType, LabelPosition } from "./draw" import type { Dictionary, ISlotType, Rect } from "./interfaces" import { distance, isInsideRectangle, overlapBounding } from "./measure" /** * The Global Scope. It contains all the registered node classes. - * - * @class LiteGraph - * @constructor */ export class LiteGraphGlobal { // Enums @@ -24,152 +22,146 @@ export class LiteGraphGlobal { SlotType = SlotType LabelPosition = LabelPosition - VERSION = 0.4; + VERSION = 0.4 - CANVAS_GRID_SIZE = 10; + CANVAS_GRID_SIZE = 10 - NODE_TITLE_HEIGHT = 30; - NODE_TITLE_TEXT_Y = 20; - NODE_SLOT_HEIGHT = 20; - NODE_WIDGET_HEIGHT = 20; - NODE_WIDTH = 140; - NODE_MIN_WIDTH = 50; - NODE_COLLAPSED_RADIUS = 10; - NODE_COLLAPSED_WIDTH = 80; - NODE_TITLE_COLOR = "#999"; - NODE_SELECTED_TITLE_COLOR = "#FFF"; - NODE_TEXT_SIZE = 14; - NODE_TEXT_COLOR = "#AAA"; - NODE_SUBTEXT_SIZE = 12; - NODE_DEFAULT_COLOR = "#333"; - NODE_DEFAULT_BGCOLOR = "#353535"; - NODE_DEFAULT_BOXCOLOR = "#666"; - NODE_DEFAULT_SHAPE = "box"; - NODE_BOX_OUTLINE_COLOR = "#FFF"; - DEFAULT_SHADOW_COLOR = "rgba(0,0,0,0.5)"; - DEFAULT_GROUP_FONT = 24; + NODE_TITLE_HEIGHT = 30 + NODE_TITLE_TEXT_Y = 20 + NODE_SLOT_HEIGHT = 20 + NODE_WIDGET_HEIGHT = 20 + NODE_WIDTH = 140 + NODE_MIN_WIDTH = 50 + NODE_COLLAPSED_RADIUS = 10 + NODE_COLLAPSED_WIDTH = 80 + NODE_TITLE_COLOR = "#999" + NODE_SELECTED_TITLE_COLOR = "#FFF" + NODE_TEXT_SIZE = 14 + NODE_TEXT_COLOR = "#AAA" + NODE_SUBTEXT_SIZE = 12 + NODE_DEFAULT_COLOR = "#333" + NODE_DEFAULT_BGCOLOR = "#353535" + NODE_DEFAULT_BOXCOLOR = "#666" + NODE_DEFAULT_SHAPE = "box" + NODE_BOX_OUTLINE_COLOR = "#FFF" + DEFAULT_SHADOW_COLOR = "rgba(0,0,0,0.5)" + DEFAULT_GROUP_FONT = 24 DEFAULT_GROUP_FONT_SIZE?: any - WIDGET_BGCOLOR = "#222"; - WIDGET_OUTLINE_COLOR = "#666"; - WIDGET_TEXT_COLOR = "#DDD"; - WIDGET_SECONDARY_TEXT_COLOR = "#999"; + WIDGET_BGCOLOR = "#222" + WIDGET_OUTLINE_COLOR = "#666" + WIDGET_TEXT_COLOR = "#DDD" + WIDGET_SECONDARY_TEXT_COLOR = "#999" - LINK_COLOR = "#9A9"; + LINK_COLOR = "#9A9" // TODO: This is a workaround until LGraphCanvas.link_type_colors is no longer static. - static DEFAULT_EVENT_LINK_COLOR = "#A86"; - EVENT_LINK_COLOR = "#A86"; - CONNECTING_LINK_COLOR = "#AFA"; - - MAX_NUMBER_OF_NODES = 10000; //avoid infinite loops - DEFAULT_POSITION = [100, 100]; //default node position - VALID_SHAPES = ["default", "box", "round", "card"]; //,"circle" + static DEFAULT_EVENT_LINK_COLOR = "#A86" + EVENT_LINK_COLOR = "#A86" + CONNECTING_LINK_COLOR = "#AFA" + MAX_NUMBER_OF_NODES = 10000 //avoid infinite loops + DEFAULT_POSITION = [100, 100] //default node position + VALID_SHAPES = ["default", "box", "round", "card"] //,"circle" //shapes are used for nodes but also for slots - BOX_SHAPE = 1; - ROUND_SHAPE = 2; - CIRCLE_SHAPE = 3; - CARD_SHAPE = 4; - ARROW_SHAPE = 5; - GRID_SHAPE = 6; // intended for slot arrays - + BOX_SHAPE = RenderShape.BOX + ROUND_SHAPE = RenderShape.ROUND + CIRCLE_SHAPE = RenderShape.CIRCLE + CARD_SHAPE = RenderShape.CARD + ARROW_SHAPE = RenderShape.ARROW + GRID_SHAPE = RenderShape.GRID // intended for slot arrays //enums - INPUT = 1; - OUTPUT = 2; + INPUT = NodeSlotType.INPUT + OUTPUT = NodeSlotType.OUTPUT - EVENT = -1; //for outputs - ACTION = -1; //for inputs + // TODO: -1 can lead to ambiguity in JS; these should be updated to a more explicit constant or Symbol. + EVENT = -1 as const //for outputs + ACTION = -1 as const //for inputs - NODE_MODES = ["Always", "On Event", "Never", "On Trigger"]; // helper, will add "On Request" and more in the future - NODE_MODES_COLORS = ["#666", "#422", "#333", "#224", "#626"]; // use with node_box_coloured_by_mode - ALWAYS = 0; - ON_EVENT = 1; - NEVER = 2; - ON_TRIGGER = 3; + NODE_MODES = ["Always", "On Event", "Never", "On Trigger"] // helper, will add "On Request" and more in the future + NODE_MODES_COLORS = ["#666", "#422", "#333", "#224", "#626"] // use with node_box_coloured_by_mode + ALWAYS = LGraphEventMode.ALWAYS + ON_EVENT = LGraphEventMode.ON_EVENT + NEVER = LGraphEventMode.NEVER + ON_TRIGGER = LGraphEventMode.ON_TRIGGER - UP = 1; - DOWN = 2; - LEFT = 3; - RIGHT = 4; - CENTER = 5; + UP = LinkDirection.UP + DOWN = LinkDirection.DOWN + LEFT = LinkDirection.LEFT + RIGHT = LinkDirection.RIGHT + CENTER = LinkDirection.CENTER - LINK_RENDER_MODES = ["Straight", "Linear", "Spline"]; // helper - HIDDEN_LINK = -1; - STRAIGHT_LINK = 0; - LINEAR_LINK = 1; - SPLINE_LINK = 2; + LINK_RENDER_MODES = ["Straight", "Linear", "Spline"] // helper + HIDDEN_LINK = LinkRenderType.HIDDEN_LINK + STRAIGHT_LINK = LinkRenderType.STRAIGHT_LINK + LINEAR_LINK = LinkRenderType.LINEAR_LINK + SPLINE_LINK = LinkRenderType.SPLINE_LINK - NORMAL_TITLE = 0; - NO_TITLE = 1; - TRANSPARENT_TITLE = 2; - AUTOHIDE_TITLE = 3; - VERTICAL_LAYOUT = "vertical"; // arrange nodes vertically + NORMAL_TITLE = TitleMode.NORMAL_TITLE + NO_TITLE = TitleMode.NO_TITLE + TRANSPARENT_TITLE = TitleMode.TRANSPARENT_TITLE + AUTOHIDE_TITLE = TitleMode.AUTOHIDE_TITLE - proxy = null; //used to redirect calls - node_images_path = ""; + VERTICAL_LAYOUT = "vertical" // arrange nodes vertically - debug = false; - catch_exceptions = true; - throw_errors = true; - allow_scripts = false; //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits - registered_node_types = {}; //nodetypes by string - node_types_by_file_extension = {}; //used for dropping files in the canvas - Nodes = {}; //node types by classname - Globals = {}; //used to store vars between graphs + proxy = null //used to redirect calls + node_images_path = "" - searchbox_extras = {}; //used to add extra features to the search box - auto_sort_node_types = false; // [true!] If set to true, will automatically sort node types / categories in the context menus + debug = false + catch_exceptions = true + throw_errors = true + allow_scripts = false //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits + registered_node_types: Record = {} //nodetypes by string + node_types_by_file_extension = {} //used for dropping files in the canvas + Nodes: Record = {} //node types by classname + Globals = {} //used to store vars between graphs - node_box_coloured_when_on = false; // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback - node_box_coloured_by_mode = false; // [true!] nodebox based on node mode, visual feedback + searchbox_extras = {} //used to add extra features to the search box + auto_sort_node_types = false // [true!] If set to true, will automatically sort node types / categories in the context menus - dialog_close_on_mouse_leave = false; // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false - dialog_close_on_mouse_leave_delay = 500; + node_box_coloured_when_on = false // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback + node_box_coloured_by_mode = false // [true!] nodebox based on node mode, visual feedback - shift_click_do_break_link_from = false; // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys - click_do_break_link_to = false; // [false!]prefer false, way too easy to break links - ctrl_alt_click_do_break_link = true; // [true!] who accidentally ctrl-alt-clicks on an in/output? nobody! that's who! + dialog_close_on_mouse_leave = false // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false + dialog_close_on_mouse_leave_delay = 500 - search_hide_on_mouse_leave = true; // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false - search_filter_enabled = false; // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] - search_show_all_on_open = true; // [true!] opens the results list when opening the search widget + shift_click_do_break_link_from = false // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys + click_do_break_link_to = false // [false!]prefer false, way too easy to break links + ctrl_alt_click_do_break_link = true // [true!] who accidentally ctrl-alt-clicks on an in/output? nobody! that's who! - auto_load_slot_types = false; // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out] + search_hide_on_mouse_leave = true // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false + search_filter_enabled = false // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] + search_show_all_on_open = true // [true!] opens the results list when opening the search widget + auto_load_slot_types = false // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out] // set these values if not using auto_load_slot_types - registered_slot_in_types = {}; // slot types for nodeclass - registered_slot_out_types = {}; // slot types for nodeclass - slot_types_in = []; // slot types IN - slot_types_out = []; // slot types OUT - // @ts-expect-error - slot_types_default_in: Record = [] // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search - // @ts-expect-error - slot_types_default_out: Record = [] // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search + registered_slot_in_types: Record = {} // slot types for nodeclass + registered_slot_out_types: Record = {} // slot types for nodeclass + slot_types_in: string[] = [] // slot types IN + slot_types_out: string[] = [] // slot types OUT + slot_types_default_in: Record = {} // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search + slot_types_default_out: Record = {} // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search - alt_drag_do_clone_nodes = false; // [true!] very handy, ALT click to clone and drag the new node + alt_drag_do_clone_nodes = false // [true!] very handy, ALT click to clone and drag the new node - do_add_triggers_slots = false; // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this + do_add_triggers_slots = false // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this - allow_multi_output_for_events = true; // [false!] being events, it is strongly reccomended to use them sequentially, one by one + allow_multi_output_for_events = true // [false!] being events, it is strongly reccomended to use them sequentially, one by one - middle_click_slot_add_default_node = false; //[true!] allows to create and connect a ndoe clicking with the third button (wheel) + middle_click_slot_add_default_node = false //[true!] allows to create and connect a ndoe clicking with the third button (wheel) - release_link_on_empty_shows_menu = false; //[true!] dragging a link to empty space will open a menu, add from list, search or defaults - - pointerevents_method = "pointer"; // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) + release_link_on_empty_shows_menu = false //[true!] dragging a link to empty space will open a menu, add from list, search or defaults + pointerevents_method = "pointer" // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary) - ctrl_shift_v_paste_connect_unselected_outputs = true; //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes - - + ctrl_shift_v_paste_connect_unselected_outputs = true //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers. // use this if you must have node IDs that are unique across all graphs and subgraphs. - use_uuids = false; + use_uuids = false // Whether to highlight the bounding box of selected groups highlight_selected_group = false @@ -183,6 +175,9 @@ export class LiteGraphGlobal { ContextMenu: typeof ContextMenu CurveEditor: typeof CurveEditor + onNodeTypeRegistered?(type: string, base_class: typeof LGraphNode): void + onNodeTypeReplaced?(type: string, base_class: typeof LGraphNode, prev: unknown): void + constructor() { //timer that works everywhere if (typeof performance != "undefined") { @@ -191,7 +186,7 @@ export class LiteGraphGlobal { this.getTime = Date.now.bind(Date) } else if (typeof process != "undefined") { this.getTime = function () { - var t = process.hrtime() + const t = process.hrtime() return t[0] * 0.001 + t[1] * 1e-6 } } else { @@ -203,34 +198,26 @@ export class LiteGraphGlobal { /** * Register a node class so it can be listed when the user wants to create a new one - * @method registerNodeType * @param {String} type name of the node and path * @param {Class} base_class class containing the structure of a node */ registerNodeType(type: string, base_class: typeof LGraphNode): void { - if (!base_class.prototype) { + if (!base_class.prototype) throw "Cannot register a simple object, it must be a class with a prototype" - } base_class.type = type - if (LiteGraph.debug) { - console.log("Node registered: " + type) - } + if (this.debug) console.log("Node registered: " + type) const classname = base_class.name const pos = type.lastIndexOf("/") base_class.category = type.substring(0, pos) - if (!base_class.title) { - base_class.title = classname - } + base_class.title ||= classname //extend class - for (var i in LGraphNode.prototype) { - if (!base_class.prototype[i]) { - base_class.prototype[i] = LGraphNode.prototype[i] - } + for (const i in LGraphNode.prototype) { + base_class.prototype[i] ||= LGraphNode.prototype[i] } const prev = this.registered_node_types[type] @@ -239,40 +226,39 @@ export class LiteGraphGlobal { } if (!Object.prototype.hasOwnProperty.call(base_class.prototype, "shape")) { Object.defineProperty(base_class.prototype, "shape", { - set: function (v) { + set(this: LGraphNode, v: RenderShape | "default" | "box" | "round" | "circle" | "card") { switch (v) { case "default": delete this._shape break case "box": - this._shape = LiteGraph.BOX_SHAPE + this._shape = RenderShape.BOX break case "round": - this._shape = LiteGraph.ROUND_SHAPE + this._shape = RenderShape.ROUND break case "circle": - this._shape = LiteGraph.CIRCLE_SHAPE + this._shape = RenderShape.CIRCLE break case "card": - this._shape = LiteGraph.CARD_SHAPE + this._shape = RenderShape.CARD break default: this._shape = v } }, - get: function () { + get() { return this._shape }, enumerable: true, configurable: true }) - //used to know which nodes to create when dragging files to the canvas if (base_class.supported_extensions) { - for (let i in base_class.supported_extensions) { + for (const i in base_class.supported_extensions) { const ext = base_class.supported_extensions[i] - if (ext && ext.constructor === String) { + if (ext && typeof ext === "string") { this.node_types_by_file_extension[ext.toLowerCase()] = base_class } } @@ -280,67 +266,48 @@ export class LiteGraphGlobal { } this.registered_node_types[type] = base_class - if (base_class.constructor.name) { - this.Nodes[classname] = base_class - } - if (this.onNodeTypeRegistered) { - this.onNodeTypeRegistered(type, base_class) - } - if (prev && this.onNodeTypeReplaced) { - this.onNodeTypeReplaced(type, base_class, prev) - } + if (base_class.constructor.name) this.Nodes[classname] = base_class + + this.onNodeTypeRegistered?.(type, base_class) + if (prev) this.onNodeTypeReplaced?.(type, base_class, prev) //warnings - if (base_class.prototype.onPropertyChange) { - console.warn( - "LiteGraph node class " + - type + - " has onPropertyChange method, it must be called onPropertyChanged with d at the end" - ) - } + if (base_class.prototype.onPropertyChange) + console.warn(`LiteGraph node class ${type} has onPropertyChange method, it must be called onPropertyChanged with d at the end`) // TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types - if (this.auto_load_slot_types) { - new base_class(base_class.title || "tmpnode") - } - }; - - onNodeTypeRegistered?(type: string, base_class: typeof LGraphNode): void - onNodeTypeReplaced?(type: string, base_class: typeof LGraphNode, prev: unknown): void + if (this.auto_load_slot_types) new base_class(base_class.title || "tmpnode") + } /** * removes a node type from the system - * @method unregisterNodeType * @param {String|Object} type name of the node or the node constructor itself */ unregisterNodeType(type: string | typeof LGraphNode): void { - const base_class = type.constructor === String + const base_class = typeof type === "string" ? this.registered_node_types[type] : type - if (!base_class) { - throw "node type not found: " + type - } + if (!base_class) throw "node type not found: " + type + delete this.registered_node_types[base_class.type] - if (base_class.constructor.name) { - delete this.Nodes[base_class.constructor.name] - } - }; + + const name = base_class.constructor.name + if (name) delete this.Nodes[name] + } /** - * Save a slot type and his node - * @method registerSlotType - * @param {String|Object} type name of the node or the node constructor itself - * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, .. - */ - registerNodeAndSlotType(type: ISlotType | LGraphNode, slot_type: ISlotType, out?: boolean): void { - out = out || false - const base_class = type.constructor === String && - // @ts-ignore - this.registered_node_types[type] !== "anonymous" - // @ts-ignore + * Save a slot type and his node + * @param {String|Object} type name of the node or the node constructor itself + * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, .. + */ + registerNodeAndSlotType(type: LGraphNode, slot_type: ISlotType, out?: boolean): void { + out ||= false + // @ts-expect-error Confirm this function no longer supports string types - base_class should always be an instance not a constructor. + const base_class = typeof type === "string" && this.registered_node_types[type] !== "anonymous" ? this.registered_node_types[type] : type + // @ts-expect-error Confirm this function no longer supports string types - base_class should always be an instance not a constructor. const class_type = base_class.constructor.type let allTypes = [] @@ -354,38 +321,30 @@ export class LiteGraphGlobal { for (let i = 0; i < allTypes.length; ++i) { let slotType = allTypes[i] - if (slotType === "") { - slotType = "*" - } + if (slotType === "") slotType = "*" + const registerTo = out ? "registered_slot_out_types" : "registered_slot_in_types" - if (this[registerTo][slotType] === undefined) { + if (this[registerTo][slotType] === undefined) this[registerTo][slotType] = { nodes: [] } - } - if (!this[registerTo][slotType].nodes.includes(class_type)) { + if (!this[registerTo][slotType].nodes.includes(class_type)) this[registerTo][slotType].nodes.push(class_type) - } // check if is a new type - if (!out) { - if (!this.slot_types_in.includes(slotType.toLowerCase())) { - this.slot_types_in.push(slotType.toLowerCase()) - this.slot_types_in.sort() - } - } else { - if (!this.slot_types_out.includes(slotType.toLowerCase())) { - this.slot_types_out.push(slotType.toLowerCase()) - this.slot_types_out.sort() - } + const types = out + ? this.slot_types_out + : this.slot_types_in + if (!types.includes(slotType.toLowerCase())) { + types.push(slotType.toLowerCase()) + types.sort() } } - }; + } /** * Create a new nodetype by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. - * @method wrapFunctionAsNode * @param {String} name node name with namespace (p.e.: 'math/sum') * @param {Function} func * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type @@ -394,48 +353,35 @@ export class LiteGraphGlobal { */ wrapFunctionAsNode( name: string, - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - func: Function, + func: (...args: any) => any, param_types: string[], return_type: string, properties: unknown ) { - var params = Array(func.length) - var code = "" - var names = this.getParameterNames(func) - for (var i = 0; i < names.length; ++i) { - code += - "this.addInput('" + - names[i] + - "'," + - (param_types && param_types[i] - ? "'" + param_types[i] + "'" - : "0") + - ");\n" + const params = Array(func.length) + let code = "" + const names = this.getParameterNames(func) + for (let i = 0; i < names.length; ++i) { + code += `this.addInput('${names[i]}',${param_types && param_types[i] ? `'${param_types[i]}'` : "0"});\n` } - code += - "this.addOutput('out'," + - (return_type ? "'" + return_type + "'" : 0) + - ");\n" - if (properties) { - code += - "this.properties = " + JSON.stringify(properties) + ";\n" - } - var classobj = Function(code) + code += `this.addOutput('out',${return_type ? `'${return_type}'` : 0});\n` + if (properties) code += `this.properties = ${JSON.stringify(properties)};\n` + + const classobj = Function(code) // @ts-ignore classobj.title = name.split("/").pop() // @ts-ignore classobj.desc = "Generated from " + func.name classobj.prototype.onExecute = function onExecute() { - for (var i = 0; i < params.length; ++i) { + for (let i = 0; i < params.length; ++i) { params[i] = this.getInputData(i) } - var r = func.apply(this, params) + const r = func.apply(this, params) this.setOutputData(0, r) } // @ts-expect-error Required to make this kludge work this.registerNodeType(name, classobj) - }; + } /** * Removes all previously registered node's types @@ -445,49 +391,40 @@ export class LiteGraphGlobal { this.node_types_by_file_extension = {} this.Nodes = {} this.searchbox_extras = {} - }; + } /** * Adds this method to all nodetypes, existing and to be created * (You can add it to LGraphNode.prototype but then existing node types wont have it) - * @method addNodeMethod * @param {Function} func */ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type addNodeMethod(name: string, func: Function): void { LGraphNode.prototype[name] = func - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i] - if (type.prototype[name]) { - type.prototype["_" + name] = type.prototype[name] - } //keep old in case of replacing + for (const i in this.registered_node_types) { + const type = this.registered_node_types[i] + //keep old in case of replacing + if (type.prototype[name]) type.prototype["_" + name] = type.prototype[name] type.prototype[name] = func } - }; + } /** * Create a node of a given type with a name. The node is not attached to any graph yet. - * @method createNode * @param {String} type full name of the node class. p.e. "math/sin" * @param {String} name a name to distinguish from other nodes * @param {Object} options to set options */ createNode(type: string, title?: string, options?: Dictionary): LGraphNode { - var base_class = this.registered_node_types[type] + const base_class = this.registered_node_types[type] if (!base_class) { - if (this.debug) { - console.log( - 'GraphNode type "' + type + '" not registered.' - ) - } + if (this.debug) console.log(`GraphNode type "${type}" not registered.`) return null } - var prototype = base_class.prototype || base_class - title = title || base_class.title || type - var node = null + let node = null if (this.catch_exceptions) { try { @@ -502,190 +439,151 @@ export class LiteGraphGlobal { node.type = type - if (!node.title && title) { - node.title = title - } - if (!node.properties) { - node.properties = {} - } - if (!node.properties_info) { - node.properties_info = [] - } - if (!node.flags) { - node.flags = {} - } - if (!node.size) { - node.size = node.computeSize() - //call onresize? - } - if (!node.pos) { - node.pos = this.DEFAULT_POSITION.concat() - } - if (!node.mode) { - node.mode = this.ALWAYS - } + if (!node.title && title) node.title = title + node.properties ||= {} + node.properties_info ||= [] + node.flags ||= {} + //call onresize? + node.size ||= node.computeSize() + node.pos ||= this.DEFAULT_POSITION.concat() + node.mode ||= this.ALWAYS //extra options if (options) { - for (var i in options) { + for (const i in options) { node[i] = options[i] } } // callback - if (node.onNodeCreated) { - node.onNodeCreated() - } - + node.onNodeCreated?.() return node - }; + } /** * Returns a registered node type with a given name - * @method getNodeType * @param {String} type full name of the node class. p.e. "math/sin" * @return {Class} the node class */ getNodeType(type: string): typeof LGraphNode { return this.registered_node_types[type] - }; + } /** * Returns a list of node types matching one category - * @method getNodeType * @param {String} category category name * @return {Array} array with all the node classes */ getNodeTypesInCategory(category: string, filter: any) { - var r = [] - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i] - if (type.filter != filter) { - continue - } + const r = [] + for (const i in this.registered_node_types) { + const type = this.registered_node_types[i] + if (type.filter != filter) continue if (category == "") { - if (type.category == null) { - r.push(type) - } + if (type.category == null) r.push(type) } else if (type.category == category) { r.push(type) } } if (this.auto_sort_node_types) { - r.sort(function (a, b) { return a.title.localeCompare(b.title) }) + r.sort(function (a, b) { + return a.title.localeCompare(b.title) + }) } return r - }; + } /** * Returns a list with all the node type categories - * @method getNodeTypesCategories * @param {String} filter only nodes with ctor.filter equal can be shown * @return {Array} array with all the names of the categories */ getNodeTypesCategories(filter: string): string[] { - var categories = { "": 1 } - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i] + const categories = { "": 1 } + for (const i in this.registered_node_types) { + const type = this.registered_node_types[i] if (type.category && !type.skip_list) { if (type.filter != filter) continue categories[type.category] = 1 } } - var result = [] - for (var i in categories) { + const result = [] + for (const i in categories) { result.push(i) } return this.auto_sort_node_types ? result.sort() : result - }; + } //debug purposes: reloads all the js scripts that matches a wildcard reloadNodes(folder_wildcard: string): void { - var tmp = document.getElementsByTagName("script") + const tmp = document.getElementsByTagName("script") //weird, this array changes by its own, so we use a copy - var script_files = [] - for (var i = 0; i < tmp.length; i++) { + const script_files = [] + for (let i = 0; i < tmp.length; i++) { script_files.push(tmp[i]) } - var docHeadObj = document.getElementsByTagName("head")[0] + const docHeadObj = document.getElementsByTagName("head")[0] folder_wildcard = document.location.href + folder_wildcard - for (var i = 0; i < script_files.length; i++) { - var src = script_files[i].src - if (!src || - src.substr(0, folder_wildcard.length) != folder_wildcard) { + for (let i = 0; i < script_files.length; i++) { + const src = script_files[i].src + if (!src || src.substr(0, folder_wildcard.length) != folder_wildcard) continue - } try { - if (this.debug) { - console.log("Reloading: " + src) - } - var dynamicScript = document.createElement("script") + if (this.debug) console.log("Reloading: " + src) + const dynamicScript = document.createElement("script") dynamicScript.type = "text/javascript" dynamicScript.src = src docHeadObj.appendChild(dynamicScript) docHeadObj.removeChild(script_files[i]) } catch (err) { - if (this.throw_errors) { - throw err - } - if (this.debug) { - console.log("Error while reloading " + src) - } + if (this.throw_errors) throw err + if (this.debug) console.log("Error while reloading " + src) } } - if (this.debug) { - console.log("Nodes reloaded") - } - }; + if (this.debug) console.log("Nodes reloaded") + } //separated just to improve if it doesn't work cloneObject(obj: T, target?: T): T { - if (obj == null) { - return null - } - var r = JSON.parse(JSON.stringify(obj)) - if (!target) { - return r - } + if (obj == null) return null - for (var i in r) { + const r = JSON.parse(JSON.stringify(obj)) + if (!target) return r + + for (const i in r) { target[i] = r[i] } return target - }; + } /* - * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670 - */ + * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670 + */ uuidv4(): string { // @ts-ignore return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, a => (a ^ Math.random() * 16 >> a / 4).toString(16)) - }; + } /** * Returns if the types of two slots are compatible (taking into account wildcards, etc) - * @method isValidConnection - * @param {String} type_a - * @param {String} type_b + * @param {String} type_a output + * @param {String} type_b input * @return {Boolean} true if they can be connected */ isValidConnection(type_a: ISlotType, type_b: ISlotType): boolean { if (type_a == "" || type_a === "*") type_a = 0 if (type_b == "" || type_b === "*") type_b = 0 - if (!type_a //generic output - || !type_b // generic input - || type_a == type_b //same type (is valid for triggers) - || (type_a == this.EVENT && type_b == this.ACTION)) { + // If generic in/output, matching types (valid for triggers), or event/action types + if (!type_a || !type_b || type_a == type_b || (type_a == this.EVENT && type_b == this.ACTION)) return true - } // Enforce string type to handle toLowerCase call (-1 number not ok) type_a = String(type_a) @@ -694,28 +592,24 @@ export class LiteGraphGlobal { type_b = type_b.toLowerCase() // For nodes supporting multiple connection types - if (type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1) { + if (type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1) return type_a == type_b - } // Check all permutations to see if one is valid - var supported_types_a = type_a.split(",") - var supported_types_b = type_b.split(",") - for (var i = 0; i < supported_types_a.length; ++i) { - for (var j = 0; j < supported_types_b.length; ++j) { - if (this.isValidConnection(supported_types_a[i], supported_types_b[j])) { - //if (supported_types_a[i] == supported_types_b[j]) { + const supported_types_a = type_a.split(",") + const supported_types_b = type_b.split(",") + for (let i = 0; i < supported_types_a.length; ++i) { + for (let j = 0; j < supported_types_b.length; ++j) { + if (this.isValidConnection(supported_types_a[i], supported_types_b[j])) return true - } } } return false - }; + } /** * Register a string in the search box so when the user types it it will recommend this node - * @method registerSearchboxExtra * @param {String} node_type the node recommended * @param {String} description text to show next to it * @param {Object} data it could contain info of how the node should be configured @@ -727,11 +621,10 @@ export class LiteGraphGlobal { desc: description, data: data } - }; + } /** * Wrapper to load files (from url using fetch or from file using FileReader) - * @method fetchFile * @param {String|File|Blob} url the url of the file (or the file itself) * @param {String} type an string to know how to fetch it: "text","arraybuffer","json","blob" * @param {Function} on_complete callback(data) @@ -739,15 +632,13 @@ export class LiteGraphGlobal { * @return {FileReader|Promise} returns the object used to */ fetchFile(url: string | URL | Request | Blob, type: string, on_complete: (data: string | ArrayBuffer) => void, on_error: (error: unknown) => void): void | Promise { - var that = this - if (!url) - return null + if (!url) return null type = type || "text" - if (url.constructor === String) { - if (url.substr(0, 4) == "http" && this.proxy) { + if (typeof url === "string") { + if (url.substr(0, 4) == "http" && this.proxy) url = this.proxy + url.substr(url.indexOf(":") + 3) - } + return fetch(url) .then(function (response) { if (!response.ok) @@ -761,25 +652,21 @@ export class LiteGraphGlobal { else if (type == "blob") return response.blob() }) - .then(function (data) { - if (on_complete) - on_complete(data) + .then(function (data: string | ArrayBuffer): void { + on_complete?.(data) }) .catch(function (error) { console.error("error fetching file:", url) - if (on_error) - on_error(error) + on_error?.(error) }) - } - else if (url.constructor === File || url.constructor === Blob) { - var reader = new FileReader() + } else if (url instanceof File || url instanceof Blob) { + const reader = new FileReader() reader.onload = function (e) { - var v = e.target.result + let v = e.target.result if (type == "json") // @ts-ignore v = JSON.parse(v) - if (on_complete) - on_complete(v) + on_complete?.(v) } if (type == "arraybuffer") return reader.readAsArrayBuffer(url) @@ -789,10 +676,10 @@ export class LiteGraphGlobal { return reader.readAsBinaryString(url) } return null - }; + } //used to create nodes from wrapping functions - getParameterNames(func: Function): string[] { + getParameterNames(func: (...args: any) => any): string[] { return (func + "") .replace(/[/][/].*$/gm, "") // strip single-line comments .replace(/\s+/g, "") // strip white space @@ -802,18 +689,15 @@ export class LiteGraphGlobal { .replace(/=[^,]+/g, "") // strip any ES6 defaults .split(",") .filter(Boolean) // split & filter [""] - }; + } /* helper for interaction: pointer, touch, mouse Listeners used by LGraphCanvas DragAndScale ContextMenu*/ pointerListenerAdd(oDOM: Node, sEvIn: string, fCall: (e: Event) => boolean | void, capture = false): void { - if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall !== "function") { - //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); - return // -- break -- - } + if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall !== "function") return - var sMethod = LiteGraph.pointerevents_method - var sEvent = sEvIn + let sMethod = LiteGraph.pointerevents_method + let sEvent = sEvIn // UNDER CONSTRUCTION // convert pointerevents to touch event when not available @@ -873,10 +757,8 @@ export class LiteGraphGlobal { } } pointerListenerRemove(oDOM: Node, sEvent: string, fCall: (e: Event) => boolean | void, capture = false): void { - if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall !== "function") { - //console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); - return // -- break -- - } + if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall !== "function") return + switch (sEvent) { // @ts-expect-error //both pointer and move events @@ -903,10 +785,8 @@ export class LiteGraphGlobal { getTime: () => number compareObjects(a: object, b: object): boolean { - for (var i in a) { - if (a[i] != b[i]) { - return false - } + for (const i in a) { + if (a[i] != b[i]) return false } return true } @@ -959,7 +839,6 @@ export class LiteGraphGlobal { return true } - //Convert a hex value to its decimal value - the inputted hex must be in the // format of a hex triplet - the kind we use for HTML colours. The function // will return an array with three values. @@ -968,11 +847,11 @@ export class LiteGraphGlobal { hex = hex.slice(1) } //Remove the '#' char - if there is one. hex = hex.toUpperCase() - var hex_alphabets = "0123456789ABCDEF" - var value = new Array(3) - var k = 0 - var int1, int2 - for (var i = 0; i < 6; i += 2) { + const hex_alphabets = "0123456789ABCDEF" + const value = new Array(3) + let k = 0 + let int1, int2 + for (let i = 0; i < 6; i += 2) { int1 = hex_alphabets.indexOf(hex.charAt(i)) int2 = hex_alphabets.indexOf(hex.charAt(i + 1)) value[k] = int1 * 16 + int2 @@ -984,10 +863,10 @@ export class LiteGraphGlobal { //Give a array with three values as the argument and the function will return // the corresponding hex triplet. num2hex(triplet: number[]): string { - var hex_alphabets = "0123456789ABCDEF" - var hex = "#" - var int1, int2 - for (var i = 0; i < 3; i++) { + const hex_alphabets = "0123456789ABCDEF" + let hex = "#" + let int1, int2 + for (let i = 0; i < 3; i++) { int1 = triplet[i] / 16 int2 = triplet[i] % 16 @@ -999,17 +878,15 @@ export class LiteGraphGlobal { closeAllContextMenus(ref_window: Window): void { ref_window = ref_window || window - var elements = ref_window.document.querySelectorAll(".litecontextmenu") - if (!elements.length) { - return - } + const elements = ref_window.document.querySelectorAll(".litecontextmenu") + if (!elements.length) return - var result = [] - for (var i = 0; i < elements.length; i++) { + const result = [] + for (let i = 0; i < elements.length; i++) { result.push(elements[i]) } - for (var i = 0; i < result.length; i++) { + for (let i = 0; i < result.length; i++) { if (result[i].close) { result[i].close() } else if (result[i].parentNode) { @@ -1019,26 +896,20 @@ export class LiteGraphGlobal { } extendClass(target: any, origin: any): void { - for (var i in origin) { + for (const i in origin) { //copy class properties - if (target.hasOwnProperty(i)) { - continue - } + if (target.hasOwnProperty(i)) continue target[i] = origin[i] } if (origin.prototype) { //copy prototype properties - for (var i in origin.prototype) { + for (const i in origin.prototype) { //only enumerable - if (!origin.prototype.hasOwnProperty(i)) { - continue - } + if (!origin.prototype.hasOwnProperty(i)) continue - if (target.prototype.hasOwnProperty(i)) { - //avoid overwriting existing ones - continue - } + //avoid overwriting existing ones + if (target.prototype.hasOwnProperty(i)) continue //copy getters if (origin.prototype.__lookupGetter__(i)) { diff --git a/src/types/globalEnums.ts b/src/types/globalEnums.ts index 3c317c0a1..f43a16c50 100644 --- a/src/types/globalEnums.ts +++ b/src/types/globalEnums.ts @@ -28,6 +28,7 @@ export enum LinkDirection { /** The path calculation that links follow */ export enum LinkRenderType { + HIDDEN_LINK = -1, /** Juts out from the input & output a little @see LinkDirection, then a straight line between them */ STRAIGHT_LINK = 0, /** 90° angles, clean and box-like */