From c13f148df9e7ade47d45e0f5889ff1bd9ff1d521 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Tue, 22 Aug 2023 21:46:20 -0500 Subject: [PATCH] Initial changes for litegraph.ts compat --- web/extensions/core/colorPalette.js | 5 +- web/extensions/core/contextMenuFilter.js | 5 +- web/extensions/core/invertMenuScrolling.js | 1 + web/extensions/core/linkRenderMode.js | 3 +- web/extensions/core/nodeTemplates.js | 1 + web/extensions/core/noteNode.js | 27 +- web/extensions/core/rerouteNode.js | 21 +- web/extensions/core/saveImageExtraOutput.js | 2 + web/extensions/core/slotDefaults.js | 2 + web/extensions/core/snapToGrid.js | 1 + web/extensions/core/widgetInputs.js | 24 +- web/index.html | 4 +- web/lib/litegraph.core.js | 21529 ++++++------------ web/lib/litegraph.extensions.js | 21 - web/scripts/app.js | 407 +- web/scripts/graph.js | 5 + web/scripts/graphCanvas.js | 236 + web/scripts/graphNode.js | 86 + web/scripts/nodeDef.js | 26 + web/scripts/utils.js | 3 + web/scripts/widgets.js | 16 +- 21 files changed, 7696 insertions(+), 14729 deletions(-) delete mode 100644 web/lib/litegraph.extensions.js create mode 100644 web/scripts/graph.js create mode 100644 web/scripts/graphCanvas.js create mode 100644 web/scripts/graphNode.js create mode 100644 web/scripts/nodeDef.js create mode 100644 web/scripts/utils.js diff --git a/web/extensions/core/colorPalette.js b/web/extensions/core/colorPalette.js index 3695b08e2..cae1df78d 100644 --- a/web/extensions/core/colorPalette.js +++ b/web/extensions/core/colorPalette.js @@ -1,5 +1,6 @@ import {app} from "../../scripts/app.js"; import {$el} from "../../scripts/ui.js"; +import { LiteGraph, LGraphCanvas } from "../../lib/litegraph.core.js" // Manage color palettes @@ -341,8 +342,8 @@ app.registerExtension({ if (colorPalette.colors) { // Sets the colors of node slots and links if (colorPalette.colors.node_slot) { - Object.assign(app.canvas.default_connection_color_byType, colorPalette.colors.node_slot); - Object.assign(LGraphCanvas.link_type_colors, colorPalette.colors.node_slot); + Object.assign(LGraphCanvas.DEFAULT_CONNECTION_COLORS_BY_TYPE, colorPalette.colors.node_slot); + Object.assign(app.canvas.link_type_colors, colorPalette.colors.node_slot); } // Sets the colors of the LiteGraph objects if (colorPalette.colors.litegraph_base) { diff --git a/web/extensions/core/contextMenuFilter.js b/web/extensions/core/contextMenuFilter.js index 152cd7043..d6fd98600 100644 --- a/web/extensions/core/contextMenuFilter.js +++ b/web/extensions/core/contextMenuFilter.js @@ -1,4 +1,5 @@ -import {app} from "../../scripts/app.js"; +import { app } from "../../scripts/app.js"; +import { LiteGraph } from "../../lib/litegraph.core.js" // Adds filtering to combo context menus @@ -141,7 +142,7 @@ const ext = { return ctx; }; - LiteGraph.ContextMenu.prototype = ctxMenu.prototype; + // LiteGraph.ContextMenu.prototype = ctxMenu.prototype; }, } diff --git a/web/extensions/core/invertMenuScrolling.js b/web/extensions/core/invertMenuScrolling.js index 98a1786ab..3b48efad0 100644 --- a/web/extensions/core/invertMenuScrolling.js +++ b/web/extensions/core/invertMenuScrolling.js @@ -1,4 +1,5 @@ import { app } from "../../scripts/app.js"; +import { LiteGraph } from "../../lib/litegraph.core.js" // Inverts the scrolling of context menus diff --git a/web/extensions/core/linkRenderMode.js b/web/extensions/core/linkRenderMode.js index 1e9091ec1..4d7304a88 100644 --- a/web/extensions/core/linkRenderMode.js +++ b/web/extensions/core/linkRenderMode.js @@ -1,4 +1,5 @@ import { app } from "../../scripts/app.js"; +import { LiteGraph, LINK_RENDER_MODE_NAMES } from "../../lib/litegraph.core.js" const id = "Comfy.LinkRenderMode"; const ext = { @@ -9,7 +10,7 @@ const ext = { name: "Link Render Mode", defaultValue: 2, type: "combo", - options: LiteGraph.LINK_RENDER_MODES.map((m, i) => ({ + options: LINK_RENDER_MODE_NAMES.map((m, i) => ({ value: i, text: m, selected: i == app.canvas.links_render_mode, diff --git a/web/extensions/core/nodeTemplates.js b/web/extensions/core/nodeTemplates.js index 7059f826d..f6ba71cf8 100644 --- a/web/extensions/core/nodeTemplates.js +++ b/web/extensions/core/nodeTemplates.js @@ -1,5 +1,6 @@ import { app } from "../../scripts/app.js"; import { ComfyDialog, $el } from "../../scripts/ui.js"; +import { LGraphCanvas } from "../../lib/litegraph.core.js" // Adds the ability to save and add multiple nodes as a template // To save: diff --git a/web/extensions/core/noteNode.js b/web/extensions/core/noteNode.js index 8d89054e9..0eeaa042d 100644 --- a/web/extensions/core/noteNode.js +++ b/web/extensions/core/noteNode.js @@ -1,11 +1,14 @@ -import {app} from "../../scripts/app.js"; -import {ComfyWidgets} from "../../scripts/widgets.js"; +import { app } from "../../scripts/app.js"; +import { ComfyWidgets } from "../../scripts/widgets.js"; +import { ComfyGraphNode } from "../../scripts/graphNode.js"; +import { LiteGraph } from "../../lib/litegraph.core.js" + // Node that add notes to your project app.registerExtension({ name: "Comfy.NoteNode", registerCustomNodes() { - class NoteNode { + class NoteNode extends ComfyGraphNode { color=LGraphCanvas.node_colors.yellow.color; bgcolor=LGraphCanvas.node_colors.yellow.bgcolor; groupcolor = LGraphCanvas.node_colors.yellow.groupcolor; @@ -19,22 +22,18 @@ app.registerExtension({ this.serialize_widgets = true; this.isVirtualNode = true; - } - - } // Load default visibility - LiteGraph.registerNodeType( - "Note", - Object.assign(NoteNode, { - title_mode: LiteGraph.NORMAL_TITLE, - title: "Note", - collapsable: true, - }) - ); + LiteGraph.registerNodeType({ + class: NoteNode, + title_mode: LiteGraph.NORMAL_TITLE, + type: "Note", + title: "Note", + collapsable: true, + }); NoteNode.category = "utils"; }, diff --git a/web/extensions/core/rerouteNode.js b/web/extensions/core/rerouteNode.js index 499a171da..7ccd1bb7e 100644 --- a/web/extensions/core/rerouteNode.js +++ b/web/extensions/core/rerouteNode.js @@ -1,11 +1,13 @@ import { app } from "../../scripts/app.js"; +import { ComfyGraphNode } from "../../scripts/graphNode.js"; +import { LiteGraph } from "../../lib/litegraph.core.js" // Node that allows you to redirect connections for cleaner graphs app.registerExtension({ name: "Comfy.RerouteNode", registerCustomNodes() { - class RerouteNode { + class RerouteNode extends ComfyGraphNode { constructor() { if (!this.properties) { this.properties = {}; @@ -109,7 +111,7 @@ app.registerExtension({ } const displayType = inputType || outputType || "*"; - const color = LGraphCanvas.link_type_colors[displayType]; + const color = app.canvas.link_type_colors[displayType]; // Update the types of each node for (const node of updateNodes) { @@ -219,14 +221,13 @@ app.registerExtension({ // Load default visibility RerouteNode.setDefaultTextVisibility(!!localStorage["Comfy.RerouteNode.DefaultVisibility"]); - LiteGraph.registerNodeType( - "Reroute", - Object.assign(RerouteNode, { - title_mode: LiteGraph.NO_TITLE, - title: "Reroute", - collapsable: false, - }) - ); + LiteGraph.registerNodeType({ + class: RerouteNode, + title_mode: LiteGraph.NO_TITLE, + type: "Reroute", + title: "Reroute", + collapsable: false, + }); RerouteNode.category = "utils"; }, diff --git a/web/extensions/core/saveImageExtraOutput.js b/web/extensions/core/saveImageExtraOutput.js index 99e2213bf..d0fe1ffcb 100644 --- a/web/extensions/core/saveImageExtraOutput.js +++ b/web/extensions/core/saveImageExtraOutput.js @@ -2,6 +2,7 @@ import { app } from "../../scripts/app.js"; // Use widget values and dates in output filenames +/* app.registerExtension({ name: "Comfy.SaveImageExtraOutput", async beforeRegisterNodeDef(nodeType, nodeData, app) { @@ -98,3 +99,4 @@ app.registerExtension({ } }, }); +*/ diff --git a/web/extensions/core/slotDefaults.js b/web/extensions/core/slotDefaults.js index 718d25405..72863e2eb 100644 --- a/web/extensions/core/slotDefaults.js +++ b/web/extensions/core/slotDefaults.js @@ -1,5 +1,7 @@ import { app } from "../../scripts/app.js"; import { ComfyWidgets } from "../../scripts/widgets.js"; +import { LiteGraph } from "../../lib/litegraph.core.js" + // Adds defaults for quickly adding nodes with middle click on the input/output app.registerExtension({ diff --git a/web/extensions/core/snapToGrid.js b/web/extensions/core/snapToGrid.js index dc534d6ed..4cd3c1263 100644 --- a/web/extensions/core/snapToGrid.js +++ b/web/extensions/core/snapToGrid.js @@ -1,4 +1,5 @@ import { app } from "../../scripts/app.js"; +import { LiteGraph } from "../../lib/litegraph.core.js" // Shift + drag/resize to snap to grid diff --git a/web/extensions/core/widgetInputs.js b/web/extensions/core/widgetInputs.js index d9eaf8a0c..2e30680e7 100644 --- a/web/extensions/core/widgetInputs.js +++ b/web/extensions/core/widgetInputs.js @@ -1,5 +1,6 @@ import { ComfyWidgets, addValueControlWidget } from "../../scripts/widgets.js"; import { app } from "../../scripts/app.js"; +import { LiteGraph } from "../../lib/litegraph.core.js" const CONVERTED_TYPE = "converted-widget"; const VALID_TYPES = ["STRING", "combo", "number", "BOOLEAN"]; @@ -95,8 +96,8 @@ app.registerExtension({ name: "Comfy.WidgetInputs", async beforeRegisterNodeDef(nodeType, nodeData, app) { // Add menu options to conver to/from widgets - const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; - nodeType.prototype.getExtraMenuOptions = function (_, options) { + const origGetExtraMenuOptions = nodeType.class.prototype.getExtraMenuOptions; + nodeType.class.prototype.getExtraMenuOptions = function (_, options) { const r = origGetExtraMenuOptions ? origGetExtraMenuOptions.apply(this, arguments) : undefined; if (this.widgets) { @@ -131,8 +132,8 @@ app.registerExtension({ }; // On initial configure of nodes hide all converted widgets - const origOnConfigure = nodeType.prototype.onConfigure; - nodeType.prototype.onConfigure = function () { + const origOnConfigure = nodeType.class.prototype.onConfigure; + nodeType.class.prototype.onConfigure = function () { const r = origOnConfigure ? origOnConfigure.apply(this, arguments) : undefined; if (this.inputs) { @@ -161,9 +162,9 @@ app.registerExtension({ } // Double click a widget input to automatically attach a primitive - const origOnInputDblClick = nodeType.prototype.onInputDblClick; + const origOnInputDblClick = nodeType.class.prototype.onInputDblClick; const ignoreDblClick = Symbol(); - nodeType.prototype.onInputDblClick = function (slot) { + nodeType.class.prototype.onInputDblClick = function (slot) { const r = origOnInputDblClick ? origOnInputDblClick.apply(this, arguments) : undefined; const input = this.inputs[slot]; @@ -403,12 +404,11 @@ app.registerExtension({ } } - LiteGraph.registerNodeType( - "PrimitiveNode", - Object.assign(PrimitiveNode, { - title: "Primitive", - }) - ); + LiteGraph.registerNodeType({ + class: PrimitiveNode, + type: "PrimitiveNode", + title: "Primitive", + }); PrimitiveNode.category = "utils"; }, }); diff --git a/web/index.html b/web/index.html index 41bc246c0..25682aeaf 100644 --- a/web/index.html +++ b/web/index.html @@ -7,12 +7,10 @@ - - diff --git a/web/lib/litegraph.core.js b/web/lib/litegraph.core.js index 356c71ac2..e74247d78 100644 --- a/web/lib/litegraph.core.js +++ b/web/lib/litegraph.core.js @@ -1,14377 +1,7246 @@ -//packer version - - -(function(global) { - // ************************************************************* - // LiteGraph CLASS ******* - // ************************************************************* - - /** - * The Global Scope. It contains all the registered node classes. - * - * @class LiteGraph - * @constructor - */ - - var LiteGraph = (global.LiteGraph = { - VERSION: 0.4, - - 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, - - WIDGET_BGCOLOR: "#222", - WIDGET_OUTLINE_COLOR: "#666", - WIDGET_TEXT_COLOR: "#DDD", - WIDGET_SECONDARY_TEXT_COLOR: "#999", - - LINK_COLOR: "#9A9", - EVENT_LINK_COLOR: "#A86", - CONNECTING_LINK_COLOR: "#AFA", - - MAX_NUMBER_OF_NODES: 1000, //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 - - //enums - INPUT: 1, - OUTPUT: 2, - - EVENT: -1, //for outputs - ACTION: -1, //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, - - UP: 1, - DOWN: 2, - LEFT: 3, - RIGHT: 4, - CENTER: 5, - - LINK_RENDER_MODES: ["Straight", "Linear", "Spline"], // helper - STRAIGHT_LINK: 0, - LINEAR_LINK: 1, - SPLINE_LINK: 2, - - NORMAL_TITLE: 0, - NO_TITLE: 1, - TRANSPARENT_TITLE: 2, - AUTOHIDE_TITLE: 3, - VERTICAL_LAYOUT: "vertical", // arrange nodes vertically - - proxy: null, //used to redirect calls - node_images_path: "", - - 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 - - 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 - - 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 - - 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, - - 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 - - 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 - slot_types_default_in: [], // 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: [], // 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 - - 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 - - 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) - // 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 - - // 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, - - /** - * 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: function(type, base_class) { - 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); - } - - const classname = base_class.name; - - const pos = type.lastIndexOf("/"); - base_class.category = type.substring(0, pos); - - if (!base_class.title) { - base_class.title = classname; - } - - //extend class - for (var i in LGraphNode.prototype) { - if (!base_class.prototype[i]) { - base_class.prototype[i] = LGraphNode.prototype[i]; - } - } - - const prev = this.registered_node_types[type]; - if(prev) { - console.log("replacing node type: " + type); - } - if( !Object.prototype.hasOwnProperty.call( base_class.prototype, "shape") ) { - Object.defineProperty(base_class.prototype, "shape", { - set: function(v) { - switch (v) { - case "default": - delete this._shape; - break; - case "box": - this._shape = LiteGraph.BOX_SHAPE; - break; - case "round": - this._shape = LiteGraph.ROUND_SHAPE; - break; - case "circle": - this._shape = LiteGraph.CIRCLE_SHAPE; - break; - case "card": - this._shape = LiteGraph.CARD_SHAPE; - break; - default: - this._shape = v; - } - }, - get: function() { - 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) { - const ext = base_class.supported_extensions[i]; - if(ext && ext.constructor === String) { - this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class; - } - } - } - } - - this.registered_node_types[type] = base_class; - if (base_class.constructor.name) { - this.Nodes[classname] = base_class; - } - if (LiteGraph.onNodeTypeRegistered) { - LiteGraph.onNodeTypeRegistered(type, base_class); - } - if (prev && LiteGraph.onNodeTypeReplaced) { - LiteGraph.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" - ); - } - - // 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"); - } - }, - - /** - * removes a node type from the system - * @method unregisterNodeType - * @param {String|Object} type name of the node or the node constructor itself - */ - unregisterNodeType: function(type) { - const base_class = - type.constructor === String - ? this.registered_node_types[type] - : 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]; - } - }, - - /** - * 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: function(type, slot_type, out){ - out = out || false; - const base_class = - type.constructor === String && - this.registered_node_types[type] !== "anonymous" - ? this.registered_node_types[type] - : type; - - const class_type = base_class.constructor.type; - - let allTypes = []; - if (typeof slot_type === "string") { - allTypes = slot_type.split(","); - } else if (slot_type == this.EVENT || slot_type == this.ACTION) { - allTypes = ["_event_"]; - } else { - allTypes = ["*"]; - } - - for (let i = 0; i < allTypes.length; ++i) { - let slotType = allTypes[i]; - if (slotType === "") { - slotType = "*"; - } - const registerTo = out - ? "registered_slot_out_types" - : "registered_slot_in_types"; - if (this[registerTo][slotType] === undefined) { - this[registerTo][slotType] = { nodes: [] }; - } - 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(); - } - } - } - }, - - /** - * 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 - * @param {String} return_type [optional] string with the return type, otherwise it will be generic - * @param {Object} properties [optional] properties to be configurable - */ - wrapFunctionAsNode: function( - name, - func, - param_types, - return_type, - properties - ) { - var params = Array(func.length); - var code = ""; - var names = LiteGraph.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"; - } - code += - "this.addOutput('out'," + - (return_type ? "'" + return_type + "'" : 0) + - ");\n"; - if (properties) { - code += - "this.properties = " + JSON.stringify(properties) + ";\n"; - } - var classobj = Function(code); - classobj.title = name.split("/").pop(); - classobj.desc = "Generated from " + func.name; - classobj.prototype.onExecute = function onExecute() { - for (var i = 0; i < params.length; ++i) { - params[i] = this.getInputData(i); - } - var r = func.apply(this, params); - this.setOutputData(0, r); - }; - this.registerNodeType(name, classobj); - }, - - /** - * Removes all previously registered node's types - */ - clearRegisteredTypes: function() { - this.registered_node_types = {}; - 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 - */ - addNodeMethod: function(name, func) { - 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 - 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: function(type, title, options) { - var base_class = this.registered_node_types[type]; - if (!base_class) { - if (LiteGraph.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; - - if (LiteGraph.catch_exceptions) { - try { - node = new base_class(title); - } catch (err) { - console.error(err); - return null; - } - } else { - node = new base_class(title); - } - - 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 = LiteGraph.DEFAULT_POSITION.concat(); - } - if (!node.mode) { - node.mode = LiteGraph.ALWAYS; - } - - //extra options - if (options) { - for (var i in options) { - node[i] = options[i]; - } - } - - // callback - if ( 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: function(type) { - 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: function(category, filter) { - var r = []; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if (type.filter != filter) { - continue; - } - - if (category == "") { - 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)}); - } - - 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: function( filter ) { - var categories = { "": 1 }; - for (var i in this.registered_node_types) { - var 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) { - result.push(i); - } - return this.auto_sort_node_types ? result.sort() : result; - }, - - //debug purposes: reloads all the js scripts that matches a wildcard - reloadNodes: function(folder_wildcard) { - var 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++) { - script_files.push(tmp[i]); - } - - var 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 - ) { - continue; - } - - try { - if (LiteGraph.debug) { - console.log("Reloading: " + src); - } - var dynamicScript = document.createElement("script"); - dynamicScript.type = "text/javascript"; - dynamicScript.src = src; - docHeadObj.appendChild(dynamicScript); - docHeadObj.removeChild(script_files[i]); - } catch (err) { - if (LiteGraph.throw_errors) { - throw err; - } - if (LiteGraph.debug) { - console.log("Error while reloading " + src); - } - } - } - - if (LiteGraph.debug) { - console.log("Nodes reloaded"); - } - }, - - //separated just to improve if it doesn't work - cloneObject: function(obj, target) { - if (obj == null) { - return null; - } - var r = JSON.parse(JSON.stringify(obj)); - if (!target) { - return r; - } - - for (var i in r) { - target[i] = r[i]; - } - return target; - }, - - /* - * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670 - */ - uuidv4: function() { - 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 - * @return {Boolean} true if they can be connected - */ - isValidConnection: function(type_a, type_b) { - 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 == LiteGraph.EVENT && type_b == LiteGraph.ACTION) - ) { - return true; - } - - // Enforce string type to handle toLowerCase call (-1 number not ok) - type_a = String(type_a); - type_b = String(type_b); - type_a = type_a.toLowerCase(); - type_b = type_b.toLowerCase(); - - // For nodes supporting multiple connection types - 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]) { - 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 - * @return {Boolean} true if they can be connected - */ - registerSearchboxExtra: function(node_type, description, data) { - this.searchbox_extras[description.toLowerCase()] = { - type: node_type, - 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) - * @param {Function} on_error in case of an error - * @return {FileReader|Promise} returns the object used to - */ - fetchFile: function( url, type, on_complete, on_error ) { - var that = this; - if(!url) - return null; - - type = type || "text"; - if( url.constructor === String ) - { - if (url.substr(0, 4) == "http" && LiteGraph.proxy) { - url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); - } - return fetch(url) - .then(function(response) { - if(!response.ok) - throw new Error("File not found"); //it will be catch below - if(type == "arraybuffer") - return response.arrayBuffer(); - else if(type == "text" || type == "string") - return response.text(); - else if(type == "json") - return response.json(); - else if(type == "blob") - return response.blob(); - }) - .then(function(data) { - if(on_complete) - on_complete(data); - }) - .catch(function(error) { - console.error("error fetching file:",url); - if(on_error) - on_error(error); - }); - } - else if( url.constructor === File || url.constructor === Blob) - { - var reader = new FileReader(); - reader.onload = function(e) - { - var v = e.target.result; - if( type == "json" ) - v = JSON.parse(v); - if(on_complete) - on_complete(v); - } - if(type == "arraybuffer") - return reader.readAsArrayBuffer(url); - else if(type == "text" || type == "json") - return reader.readAsText(url); - else if(type == "blob") - return reader.readAsBinaryString(url); - } - return null; - } - }); - - //timer that works everywhere - if (typeof performance != "undefined") { - LiteGraph.getTime = performance.now.bind(performance); - } else if (typeof Date != "undefined" && Date.now) { - LiteGraph.getTime = Date.now.bind(Date); - } else if (typeof process != "undefined") { - LiteGraph.getTime = function() { - var t = process.hrtime(); - return t[0] * 0.001 + t[1] * 1e-6; - }; - } else { - LiteGraph.getTime = function getTime() { - return new Date().getTime(); - }; - } - - //********************************************************************************* - // LGraph CLASS - //********************************************************************************* - - /** - * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. - * supported callbacks: - + onNodeAdded: when a new node is added to the graph - + onNodeRemoved: when a node inside this graph is removed - + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected) - * - * @class LGraph - * @constructor - * @param {Object} o data from previous serialization [optional] - */ - - function LGraph(o) { - if (LiteGraph.debug) { - console.log("Graph created"); - } - this.list_of_graphcanvas = null; - this.clear(); - - if (o) { - this.configure(o); - } - } - - global.LGraph = LiteGraph.LGraph = LGraph; - - //default supported types - LGraph.supported_types = ["number", "string", "boolean"]; - - //used to know which types of connections support this graph (some graphs do not allow certain types) - LGraph.prototype.getSupportedTypes = function() { - return this.supported_types || LGraph.supported_types; - }; - - LGraph.STATUS_STOPPED = 1; - LGraph.STATUS_RUNNING = 2; - - /** - * Removes all nodes from this graph - * @method clear - */ - - LGraph.prototype.clear = function() { - this.stop(); - this.status = LGraph.STATUS_STOPPED; - - this.last_node_id = 0; - this.last_link_id = 0; - - this._version = -1; //used to detect changes - - //safe clear - if (this._nodes) { - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - if (node.onRemoved) { - node.onRemoved(); - } - } - } - - //nodes - this._nodes = []; - this._nodes_by_id = {}; - this._nodes_in_order = []; //nodes sorted in execution order - this._nodes_executable = null; //nodes that contain onExecute sorted in execution order - - //other scene stuff - this._groups = []; - - //links - this.links = {}; //container with all the links - - //iterations - this.iteration = 0; - - //custom data - this.config = {}; - this.vars = {}; - this.extra = {}; //to store custom data - - //timing - this.globaltime = 0; - this.runningtime = 0; - this.fixedtime = 0; - this.fixedtime_lapse = 0.01; - this.elapsed_time = 0.01; - this.last_update_time = 0; - this.starttime = 0; - - this.catch_errors = true; - - this.nodes_executing = []; - this.nodes_actioning = []; - this.nodes_executedAction = []; - - //subgraph_data - this.inputs = {}; - this.outputs = {}; - - //notify canvas to redraw - this.change(); - - this.sendActionToCanvas("clear"); - }; - - /** - * Attach Canvas to this graph - * @method attachCanvas - * @param {GraphCanvas} graph_canvas - */ - - LGraph.prototype.attachCanvas = function(graphcanvas) { - if (graphcanvas.constructor != LGraphCanvas) { - throw "attachCanvas expects a LGraphCanvas instance"; - } - if (graphcanvas.graph && graphcanvas.graph != this) { - graphcanvas.graph.detachCanvas(graphcanvas); - } - - graphcanvas.graph = this; - - if (!this.list_of_graphcanvas) { - this.list_of_graphcanvas = []; - } - this.list_of_graphcanvas.push(graphcanvas); - }; - - /** - * Detach Canvas from this graph - * @method detachCanvas - * @param {GraphCanvas} graph_canvas - */ - LGraph.prototype.detachCanvas = function(graphcanvas) { - if (!this.list_of_graphcanvas) { - return; - } - - var pos = this.list_of_graphcanvas.indexOf(graphcanvas); - if (pos == -1) { - return; - } - graphcanvas.graph = null; - this.list_of_graphcanvas.splice(pos, 1); - }; - - /** - * Starts running this graph every interval milliseconds. - * @method start - * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate - */ - - LGraph.prototype.start = function(interval) { - if (this.status == LGraph.STATUS_RUNNING) { - return; - } - this.status = LGraph.STATUS_RUNNING; - - if (this.onPlayEvent) { - this.onPlayEvent(); - } - - this.sendEventToAllNodes("onStart"); - - //launch - this.starttime = LiteGraph.getTime(); - this.last_update_time = this.starttime; - interval = interval || 0; - var that = this; - - //execute once per frame - if ( interval == 0 && typeof window != "undefined" && window.requestAnimationFrame ) { - function on_frame() { - if (that.execution_timer_id != -1) { - return; - } - window.requestAnimationFrame(on_frame); - if(that.onBeforeStep) - that.onBeforeStep(); - that.runStep(1, !that.catch_errors); - if(that.onAfterStep) - that.onAfterStep(); - } - this.execution_timer_id = -1; - on_frame(); - } else { //execute every 'interval' ms - this.execution_timer_id = setInterval(function() { - //execute - if(that.onBeforeStep) - that.onBeforeStep(); - that.runStep(1, !that.catch_errors); - if(that.onAfterStep) - that.onAfterStep(); - }, interval); - } - }; - - /** - * Stops the execution loop of the graph - * @method stop execution - */ - - LGraph.prototype.stop = function() { - if (this.status == LGraph.STATUS_STOPPED) { - return; - } - - this.status = LGraph.STATUS_STOPPED; - - if (this.onStopEvent) { - this.onStopEvent(); - } - - if (this.execution_timer_id != null) { - if (this.execution_timer_id != -1) { - clearInterval(this.execution_timer_id); - } - this.execution_timer_id = null; - } - - this.sendEventToAllNodes("onStop"); - }; - - /** - * Run N steps (cycles) of the graph - * @method runStep - * @param {number} num number of steps to run, default is 1 - * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors - * @param {number} limit max number of nodes to execute (used to execute from start to a node) - */ - - LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) { - num = num || 1; - - var start = LiteGraph.getTime(); - this.globaltime = 0.001 * (start - this.starttime); - - var nodes = this._nodes_executable - ? this._nodes_executable - : this._nodes; - if (!nodes) { - return; - } - - limit = limit || nodes.length; - - if (do_not_catch_errors) { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - //wrap node.onExecute(); - node.doExecute(); - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - } else { - try { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - node.onExecute(); - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - this.errors_in_execution = false; - } catch (err) { - this.errors_in_execution = true; - if (LiteGraph.throw_errors) { - throw err; - } - if (LiteGraph.debug) { - console.log("Error during execution: " + err); - } - this.stop(); - } - } - - var now = LiteGraph.getTime(); - var elapsed = now - start; - if (elapsed == 0) { - elapsed = 1; - } - this.execution_time = 0.001 * elapsed; - this.globaltime += 0.001 * elapsed; - this.iteration += 1; - this.elapsed_time = (now - this.last_update_time) * 0.001; - this.last_update_time = now; - this.nodes_executing = []; - this.nodes_actioning = []; - this.nodes_executedAction = []; - }; - - /** - * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than - * nodes with only inputs. - * @method updateExecutionOrder - */ - LGraph.prototype.updateExecutionOrder = function() { - this._nodes_in_order = this.computeExecutionOrder(false); - this._nodes_executable = []; - for (var i = 0; i < this._nodes_in_order.length; ++i) { - if (this._nodes_in_order[i].onExecute) { - this._nodes_executable.push(this._nodes_in_order[i]); - } - } - }; - - //This is more internal, it computes the executable nodes in order and returns it - LGraph.prototype.computeExecutionOrder = function( - only_onExecute, - set_level - ) { - var L = []; - var S = []; - var M = {}; - var visited_links = {}; //to avoid repeating links - var remaining_links = {}; //to a - - //search for the nodes without inputs (starting nodes) - for (var i = 0, l = this._nodes.length; i < l; ++i) { - var node = this._nodes[i]; - if (only_onExecute && !node.onExecute) { - continue; - } - - M[node.id] = node; //add to pending nodes - - var num = 0; //num of input connections - if (node.inputs) { - for (var j = 0, l2 = node.inputs.length; j < l2; j++) { - if (node.inputs[j] && node.inputs[j].link != null) { - num += 1; - } - } - } - - if (num == 0) { - //is a starting node - S.push(node); - if (set_level) { - node._level = 1; - } - } //num of input links - else { - if (set_level) { - node._level = 0; - } - remaining_links[node.id] = num; - } - } - - while (true) { - if (S.length == 0) { - break; - } - - //get an starting node - var node = S.shift(); - L.push(node); //add to ordered list - delete M[node.id]; //remove from the pending nodes - - if (!node.outputs) { - continue; - } - - //for every output - for (var i = 0; i < node.outputs.length; i++) { - var output = node.outputs[i]; - //not connected - if ( - output == null || - output.links == null || - output.links.length == 0 - ) { - continue; - } - - //for every connection - for (var j = 0; j < output.links.length; j++) { - var link_id = output.links[j]; - var link = this.links[link_id]; - if (!link) { - continue; - } - - //already visited link (ignore it) - if (visited_links[link.id]) { - continue; - } - - var target_node = this.getNodeById(link.target_id); - if (target_node == null) { - visited_links[link.id] = true; - continue; - } - - if ( - set_level && - (!target_node._level || - target_node._level <= node._level) - ) { - target_node._level = node._level + 1; - } - - visited_links[link.id] = true; //mark as visited - remaining_links[target_node.id] -= 1; //reduce the number of links remaining - if (remaining_links[target_node.id] == 0) { - S.push(target_node); - } //if no more links, then add to starters array - } - } - } - - //the remaining ones (loops) - for (var i in M) { - L.push(M[i]); - } - - if (L.length != this._nodes.length && LiteGraph.debug) { - console.warn("something went wrong, nodes missing"); - } - - var l = L.length; - - //save order number in the node - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - //sort now by priority - L = L.sort(function(A, B) { - var Ap = A.constructor.priority || A.priority || 0; - var Bp = B.constructor.priority || B.priority || 0; - if (Ap == Bp) { - //if same priority, sort by order - return A.order - B.order; - } - return Ap - Bp; //sort by priority - }); - - //save order number in the node, again... - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - return L; - }; - - /** - * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. - * It doesn't include the node itself - * @method getAncestors - * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution - */ - LGraph.prototype.getAncestors = function(node) { - var ancestors = []; - var pending = [node]; - var visited = {}; - - while (pending.length) { - var current = pending.shift(); - if (!current.inputs) { - continue; - } - if (!visited[current.id] && current != node) { - visited[current.id] = true; - ancestors.push(current); - } - - for (var i = 0; i < current.inputs.length; ++i) { - var input = current.getInputNode(i); - if (input && ancestors.indexOf(input) == -1) { - pending.push(input); - } - } - } - - ancestors.sort(function(a, b) { - return a.order - b.order; - }); - return ancestors; - }; - - /** - * Positions every node in a more readable manner - * @method arrange - */ - LGraph.prototype.arrange = function (margin, layout) { - margin = margin || 100; - - const nodes = this.computeExecutionOrder(false, true); - const columns = []; - for (let i = 0; i < nodes.length; ++i) { - const node = nodes[i]; - const col = node._level || 1; - if (!columns[col]) { - columns[col] = []; - } - columns[col].push(node); - } - - let x = margin; - - for (let i = 0; i < columns.length; ++i) { - const column = columns[i]; - if (!column) { - continue; - } - let max_size = 100; - let y = margin + LiteGraph.NODE_TITLE_HEIGHT; - for (let j = 0; j < column.length; ++j) { - const node = column[j]; - node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x; - node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y; - const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0; - if (node.size[max_size_index] > max_size) { - max_size = node.size[max_size_index]; - } - const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1; - y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT; - } - x += max_size + margin; - } - - this.setDirtyCanvas(true, true); - }; - - /** - * Returns the amount of time the graph has been running in milliseconds - * @method getTime - * @return {number} number of milliseconds the graph has been running - */ - LGraph.prototype.getTime = function() { - return this.globaltime; - }; - - /** - * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant - * @method getFixedTime - * @return {number} number of milliseconds the graph has been running - */ - - LGraph.prototype.getFixedTime = function() { - return this.fixedtime; - }; - - /** - * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct - * if the nodes are using graphical actions - * @method getElapsedTime - * @return {number} number of milliseconds it took the last cycle - */ - - LGraph.prototype.getElapsedTime = function() { - return this.elapsed_time; - }; - - /** - * Sends an event to all the nodes, useful to trigger stuff - * @method sendEventToAllNodes - * @param {String} eventname the name of the event (function to be called) - * @param {Array} params parameters in array format - */ - LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) { - mode = mode || LiteGraph.ALWAYS; - - var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; - if (!nodes) { - return; - } - - for (var j = 0, l = nodes.length; j < l; ++j) { - var node = nodes[j]; - - if ( - node.constructor === LiteGraph.Subgraph && - eventname != "onExecute" - ) { - if (node.mode == mode) { - node.sendEventToAllNodes(eventname, params, mode); - } - continue; - } - - if (!node[eventname] || node.mode != mode) { - continue; - } - if (params === undefined) { - node[eventname](); - } else if (params && params.constructor === Array) { - node[eventname].apply(node, params); - } else { - node[eventname](params); - } - } - }; - - LGraph.prototype.sendActionToCanvas = function(action, params) { - if (!this.list_of_graphcanvas) { - return; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c[action]) { - c[action].apply(c, params); - } - } - }; - - /** - * Adds a new node instance to this graph - * @method add - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.add = function(node, skip_compute_order) { - if (!node) { - return; - } - - //groups - if (node.constructor === LGraphGroup) { - this._groups.push(node); - this.setDirtyCanvas(true); - this.change(); - node.graph = this; - this._version++; - return; - } - - //nodes - if (node.id != -1 && this._nodes_by_id[node.id] != null) { - console.warn( - "LiteGraph: there is already a node with this ID, changing it" - ); - if (LiteGraph.use_uuids) { - node.id = LiteGraph.uuidv4(); - } - else { - node.id = ++this.last_node_id; - } - } - - if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) { - throw "LiteGraph: max number of nodes in a graph reached"; - } - - //give him an id - if (LiteGraph.use_uuids) { - if (node.id == null || node.id == -1) - node.id = LiteGraph.uuidv4(); - } - else { - if (node.id == null || node.id == -1) { - node.id = ++this.last_node_id; - } else if (this.last_node_id < node.id) { - this.last_node_id = node.id; - } - } - - node.graph = this; - this._version++; - - this._nodes.push(node); - this._nodes_by_id[node.id] = node; - - if (node.onAdded) { - node.onAdded(this); - } - - if (this.config.align_to_grid) { - node.alignToGrid(); - } - - if (!skip_compute_order) { - this.updateExecutionOrder(); - } - - if (this.onNodeAdded) { - this.onNodeAdded(node); - } - - this.setDirtyCanvas(true); - this.change(); - - return node; //to chain actions - }; - - /** - * Removes a node from the graph - * @method remove - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.remove = function(node) { - if (node.constructor === LiteGraph.LGraphGroup) { - var index = this._groups.indexOf(node); - if (index != -1) { - this._groups.splice(index, 1); - } - node.graph = null; - this._version++; - this.setDirtyCanvas(true, true); - this.change(); - return; - } - - if (this._nodes_by_id[node.id] == null) { - return; - } //not found - - if (node.ignore_remove) { - return; - } //cannot be removed - - this.beforeChange(); //sure? - almost sure is wrong - - //disconnect inputs - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link != null) { - node.disconnectInput(i); - } - } - } - - //disconnect outputs - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (slot.links != null && slot.links.length) { - node.disconnectOutput(i); - } - } - } - - //node.id = -1; //why? - - //callback - if (node.onRemoved) { - node.onRemoved(); - } - - node.graph = null; - this._version++; - - //remove from canvas render - if (this.list_of_graphcanvas) { - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var canvas = this.list_of_graphcanvas[i]; - if (canvas.selected_nodes[node.id]) { - delete canvas.selected_nodes[node.id]; - } - if (canvas.node_dragged == node) { - canvas.node_dragged = null; - } - } - } - - //remove from containers - var pos = this._nodes.indexOf(node); - if (pos != -1) { - this._nodes.splice(pos, 1); - } - delete this._nodes_by_id[node.id]; - - if (this.onNodeRemoved) { - this.onNodeRemoved(node); - } - - //close panels - this.sendActionToCanvas("checkPanels"); - - this.setDirtyCanvas(true, true); - this.afterChange(); //sure? - almost sure is wrong - this.change(); - - this.updateExecutionOrder(); - }; - - /** - * Returns a node by its id. - * @method getNodeById - * @param {Number} id - */ - - LGraph.prototype.getNodeById = function(id) { - if (id == null) { - return null; - } - return this._nodes_by_id[id]; - }; - - /** - * Returns a list of nodes that matches a class - * @method findNodesByClass - * @param {Class} classObject the class itself (not an string) - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByClass = function(classObject, result) { - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].constructor === classObject) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns a list of nodes that matches a type - * @method findNodesByType - * @param {String} type the name of the node type - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByType = function(type, result) { - var type = type.toLowerCase(); - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].type.toLowerCase() == type) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the first node that matches a name in its title - * @method findNodeByTitle - * @param {String} name the name of the node to search - * @return {Node} the node or null - */ - LGraph.prototype.findNodeByTitle = function(title) { - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - return this._nodes[i]; - } - } - return null; - }; - - /** - * Returns a list of nodes that matches a name - * @method findNodesByTitle - * @param {String} name the name of the node to search - * @return {Array} a list with all the nodes with this name - */ - LGraph.prototype.findNodesByTitle = function(title) { - var result = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the top-most node in this position of the canvas - * @method getNodeOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph - * @return {LGraphNode} the node at this position or null - */ - LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) { - nodes_list = nodes_list || this._nodes; - var nRet = null; - for (var i = nodes_list.length - 1; i >= 0; i--) { - var n = nodes_list[i]; - var skip_title = n.constructor.title_mode == LiteGraph.NO_TITLE; - if (n.isPointInside(x, y, margin, skip_title)) { - // check for lesser interest nodes (TODO check for overlapping, use the top) - /*if (typeof n == "LGraphGroup"){ - nRet = n; - }else{*/ - return n; - /*}*/ - } - } - return nRet; - }; - - /** - * Returns the top-most group in that position - * @method getGroupOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @return {LGraphGroup} the group or null - */ - LGraph.prototype.getGroupOnPos = function(x, y) { - for (var i = this._groups.length - 1; i >= 0; i--) { - var g = this._groups[i]; - if (g.isPointInside(x, y, 2, true)) { - return g; - } - } - return null; - }; - - /** - * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution - * this replaces the ones using the old version with the new version - * @method checkNodeTypes - */ - LGraph.prototype.checkNodeTypes = function() { - var changes = false; - for (var i = 0; i < this._nodes.length; i++) { - var node = this._nodes[i]; - var ctor = LiteGraph.registered_node_types[node.type]; - if (node.constructor == ctor) { - continue; - } - console.log("node being replaced by newer version: " + node.type); - var newnode = LiteGraph.createNode(node.type); - changes = true; - this._nodes[i] = newnode; - newnode.configure(node.serialize()); - newnode.graph = this; - this._nodes_by_id[newnode.id] = newnode; - if (node.inputs) { - newnode.inputs = node.inputs.concat(); - } - if (node.outputs) { - newnode.outputs = node.outputs.concat(); - } - } - this.updateExecutionOrder(); - }; - - // ********** GLOBALS ***************** - - LGraph.prototype.onAction = function(action, param, options) { - this._input_nodes = this.findNodesByClass( - LiteGraph.GraphInput, - this._input_nodes - ); - for (var i = 0; i < this._input_nodes.length; ++i) { - var node = this._input_nodes[i]; - if (node.properties.name != action) { - continue; - } - //wrap node.onAction(action, param); - node.actionDo(action, param, options); - break; - } - }; - - LGraph.prototype.trigger = function(action, param) { - if (this.onTrigger) { - this.onTrigger(action, param); - } - }; - - /** - * Tell this graph it has a global graph input of this type - * @method addGlobalInput - * @param {String} name - * @param {String} type - * @param {*} value [optional] - */ - LGraph.prototype.addInput = function(name, type, value) { - var input = this.inputs[name]; - if (input) { - //already exist - return; - } - - this.beforeChange(); - this.inputs[name] = { name: name, type: type, value: value }; - this._version++; - this.afterChange(); - - if (this.onInputAdded) { - this.onInputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global graph input - * @method setGlobalInputData - * @param {String} name - * @param {*} data - */ - LGraph.prototype.setInputData = function(name, data) { - var input = this.inputs[name]; - if (!input) { - return; - } - input.value = data; - }; - - /** - * Returns the current value of a global graph input - * @method getInputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getInputData = function(name) { - var input = this.inputs[name]; - if (!input) { - return null; - } - return input.value; - }; - - /** - * Changes the name of a global graph input - * @method renameInput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameInput = function(old_name, name) { - if (name == old_name) { - return; - } - - if (!this.inputs[old_name]) { - return false; - } - - if (this.inputs[name]) { - console.error("there is already one input with that name"); - return false; - } - - this.inputs[name] = this.inputs[old_name]; - delete this.inputs[old_name]; - this._version++; - - if (this.onInputRenamed) { - this.onInputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph input - * @method changeInputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeInputType = function(name, type) { - if (!this.inputs[name]) { - return false; - } - - if ( - this.inputs[name].type && - String(this.inputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.inputs[name].type = type; - this._version++; - if (this.onInputTypeChanged) { - this.onInputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph input - * @method removeInput - * @param {String} name - * @param {String} type - */ - LGraph.prototype.removeInput = function(name) { - if (!this.inputs[name]) { - return false; - } - - delete this.inputs[name]; - this._version++; - - if (this.onInputRemoved) { - this.onInputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - /** - * Creates a global graph output - * @method addOutput - * @param {String} name - * @param {String} type - * @param {*} value - */ - LGraph.prototype.addOutput = function(name, type, value) { - this.outputs[name] = { name: name, type: type, value: value }; - this._version++; - - if (this.onOutputAdded) { - this.onOutputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global output - * @method setOutputData - * @param {String} name - * @param {String} value - */ - LGraph.prototype.setOutputData = function(name, value) { - var output = this.outputs[name]; - if (!output) { - return; - } - output.value = value; - }; - - /** - * Returns the current value of a global graph output - * @method getOutputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getOutputData = function(name) { - var output = this.outputs[name]; - if (!output) { - return null; - } - return output.value; - }; - - /** - * Renames a global graph output - * @method renameOutput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameOutput = function(old_name, name) { - if (!this.outputs[old_name]) { - return false; - } - - if (this.outputs[name]) { - console.error("there is already one output with that name"); - return false; - } - - this.outputs[name] = this.outputs[old_name]; - delete this.outputs[old_name]; - this._version++; - - if (this.onOutputRenamed) { - this.onOutputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph output - * @method changeOutputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeOutputType = function(name, type) { - if (!this.outputs[name]) { - return false; - } - - if ( - this.outputs[name].type && - String(this.outputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.outputs[name].type = type; - this._version++; - if (this.onOutputTypeChanged) { - this.onOutputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph output - * @method removeOutput - * @param {String} name - */ - LGraph.prototype.removeOutput = function(name) { - if (!this.outputs[name]) { - return false; - } - delete this.outputs[name]; - this._version++; - - if (this.onOutputRemoved) { - this.onOutputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - LGraph.prototype.triggerInput = function(name, value) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].onTrigger(value); - } - }; - - LGraph.prototype.setCallback = function(name, func) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].setTrigger(func); - } - }; - - //used for undo, called before any change is made to the graph - LGraph.prototype.beforeChange = function(info) { - if (this.onBeforeChange) { - this.onBeforeChange(this,info); - } - this.sendActionToCanvas("onBeforeChange", this); - }; - - //used to resend actions, called after any change is made to the graph - LGraph.prototype.afterChange = function(info) { - if (this.onAfterChange) { - this.onAfterChange(this,info); - } - this.sendActionToCanvas("onAfterChange", this); - }; - - LGraph.prototype.connectionChange = function(node, link_info) { - this.updateExecutionOrder(); - if (this.onConnectionChange) { - this.onConnectionChange(node); - } - this._version++; - this.sendActionToCanvas("onConnectionChange"); - }; - - /** - * returns if the graph is in live mode - * @method isLive - */ - - LGraph.prototype.isLive = function() { - if (!this.list_of_graphcanvas) { - return false; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c.live_mode) { - return true; - } - } - return false; - }; - - /** - * clears the triggered slot animation in all links (stop visual animation) - * @method clearTriggeredSlots - */ - LGraph.prototype.clearTriggeredSlots = function() { - for (var i in this.links) { - var link_info = this.links[i]; - if (!link_info) { - continue; - } - if (link_info._last_time) { - link_info._last_time = 0; - } - } - }; - - /* Called when something visually changed (not the graph!) */ - LGraph.prototype.change = function() { - if (LiteGraph.debug) { - console.log("Graph changed"); - } - this.sendActionToCanvas("setDirty", [true, true]); - if (this.on_change) { - this.on_change(this); - } - }; - - LGraph.prototype.setDirtyCanvas = function(fg, bg) { - this.sendActionToCanvas("setDirty", [fg, bg]); - }; - - /** - * Destroys a link - * @method removeLink - * @param {Number} link_id - */ - LGraph.prototype.removeLink = function(link_id) { - var link = this.links[link_id]; - if (!link) { - return; - } - var node = this.getNodeById(link.target_id); - if (node) { - node.disconnectInput(link.target_slot); - } - }; - - //save and recover app state *************************************** - /** - * Creates a Object containing all the info about this graph, it can be serialized - * @method serialize - * @return {Object} value of the node - */ - LGraph.prototype.serialize = function() { - var nodes_info = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - nodes_info.push(this._nodes[i].serialize()); - } - - //pack link info into a non-verbose format - var links = []; - for (var i in this.links) { - //links is an OBJECT - var link = this.links[i]; - if (!link.serialize) { - //weird bug I havent solved yet - console.warn( - "weird LLink bug, link info is not a LLink but a regular object" - ); - var link2 = new LLink(); - for (var j in link) { - link2[j] = link[j]; - } - this.links[i] = link2; - link = link2; - } - - links.push(link.serialize()); - } - - var groups_info = []; - for (var i = 0; i < this._groups.length; ++i) { - groups_info.push(this._groups[i].serialize()); - } - - var data = { - last_node_id: this.last_node_id, - last_link_id: this.last_link_id, - nodes: nodes_info, - links: links, - groups: groups_info, - config: this.config, - extra: this.extra, - version: LiteGraph.VERSION - }; - - if(this.onSerialize) - this.onSerialize(data); - - return data; - }; - - /** - * Configure a graph from a JSON string - * @method configure - * @param {String} str configure a graph from a JSON string - * @param {Boolean} returns if there was any error parsing - */ - LGraph.prototype.configure = function(data, keep_old) { - if (!data) { - return; - } - - if (!keep_old) { - this.clear(); - } - - var nodes = data.nodes; - - //decode links info (they are very verbose) - if (data.links && data.links.constructor === Array) { - var links = []; - for (var i = 0; i < data.links.length; ++i) { - var link_data = data.links[i]; - if(!link_data) //weird bug - { - console.warn("serialized graph link data contains errors, skipping."); - continue; - } - var link = new LLink(); - link.configure(link_data); - links[link.id] = link; - } - data.links = links; - } - - //copy all stored fields - for (var i in data) { - if(i == "nodes" || i == "groups" ) //links must be accepted - continue; - this[i] = data[i]; - } - - var error = false; - - //create nodes - this._nodes = []; - if (nodes) { - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; //stored info - var node = LiteGraph.createNode(n_info.type, n_info.title); - if (!node) { - if (LiteGraph.debug) { - console.log( - "Node not found or has errors: " + n_info.type - ); - } - - //in case of error we create a replacement node to avoid losing info - node = new LGraphNode(); - node.last_serialization = n_info; - node.has_errors = true; - error = true; - //continue; - } - - node.id = n_info.id; //id it or it will create a new id - this.add(node, true); //add before configure, otherwise configure cannot create links - } - - //configure nodes afterwards so they can reach each other - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; - var node = this.getNodeById(n_info.id); - if (node) { - node.configure(n_info); - } - } - } - - //groups - this._groups.length = 0; - if (data.groups) { - for (var i = 0; i < data.groups.length; ++i) { - var group = new LiteGraph.LGraphGroup(); - group.configure(data.groups[i]); - this.add(group); - } - } - - this.updateExecutionOrder(); - - this.extra = data.extra || {}; - - if(this.onConfigure) - this.onConfigure(data); - - this._version++; - this.setDirtyCanvas(true, true); - return error; - }; - - LGraph.prototype.load = function(url, callback) { - var that = this; - - //from file - if(url.constructor === File || url.constructor === Blob) - { - var reader = new FileReader(); - reader.addEventListener('load', function(event) { - var data = JSON.parse(event.target.result); - that.configure(data); - if(callback) - callback(); - }); - - reader.readAsText(url); - return; - } - - //is a string, then an URL - var req = new XMLHttpRequest(); - req.open("GET", url, true); - req.send(null); - req.onload = function(oEvent) { - if (req.status !== 200) { - console.error("Error loading graph:", req.status, req.response); - return; - } - var data = JSON.parse( req.response ); - that.configure(data); - if(callback) - callback(); - }; - req.onerror = function(err) { - console.error("Error loading graph:", err); - }; - }; - - LGraph.prototype.onNodeTrace = function(node, msg, color) { - //TODO - }; - - //this is the class in charge of storing link information - function LLink(id, type, origin_id, origin_slot, target_id, target_slot) { - this.id = id; - this.type = type; - this.origin_id = origin_id; - this.origin_slot = origin_slot; - this.target_id = target_id; - this.target_slot = target_slot; - - this._data = null; - this._pos = new Float32Array(2); //center - } - - LLink.prototype.configure = function(o) { - if (o.constructor === Array) { - this.id = o[0]; - this.origin_id = o[1]; - this.origin_slot = o[2]; - this.target_id = o[3]; - this.target_slot = o[4]; - this.type = o[5]; - } else { - this.id = o.id; - this.type = o.type; - this.origin_id = o.origin_id; - this.origin_slot = o.origin_slot; - this.target_id = o.target_id; - this.target_slot = o.target_slot; - } - }; - - LLink.prototype.serialize = function() { - return [ - this.id, - this.origin_id, - this.origin_slot, - this.target_id, - this.target_slot, - this.type - ]; - }; - - LiteGraph.LLink = LLink; - - // ************************************************************* - // Node CLASS ******* - // ************************************************************* - - /* - title: string - pos: [x,y] - size: [x,y] - - input|output: every connection - + { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array }); - - general properties: - + clip_area: if you render outside the node, it will be clipped - + unsafe_execution: not allowed for safe execution - + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected - + resizable: if set to false it wont be resizable with the mouse - + horizontal: slots are distributed horizontally - + widgets_start_y: widgets start at y distance from the top of the node - - flags object: - + collapsed: if it is collapsed - - supported callbacks: - + onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading) - + onRemoved: when removed from graph - + onStart: when the graph starts playing - + onStop: when the graph stops playing - + onDrawForeground: render the inside widgets inside the node - + onDrawBackground: render the background area inside the node (only in edit mode) - + onMouseDown - + onMouseMove - + onMouseUp - + onMouseEnter - + onMouseLeave - + onExecute: execute the node - + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) - + onGetInputs: returns an array of possible inputs - + onGetOutputs: returns an array of possible outputs - + onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h]) - + onDblClick: double clicked in the node - + onInputDblClick: input slot double clicked (can be used to automatically create a node connected) - + onOutputDblClick: output slot double clicked (can be used to automatically create a node connected) - + onConfigure: called after the node has been configured - + onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data) - + onSelected - + onDeselected - + onDropItem : DOM item dropped over the node - + onDropFile : file dropped over the node - + onConnectInput : if returns false the incoming connection will be canceled - + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) - + onAction: action slot triggered - + getExtraMenuOptions: to add option to context menu -*/ - - /** - * Base Class for all the node type classes - * @class LGraphNode - * @param {String} name a name for the node - */ - - function LGraphNode(title) { - this._ctor(title); - } - - global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; - - LGraphNode.prototype._ctor = function(title) { - this.title = title || "Unnamed"; - this.size = [LiteGraph.NODE_WIDTH, 60]; - this.graph = null; - - this._pos = new Float32Array(10, 10); - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - if (LiteGraph.use_uuids) { - this.id = LiteGraph.uuidv4(); - } - else { - this.id = -1; //not know till not added - } - this.type = null; - - //inputs available: array of inputs - this.inputs = []; - this.outputs = []; - this.connections = []; - - //local data - this.properties = {}; //for the values - this.properties_info = []; //for the info - - this.flags = {}; - }; - - /** - * configure a node from an object containing the serialized info - * @method configure - */ - LGraphNode.prototype.configure = function(info) { - if (this.graph) { - this.graph._version++; - } - for (var j in info) { - if (j == "properties") { - //i don't want to clone properties, I want to reuse the old container - for (var k in info.properties) { - this.properties[k] = info.properties[k]; - if (this.onPropertyChanged) { - this.onPropertyChanged( k, info.properties[k] ); - } - } - continue; - } - - if (info[j] == null) { - continue; - } else if (typeof info[j] == "object") { - //object - if (this[j] && this[j].configure) { - this[j].configure(info[j]); - } else { - this[j] = LiteGraph.cloneObject(info[j], this[j]); - } - } //value - else { - this[j] = info[j]; - } - } - - if (!info.title) { - this.title = this.constructor.title; - } - - if (this.inputs) { - for (var i = 0; i < this.inputs.length; ++i) { - var input = this.inputs[i]; - var link_info = this.graph ? this.graph.links[input.link] : null; - if (this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated - - if( this.onInputAdded ) - this.onInputAdded(input); - - } - } - - if (this.outputs) { - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if (!output.links) { - continue; - } - for (var j = 0; j < output.links.length; ++j) { - var link_info = this.graph ? this.graph.links[output.links[j]] : null; - if (this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated - } - - if( this.onOutputAdded ) - this.onOutputAdded(output); - } - } - - if( this.widgets ) - { - for (var i = 0; i < this.widgets.length; ++i) - { - var w = this.widgets[i]; - if(!w) - continue; - if(w.options && w.options.property && this.properties[ w.options.property ]) - w.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) ); - } - if (info.widgets_values) { - for (var i = 0; i < info.widgets_values.length; ++i) { - if (this.widgets[i]) { - this.widgets[i].value = info.widgets_values[i]; - } - } - } - } - - if (this.onConfigure) { - this.onConfigure(info); - } - }; - - /** - * serialize the content - * @method serialize - */ - - LGraphNode.prototype.serialize = function() { - //create serialization object - var o = { - id: this.id, - type: this.type, - pos: this.pos, - size: this.size, - flags: LiteGraph.cloneObject(this.flags), - order: this.order, - mode: this.mode - }; - - //special case for when there were errors - if (this.constructor === LGraphNode && this.last_serialization) { - return this.last_serialization; - } - - if (this.inputs) { - o.inputs = this.inputs; - } - - if (this.outputs) { - //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) - for (var i = 0; i < this.outputs.length; i++) { - delete this.outputs[i]._data; - } - o.outputs = this.outputs; - } - - if (this.title && this.title != this.constructor.title) { - o.title = this.title; - } - - if (this.properties) { - o.properties = LiteGraph.cloneObject(this.properties); - } - - if (this.widgets && this.serialize_widgets) { - o.widgets_values = []; - for (var i = 0; i < this.widgets.length; ++i) { - if(this.widgets[i]) - o.widgets_values[i] = this.widgets[i].value; - else - o.widgets_values[i] = null; - } - } - - if (!o.type) { - o.type = this.constructor.type; - } - - if (this.color) { - o.color = this.color; - } - if (this.bgcolor) { - o.bgcolor = this.bgcolor; - } - if (this.boxcolor) { - o.boxcolor = this.boxcolor; - } - if (this.shape) { - o.shape = this.shape; - } - - if (this.onSerialize) { - if (this.onSerialize(o)) { - console.warn( - "node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter" - ); - } - } - - return o; - }; - - /* Creates a clone of this node */ - LGraphNode.prototype.clone = function() { - var node = LiteGraph.createNode(this.type); - if (!node) { - return null; - } - - //we clone it because serialize returns shared containers - var data = LiteGraph.cloneObject(this.serialize()); - - //remove links - if (data.inputs) { - for (var i = 0; i < data.inputs.length; ++i) { - data.inputs[i].link = null; - } - } - - if (data.outputs) { - for (var i = 0; i < data.outputs.length; ++i) { - if (data.outputs[i].links) { - data.outputs[i].links.length = 0; - } - } - } - - delete data["id"]; - - if (LiteGraph.use_uuids) { - data["id"] = LiteGraph.uuidv4() - } - - //remove links - node.configure(data); - - return node; - }; - - /** - * serialize and stringify - * @method toString - */ - - LGraphNode.prototype.toString = function() { - return JSON.stringify(this.serialize()); - }; - //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph - - /** - * get the title string - * @method getTitle - */ - - LGraphNode.prototype.getTitle = function() { - return this.title || this.constructor.title; - }; - - /** - * sets the value of a property - * @method setProperty - * @param {String} name - * @param {*} value - */ - LGraphNode.prototype.setProperty = function(name, value) { - if (!this.properties) { - this.properties = {}; - } - if( value === this.properties[name] ) - return; - var prev_value = this.properties[name]; - this.properties[name] = value; - if (this.onPropertyChanged) { - if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change - this.properties[name] = prev_value; - } - if(this.widgets) //widgets could be linked to properties - for(var i = 0; i < this.widgets.length; ++i) - { - var w = this.widgets[i]; - if(!w) - continue; - if(w.options.property == name) - { - w.value = value; - break; - } - } - }; - - // Execution ************************* - /** - * sets the output data - * @method setOutputData - * @param {number} slot - * @param {*} data - */ - LGraphNode.prototype.setOutputData = function(slot, data) { - if (!this.outputs) { - return; - } - - //this maybe slow and a niche case - //if(slot && slot.constructor === String) - // slot = this.findOutputSlot(slot); - - if (slot == -1 || slot >= this.outputs.length) { - return; - } - - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - - //store data in the output itself in case we want to debug - output_info._data = data; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - var link = this.graph.links[link_id]; - if(link) - link.data = data; - } - } - }; - - /** - * sets the output data type, useful when you want to be able to overwrite the data type - * @method setOutputDataType - * @param {number} slot - * @param {String} datatype - */ - LGraphNode.prototype.setOutputDataType = function(slot, type) { - if (!this.outputs) { - return; - } - if (slot == -1 || slot >= this.outputs.length) { - return; - } - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - //store data in the output itself in case we want to debug - output_info.type = type; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - this.graph.links[link_id].type = type; - } - } - }; - - /** - * Retrieves the input data (data traveling through the connection) from one slot - * @method getInputData - * @param {number} slot - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns undefined - */ - LGraphNode.prototype.getInputData = function(slot, force_update) { - if (!this.inputs) { - return; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return; - } - - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - - if (!force_update) { - return link.data; - } - - //special case: used to extract data from the incoming connection before the graph has been executed - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.data; - } - - if (node.updateOutputData) { - node.updateOutputData(link.origin_slot); - } else if (node.onExecute) { - node.onExecute(); - } - - return link.data; - }; - - /** - * Retrieves the input data type (in case this supports multiple input types) - * @method getInputDataType - * @param {number} slot - * @return {String} datatype in string format - */ - LGraphNode.prototype.getInputDataType = function(slot) { - if (!this.inputs) { - return null; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return null; - } - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.type; - } - var output_info = node.outputs[link.origin_slot]; - if (output_info) { - return output_info.type; - } - return null; - }; - - /** - * Retrieves the input data from one slot using its name instead of slot number - * @method getInputDataByName - * @param {String} slot_name - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns null - */ - LGraphNode.prototype.getInputDataByName = function( - slot_name, - force_update - ) { - var slot = this.findInputSlot(slot_name); - if (slot == -1) { - return null; - } - return this.getInputData(slot, force_update); - }; - - /** - * tells you if there is a connection in one input slot - * @method isInputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isInputConnected = function(slot) { - if (!this.inputs) { - return false; - } - return slot < this.inputs.length && this.inputs[slot].link != null; - }; - - /** - * tells you info about an input connection (which node, type, etc) - * @method getInputInfo - * @param {number} slot - * @return {Object} object or null { link: id, name: string, type: string or 0 } - */ - LGraphNode.prototype.getInputInfo = function(slot) { - if (!this.inputs) { - return null; - } - if (slot < this.inputs.length) { - return this.inputs[slot]; - } - return null; - }; - - /** - * Returns the link info in the connection of an input slot - * @method getInputLink - * @param {number} slot - * @return {LLink} object or null - */ - LGraphNode.prototype.getInputLink = function(slot) { - if (!this.inputs) { - return null; - } - if (slot < this.inputs.length) { - var slot_info = this.inputs[slot]; - return this.graph.links[ slot_info.link ]; - } - return null; - }; - - /** - * returns the node connected in the input slot - * @method getInputNode - * @param {number} slot - * @return {LGraphNode} node or null - */ - LGraphNode.prototype.getInputNode = function(slot) { - if (!this.inputs) { - return null; - } - if (slot >= this.inputs.length) { - return null; - } - var input = this.inputs[slot]; - if (!input || input.link === null) { - return null; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - return null; - } - return this.graph.getNodeById(link_info.origin_id); - }; - - /** - * returns the value of an input with this name, otherwise checks if there is a property with that name - * @method getInputOrProperty - * @param {string} name - * @return {*} value - */ - LGraphNode.prototype.getInputOrProperty = function(name) { - if (!this.inputs || !this.inputs.length) { - return this.properties ? this.properties[name] : null; - } - - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input_info = this.inputs[i]; - if (name == input_info.name && input_info.link != null) { - var link = this.graph.links[input_info.link]; - if (link) { - return link.data; - } - } - } - return this.properties[name]; - }; - - /** - * tells you the last output data that went in that slot - * @method getOutputData - * @param {number} slot - * @return {Object} object or null - */ - LGraphNode.prototype.getOutputData = function(slot) { - if (!this.outputs) { - return null; - } - if (slot >= this.outputs.length) { - return null; - } - - var info = this.outputs[slot]; - return info._data; - }; - - /** - * tells you info about an output connection (which node, type, etc) - * @method getOutputInfo - * @param {number} slot - * @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } - */ - LGraphNode.prototype.getOutputInfo = function(slot) { - if (!this.outputs) { - return null; - } - if (slot < this.outputs.length) { - return this.outputs[slot]; - } - return null; - }; - - /** - * tells you if there is a connection in one output slot - * @method isOutputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isOutputConnected = function(slot) { - if (!this.outputs) { - return false; - } - return ( - slot < this.outputs.length && - this.outputs[slot].links && - this.outputs[slot].links.length - ); - }; - - /** - * tells you if there is any connection in the output slots - * @method isAnyOutputConnected - * @return {boolean} - */ - LGraphNode.prototype.isAnyOutputConnected = function() { - if (!this.outputs) { - return false; - } - for (var i = 0; i < this.outputs.length; ++i) { - if (this.outputs[i].links && this.outputs[i].links.length) { - return true; - } - } - return false; - }; - - /** - * retrieves all the nodes connected to this output slot - * @method getOutputNodes - * @param {number} slot - * @return {array} - */ - LGraphNode.prototype.getOutputNodes = function(slot) { - if (!this.outputs || this.outputs.length == 0) { - return null; - } - - if (slot >= this.outputs.length) { - return null; - } - - var output = this.outputs[slot]; - if (!output.links || output.links.length == 0) { - return null; - } - - var r = []; - for (var i = 0; i < output.links.length; i++) { - var link_id = output.links[i]; - var link = this.graph.links[link_id]; - if (link) { - var target_node = this.graph.getNodeById(link.target_id); - if (target_node) { - r.push(target_node); - } - } - } - return r; - }; - - LGraphNode.prototype.addOnTriggerInput = function(){ - var trigS = this.findInputSlot("onTrigger"); - if (trigS == -1){ //!trigS || - var input = this.addInput("onTrigger", LiteGraph.EVENT, {optional: true, nameLocked: true}); - return this.findInputSlot("onTrigger"); - } - return trigS; - } - - LGraphNode.prototype.addOnExecutedOutput = function(){ - var trigS = this.findOutputSlot("onExecuted"); - if (trigS == -1){ //!trigS || - var output = this.addOutput("onExecuted", LiteGraph.ACTION, {optional: true, nameLocked: true}); - return this.findOutputSlot("onExecuted"); - } - return trigS; - } - - LGraphNode.prototype.onAfterExecuteNode = function(param, options){ - var trigS = this.findOutputSlot("onExecuted"); - if (trigS != -1){ - - //console.debug(this.id+":"+this.order+" triggering slot onAfterExecute"); - //console.debug(param); - //console.debug(options); - this.triggerSlot(trigS, param, null, options); - - } - } - - LGraphNode.prototype.changeMode = function(modeTo){ - switch(modeTo){ - case LiteGraph.ON_EVENT: - // this.addOnExecutedOutput(); - break; - - case LiteGraph.ON_TRIGGER: - this.addOnTriggerInput(); - this.addOnExecutedOutput(); - break; - - case LiteGraph.NEVER: - break; - - case LiteGraph.ALWAYS: - break; - - case LiteGraph.ON_REQUEST: - break; - - default: - return false; - break; - } - this.mode = modeTo; - return true; - }; - - /** - * Triggers the node code execution, place a boolean/counter to mark the node as being executed - * @method execute - * @param {*} param - * @param {*} options - */ - LGraphNode.prototype.doExecute = function(param, options) { - options = options || {}; - if (this.onExecute){ - - // enable this to give the event an ID - if (!options.action_call) options.action_call = this.id+"_exec_"+Math.floor(Math.random()*9999); - - this.graph.nodes_executing[this.id] = true; //.push(this.id); - - this.onExecute(param, options); - - this.graph.nodes_executing[this.id] = false; //.pop(); - - // save execution/action ref - this.exec_version = this.graph.iteration; - if(options && options.action_call){ - this.action_call = options.action_call; // if (param) - this.graph.nodes_executedAction[this.id] = options.action_call; - } - } - this.execute_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event - if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback - }; - - /** - * Triggers an action, wrapped by logics to control execution flow - * @method actionDo - * @param {String} action name - * @param {*} param - */ - LGraphNode.prototype.actionDo = function(action, param, options) { - options = options || {}; - if (this.onAction){ - - // enable this to give the event an ID - if (!options.action_call) options.action_call = this.id+"_"+(action?action:"action")+"_"+Math.floor(Math.random()*9999); - - this.graph.nodes_actioning[this.id] = (action?action:"actioning"); //.push(this.id); - - this.onAction(action, param, options); - - this.graph.nodes_actioning[this.id] = false; //.pop(); - - // save execution/action ref - if(options && options.action_call){ - this.action_call = options.action_call; // if (param) - this.graph.nodes_executedAction[this.id] = options.action_call; - } - } - this.action_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event - if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); - }; - - /** - * Triggers an event in this node, this will trigger any output with the same name - * @method trigger - * @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all - * @param {*} param - */ - LGraphNode.prototype.trigger = function(action, param, options) { - if (!this.outputs || !this.outputs.length) { - return; - } - - if (this.graph) - this.graph._last_trigger_time = LiteGraph.getTime(); - - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) - continue; - this.triggerSlot(i, param, null, options); - } - }; - - /** - * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes - * @method triggerSlot - * @param {Number} slot the index of the output slot - * @param {*} param - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) { - options = options || {}; - if (!this.outputs) { - return; - } - - if(slot == null) - { - console.error("slot must be a number"); - return; - } - - if(slot.constructor !== Number) - console.warn("slot must be a number, use node.trigger('name') if you want to use a string"); - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - if (this.graph) { - this.graph._last_trigger_time = LiteGraph.getTime(); - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = LiteGraph.getTime(); - var node = this.graph.getNodeById(link_info.target_id); - if (!node) { - //node not found? - continue; - } - - //used to mark events in graph - var target_connection = node.inputs[link_info.target_slot]; - - if (node.mode === LiteGraph.ON_TRIGGER) - { - // generate unique trigger ID if not present - if (!options.action_call) options.action_call = this.id+"_trigg_"+Math.floor(Math.random()*9999); - if (node.onExecute) { - // -- wrapping node.onExecute(param); -- - node.doExecute(param, options); - } - } - else if (node.onAction) { - // generate unique action ID if not present - if (!options.action_call) options.action_call = this.id+"_act_"+Math.floor(Math.random()*9999); - //pass the action name - var target_connection = node.inputs[link_info.target_slot]; - // wrap node.onAction(target_connection.name, param); - node.actionDo(target_connection.name, param, options); - } - } - }; - - /** - * clears the trigger slot animation - * @method clearTriggeredSlot - * @param {Number} slot the index of the output slot - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) { - if (!this.outputs) { - return; - } - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = 0; - } - }; - - /** - * changes node size and triggers callback - * @method setSize - * @param {vec2} size - */ - LGraphNode.prototype.setSize = function(size) - { - this.size = size; - if(this.onResize) - this.onResize(this.size); - } - - /** - * add a new property to this node - * @method addProperty - * @param {string} name - * @param {*} default_value - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of the property (like values, etc) - */ - LGraphNode.prototype.addProperty = function( - name, - default_value, - type, - extra_info - ) { - var o = { name: name, type: type, default_value: default_value }; - if (extra_info) { - for (var i in extra_info) { - o[i] = extra_info[i]; - } - } - if (!this.properties_info) { - this.properties_info = []; - } - this.properties_info.push(o); - if (!this.properties) { - this.properties = {}; - } - this.properties[name] = default_value; - return o; - }; - - //connections - - /** - * add a new output slot to use in this node - * @method addOutput - * @param {string} name - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc) - */ - LGraphNode.prototype.addOutput = function(name, type, extra_info) { - var output = { name: name, type: type, links: null }; - if (extra_info) { - for (var i in extra_info) { - output[i] = extra_info[i]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(output); - if (this.onOutputAdded) { - this.onOutputAdded(output); - } - - if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true); - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - return output; - }; - - /** - * add a new output slot to use in this node - * @method addOutputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addOutputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(o); - if (this.onOutputAdded) { - this.onOutputAdded(o); - } - - if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true); - - } - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing output slot - * @method removeOutput - * @param {number} slot - */ - LGraphNode.prototype.removeOutput = function(slot) { - this.disconnectOutput(slot); - this.outputs.splice(slot, 1); - for (var i = slot; i < this.outputs.length; ++i) { - if (!this.outputs[i] || !this.outputs[i].links) { - continue; - } - var links = this.outputs[i].links; - for (var j = 0; j < links.length; ++j) { - var link = this.graph.links[links[j]]; - if (!link) { - continue; - } - link.origin_slot -= 1; - } - } - - this.setSize( this.computeSize() ); - if (this.onOutputRemoved) { - this.onOutputRemoved(slot); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add a new input slot to use in this node - * @method addInput - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0 - * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc) - */ - LGraphNode.prototype.addInput = function(name, type, extra_info) { - type = type || 0; - var input = { name: name, type: type, link: null }; - if (extra_info) { - for (var i in extra_info) { - input[i] = extra_info[i]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - - this.inputs.push(input); - this.setSize( this.computeSize() ); - - if (this.onInputAdded) { - this.onInputAdded(input); - } - - LiteGraph.registerNodeAndSlotType(this,type); - - this.setDirtyCanvas(true, true); - return input; - }; - - /** - * add several new input slots in this node - * @method addInputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addInputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - this.inputs.push(o); - if (this.onInputAdded) { - this.onInputAdded(o); - } - - LiteGraph.registerNodeAndSlotType(this,info[1]); - } - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing input slot - * @method removeInput - * @param {number} slot - */ - LGraphNode.prototype.removeInput = function(slot) { - this.disconnectInput(slot); - var slot_info = this.inputs.splice(slot, 1); - for (var i = slot; i < this.inputs.length; ++i) { - if (!this.inputs[i]) { - continue; - } - var link = this.graph.links[this.inputs[i].link]; - if (!link) { - continue; - } - link.target_slot -= 1; - } - this.setSize( this.computeSize() ); - if (this.onInputRemoved) { - this.onInputRemoved(slot, slot_info[0] ); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add an special connection to this node (used for special kinds of graphs) - * @method addConnection - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...) - * @param {[x,y]} pos position of the connection inside the node - * @param {string} direction if is input or output - */ - LGraphNode.prototype.addConnection = function(name, type, pos, direction) { - var o = { - name: name, - type: type, - pos: pos, - direction: direction, - links: null - }; - this.connections.push(o); - return o; - }; - - /** - * computes the minimum size of a node according to its inputs and output slots - * @method computeSize - * @param {vec2} minHeight - * @return {vec2} the total size - */ - LGraphNode.prototype.computeSize = function(out) { - if (this.constructor.size) { - return this.constructor.size.concat(); - } - - var rows = Math.max( - this.inputs ? this.inputs.length : 1, - this.outputs ? this.outputs.length : 1 - ); - var size = out || new Float32Array([0, 0]); - rows = Math.max(rows, 1); - var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - - var title_width = compute_text_size(this.title); - var input_width = 0; - var output_width = 0; - - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - var text = input.label || input.name || ""; - var text_width = compute_text_size(text); - if (input_width < text_width) { - input_width = text_width; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - var text = output.label || output.name || ""; - var text_width = compute_text_size(text); - if (output_width < text_width) { - output_width = text_width; - } - } - } - - size[0] = Math.max(input_width + output_width + 10, title_width); - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH); - if (this.widgets && this.widgets.length) { - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5); - } - - size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - - var widgets_height = 0; - if (this.widgets && this.widgets.length) { - for (var i = 0, l = this.widgets.length; i < l; ++i) { - if (this.widgets[i].computeSize) - widgets_height += this.widgets[i].computeSize(size[0])[1] + 4; - else - widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4; - } - widgets_height += 8; - } - - //compute height using widgets height - if( this.widgets_up ) - size[1] = Math.max( size[1], widgets_height ); - else if( this.widgets_start_y != null ) - size[1] = Math.max( size[1], widgets_height + this.widgets_start_y ); - else - size[1] += widgets_height; - - function compute_text_size(text) { - if (!text) { - return 0; - } - return font_size * text.length * 0.6; - } - - if ( - this.constructor.min_height && - size[1] < this.constructor.min_height - ) { - size[1] = this.constructor.min_height; - } - - size[1] += 6; //margin - - return size; - }; - - LGraphNode.prototype.inResizeCorner = function(canvasX, canvasY) { - var rows = this.outputs ? this.outputs.length : 1; - var outputs_offset = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - return isInsideRectangle(canvasX, - canvasY, - this.pos[0] + this.size[0] - 15, - this.pos[1] + Math.max(this.size[1] - 15, outputs_offset), - 20, - 20 - ); - } - - /** - * returns all the info available about a property of this node. - * - * @method getPropertyInfo - * @param {String} property name of the property - * @return {Object} the object with all the available info - */ - LGraphNode.prototype.getPropertyInfo = function( property ) - { - var info = null; - - //there are several ways to define info about a property - //legacy mode - if (this.properties_info) { - for (var i = 0; i < this.properties_info.length; ++i) { - if (this.properties_info[i].name == property) { - info = this.properties_info[i]; - break; - } - } - } - //litescene mode using the constructor - if(this.constructor["@" + property]) - info = this.constructor["@" + property]; - - if(this.constructor.widgets_info && this.constructor.widgets_info[property]) - info = this.constructor.widgets_info[property]; - - //litescene mode using the constructor - if (!info && this.onGetPropertyInfo) { - info = this.onGetPropertyInfo(property); - } - - if (!info) - info = {}; - if(!info.type) - info.type = typeof this.properties[property]; - if(info.widget == "combo") - info.type = "enum"; - - return info; - } - - /** - * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties - * - * @method addWidget - * @param {String} type the widget type (could be "number","string","combo" - * @param {String} name the text to show on the widget - * @param {String} value the default value - * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify) - * @param {Object} options the object that contains special properties of this widget - * @return {Object} the created widget object - */ - LGraphNode.prototype.addWidget = function( type, name, value, callback, options ) - { - if (!this.widgets) { - this.widgets = []; - } - - if(!options && callback && callback.constructor === Object) - { - options = callback; - callback = null; - } - - if(options && options.constructor === String) //options can be the property name - options = { property: options }; - - if(callback && callback.constructor === String) //callback can be the property name - { - if(!options) - options = {}; - options.property = callback; - callback = null; - } - - if(callback && callback.constructor !== Function) - { - console.warn("addWidget: callback must be a function"); - callback = null; - } - - var w = { - type: type.toLowerCase(), - name: name, - value: value, - callback: callback, - options: options || {} - }; - - if (w.options.y !== undefined) { - w.y = w.options.y; - } - - if (!callback && !w.options.callback && !w.options.property) { - console.warn("LiteGraph addWidget(...) without a callback or property assigned"); - } - if (type == "combo" && !w.options.values) { - throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; - } - this.widgets.push(w); - this.setSize( this.computeSize() ); - return w; - }; - - LGraphNode.prototype.addCustomWidget = function(custom_widget) { - if (!this.widgets) { - this.widgets = []; - } - this.widgets.push(custom_widget); - return custom_widget; - }; - - /** - * returns the bounding of the object, used for rendering purposes - * bounding is: [topleft_cornerx, topleft_cornery, width, height] - * @method getBounding - * @return {Float32Array[4]} the total size - */ - LGraphNode.prototype.getBounding = function(out) { - out = out || new Float32Array(4); - out[0] = this.pos[0] - 4; - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - out[2] = this.size[0] + 4; - out[3] = this.flags.collapsed ? LiteGraph.NODE_TITLE_HEIGHT : this.size[1] + LiteGraph.NODE_TITLE_HEIGHT; - - if (this.onBounding) { - this.onBounding(out); - } - return out; - }; - - /** - * checks if a point is inside the shape of a node - * @method isPointInside - * @param {number} x - * @param {number} y - * @return {boolean} - */ - LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) { - margin = margin || 0; - - var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT; - if (skip_title) { - margin_top = 0; - } - if (this.flags && this.flags.collapsed) { - //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) - if ( - isInsideRectangle( - x, - y, - this.pos[0] - margin, - this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, - (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + - 2 * margin, - LiteGraph.NODE_TITLE_HEIGHT + 2 * margin - ) - ) { - return true; - } - } else if ( - this.pos[0] - 4 - margin < x && - this.pos[0] + this.size[0] + 4 + margin > x && - this.pos[1] - margin_top - margin < y && - this.pos[1] + this.size[1] + margin > y - ) { - return true; - } - return false; - }; - - /** - * checks if a point is inside a node slot, and returns info about which slot - * @method getSlotInPosition - * @param {number} x - * @param {number} y - * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } - */ - LGraphNode.prototype.getSlotInPosition = function(x, y) { - //search for inputs - var link_pos = new Float32Array(2); - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - this.getConnectionPos(true, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { input: input, slot: i, link_pos: link_pos }; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - this.getConnectionPos(false, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { output: output, slot: i, link_pos: link_pos }; - } - } - } - - return null; - }; - - /** - * returns the input slot with a given name (used for dynamic slots), -1 if not found - * @method findInputSlot - * @param {string} name the name of the slot - * @param {boolean} returnObj if the obj itself wanted - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findInputSlot = function(name, returnObj) { - if (!this.inputs) { - return -1; - } - for (var i = 0, l = this.inputs.length; i < l; ++i) { - if (name == this.inputs[i].name) { - return !returnObj ? i : this.inputs[i]; - } - } - return -1; - }; - - /** - * returns the output slot with a given name (used for dynamic slots), -1 if not found - * @method findOutputSlot - * @param {string} name the name of the slot - * @param {boolean} returnObj if the obj itself wanted - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findOutputSlot = function(name, returnObj) { - returnObj = returnObj || false; - if (!this.outputs) { - return -1; - } - for (var i = 0, l = this.outputs.length; i < l; ++i) { - if (name == this.outputs[i].name) { - return !returnObj ? i : this.outputs[i]; - } - } - return -1; - }; - - // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options - - /** - * returns the first free input slot - * @method findInputSlotFree - * @param {object} options - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findInputSlotFree = function(optsIn) { - var optsIn = optsIn || {}; - var optsDef = {returnObj: false - ,typesNotAccepted: [] - }; - var opts = Object.assign(optsDef,optsIn); - if (!this.inputs) { - return -1; - } - for (var i = 0, l = this.inputs.length; i < l; ++i) { - if (this.inputs[i].link && this.inputs[i].link != null) { - continue; - } - if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){ - continue; - } - return !opts.returnObj ? i : this.inputs[i]; - } - return -1; - }; - - /** - * returns the first output slot free - * @method findOutputSlotFree - * @param {object} options - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findOutputSlotFree = function(optsIn) { - var optsIn = optsIn || {}; - var optsDef = { returnObj: false - ,typesNotAccepted: [] - }; - var opts = Object.assign(optsDef,optsIn); - if (!this.outputs) { - return -1; - } - for (var i = 0, l = this.outputs.length; i < l; ++i) { - if (this.outputs[i].links && this.outputs[i].links != null) { - continue; - } - if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){ - continue; - } - return !opts.returnObj ? i : this.outputs[i]; - } - return -1; - }; - - /** - * findSlotByType for INPUTS - */ - LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { - return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied); - }; - - /** - * findSlotByType for OUTPUTS - */ - LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { - return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied); - }; - - /** - * returns the output (or input) slot with a given type, -1 if not found - * @method findSlotByType - * @param {boolean} input uise inputs instead of outputs - * @param {string} type the type of the slot - * @param {boolean} returnObj if the obj itself wanted - * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway) - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) { - input = input || false; - returnObj = returnObj || false; - preferFreeSlot = preferFreeSlot || false; - doNotUseOccupied = doNotUseOccupied || false; - var aSlots = input ? this.inputs : this.outputs; - if (!aSlots) { - return -1; - } - // !! empty string type is considered 0, * !! - if (type == "" || type == "*") type = 0; - for (var i = 0, l = aSlots.length; i < l; ++i) { - var tFound = false; - var aSource = (type+"").toLowerCase().split(","); - var aDest = aSlots[i].type=="0"||aSlots[i].type=="*"?"0":aSlots[i].type; - aDest = (aDest+"").toLowerCase().split(","); - for(var sI=0;sI= 0 && target_slot !== null){ - //console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot) - return this.connect(slot, target_node, target_slot); - }else{ - //console.log("type "+target_slotType+" not found or not free?") - if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){ - // WILL CREATE THE onTrigger IN SLOT - //console.debug("connect WILL CREATE THE onTrigger "+target_slotType+" to "+target_node); - return this.connect(slot, target_node, -1); - } - // connect to the first general output slot if not found a specific type and - if (opts.generalTypeInCase){ - var target_slot = target_node.findInputSlotByType(0, false, true, true); - //console.debug("connect TO a general type (*, 0), if not found the specific type ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); - if (target_slot >= 0){ - return this.connect(slot, target_node, target_slot); - } - } - // connect to the first free input slot if not found a specific type and this output is general - if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == "*" || target_slotType == "")){ - var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); - //console.debug("connect TO TheFirstFREE ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); - if (target_slot >= 0){ - return this.connect(slot, target_node, target_slot); - } - } - - console.debug("no way to connect type: ",target_slotType," to targetNODE ",target_node); - //TODO filter - - return null; - } - } - - /** - * connect this node input to the output of another node BY TYPE - * @method connectByType - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} node the target node - * @param {string} target_type the output slot type of the target node - * @return {Object} the link_info is created, otherwise null - */ - LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) { - var optsIn = optsIn || {}; - var optsDef = { createEventInCase: true - ,firstFreeIfInputGeneralInCase: true - ,generalTypeInCase: true - }; - var opts = Object.assign(optsDef,optsIn); - if (source_node && source_node.constructor === Number) { - source_node = this.graph.getNodeById(source_node); - } - var source_slot = source_node.findOutputSlotByType(source_slotType, false, true); - if (source_slot >= 0 && source_slot !== null){ - //console.debug("CONNbyTYPE OUT! type "+source_slotType+" for "+source_slot) - return source_node.connect(source_slot, this, slot); - }else{ - - // connect to the first general output slot if not found a specific type and - if (opts.generalTypeInCase){ - var source_slot = source_node.findOutputSlotByType(0, false, true, true); - if (source_slot >= 0){ - return source_node.connect(source_slot, this, slot); - } - } - - if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){ - // WILL CREATE THE onExecuted OUT SLOT - if (LiteGraph.do_add_triggers_slots){ - var source_slot = source_node.addOnExecutedOutput(); - return source_node.connect(source_slot, this, slot); - } - } - // connect to the first free output slot if not found a specific type and this input is general - if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == "*" || source_slotType == "")){ - var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); - if (source_slot >= 0){ - return source_node.connect(source_slot, this, slot); - } - } - - console.debug("no way to connect byOUT type: ",source_slotType," to sourceNODE ",source_node); - //TODO filter - - //console.log("type OUT! "+source_slotType+" not found or not free?") - return null; - } - } - - /** - * connect this node output to the input of another node - * @method connect - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} node the target node - * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) - * @return {Object} the link_info is created, otherwise null - */ - LGraphNode.prototype.connect = function(slot, target_node, target_slot) { - target_slot = target_slot || 0; - - if (!this.graph) { - //could be connected before adding it to a graph - console.log( - "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them." - ); //due to link ids being associated with graphs - return null; - } - - //seek for the output slot - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return null; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - if (target_node && target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "target node is null"; - } - - //avoid loopback - if (target_node == this) { - return null; - } - - //you can specify the slot by name - if (target_slot.constructor === String) { - target_slot = target_node.findInputSlot(target_slot); - if (target_slot == -1) { - if (LiteGraph.debug) { - console.log( - "Connect: Error, no slot of name " + target_slot - ); - } - return null; - } - } else if (target_slot === LiteGraph.EVENT) { - - if (LiteGraph.do_add_triggers_slots){ - //search for first slot with event? :: NO this is done outside - //console.log("Connect: Creating triggerEvent"); - // force mode - target_node.changeMode(LiteGraph.ON_TRIGGER); - target_slot = target_node.findInputSlot("onTrigger"); - }else{ - return null; // -- break -- - } - } else if ( - !target_node.inputs || - target_slot >= target_node.inputs.length - ) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - var changed = false; - - var input = target_node.inputs[target_slot]; - var link_info = null; - var output = this.outputs[slot]; - - if (!this.outputs[slot]){ - /*console.debug("Invalid slot passed: "+slot); - console.debug(this.outputs);*/ - return null; - } - - // allow target node to change slot - if (target_node.onBeforeConnectInput) { - // This way node can choose another slot (or make a new one?) - target_slot = target_node.onBeforeConnectInput(target_slot); //callback - } - - //check target_slot and check connection types - if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type)) - { - this.setDirtyCanvas(false, true); - if(changed) - this.graph.connectionChange(this, link_info); - return null; - }else{ - //console.debug("valid connection",output.type, input.type); - } - - //allows nodes to block connection, callback - if (target_node.onConnectInput) { - if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) { - return null; - } - } - if (this.onConnectOutput) { // callback - if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) { - return null; - } - } - - //if there is something already plugged there, disconnect - if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) { - this.graph.beforeChange(); - target_node.disconnectInput(target_slot, {doProcessChange: false}); - changed = true; - } - if (output.links !== null && output.links.length){ - switch(output.type){ - case LiteGraph.EVENT: - if (!LiteGraph.allow_multi_output_for_events){ - this.graph.beforeChange(); - this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false}); - changed = true; - } - break; - default: - break; - } - } - - var nextId - if (LiteGraph.use_uuids) - nextId = LiteGraph.uuidv4(); - else - nextId = ++this.graph.last_link_id; - - //create link class - link_info = new LLink( - nextId, - input.type || output.type, - this.id, - slot, - target_node.id, - target_slot - ); - - //add to graph links list - this.graph.links[link_info.id] = link_info; - - //connect in output - if (output.links == null) { - output.links = []; - } - output.links.push(link_info.id); - //connect in input - target_node.inputs[target_slot].link = link_info.id; - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - true, - link_info, - output - ); - } //link_info has been created now, so its updated - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - target_slot, - true, - link_info, - input - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - target_slot, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot, - target_node, - target_slot - ); - } - - this.setDirtyCanvas(false, true); - this.graph.afterChange(); - this.graph.connectionChange(this, link_info); - - return link_info; - }; - - /** - * disconnect one output to an specific node - * @method disconnectOutput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectOutput = function(slot, target_node) { - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - //get output slot - var output = this.outputs[slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //one of the output links in this slot - if (target_node) { - if (target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "Target Node not found"; - } - - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - - //is the link we are searching for... - if (link_info.target_id == target_node.id) { - output.links.splice(i, 1); //remove here - var input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove there - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.graph) { - this.graph._version++; - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - break; - } - } - } //all the links in this output slot - else { - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - if (!link_info) { - //bug: it happens sometimes - continue; - } - - var target_node = this.graph.getNodeById(link_info.target_id); - var input = null; - if (this.graph) { - this.graph._version++; - } - if (target_node) { - input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove other side link - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - output.links = null; - } - - this.setDirtyCanvas(false, true); - this.graph.connectionChange(this); - return true; - }; - - /** - * disconnect one input - * @method disconnectInput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectInput = function(slot) { - //seek for the output slot - if (slot.constructor === String) { - slot = this.findInputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.inputs || slot >= this.inputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - var input = this.inputs[slot]; - if (!input) { - return false; - } - - var link_id = this.inputs[slot].link; - if(link_id != null) - { - this.inputs[slot].link = null; - - //remove other side - var link_info = this.graph.links[link_id]; - if (link_info) { - var target_node = this.graph.getNodeById(link_info.origin_id); - if (!target_node) { - return false; - } - - var output = target_node.outputs[link_info.origin_slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //search in the inputs list for this link - for (var i = 0, l = output.links.length; i < l; i++) { - if (output.links[i] == link_id) { - output.links.splice(i, 1); - break; - } - } - - delete this.graph.links[link_id]; //remove from the pool - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.INPUT, - slot, - false, - link_info, - input - ); - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.OUTPUT, - i, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - target_node, - i - ); - this.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot); - } - } - } //link != null - - this.setDirtyCanvas(false, true); - if(this.graph) - this.graph.connectionChange(this); - return true; - }; - - /** - * returns the center of a connection point in canvas coords - * @method getConnectionPos - * @param {boolean} is_input true if if a input slot, false if it is an output - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {vec2} out [optional] a place to store the output, to free garbage - * @return {[x,y]} the position - **/ - LGraphNode.prototype.getConnectionPos = function( - is_input, - slot_number, - out - ) { - out = out || new Float32Array(2); - var num_slots = 0; - if (is_input && this.inputs) { - num_slots = this.inputs.length; - } - if (!is_input && this.outputs) { - num_slots = this.outputs.length; - } - - var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5; - - if (this.flags.collapsed) { - var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH; - if (this.horizontal) { - out[0] = this.pos[0] + w * 0.5; - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1]; - } - } else { - if (is_input) { - out[0] = this.pos[0]; - } else { - out[0] = this.pos[0] + w; - } - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5; - } - return out; - } - - //weird feature that never got finished - if (is_input && slot_number == -1) { - out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - return out; - } - - //hard-coded pos - if ( - is_input && - num_slots > slot_number && - this.inputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.inputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.inputs[slot_number].pos[1]; - return out; - } else if ( - !is_input && - num_slots > slot_number && - this.outputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.outputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.outputs[slot_number].pos[1]; - return out; - } - - //horizontal distributed slots - if (this.horizontal) { - out[0] = - this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots); - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1] + this.size[1]; - } - return out; - } - - //default vertical slots - if (is_input) { - out[0] = this.pos[0] + offset; - } else { - out[0] = this.pos[0] + this.size[0] + 1 - offset; - } - out[1] = - this.pos[1] + - (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + - (this.constructor.slot_start_y || 0); - return out; - }; - - /* Force align to grid */ - LGraphNode.prototype.alignToGrid = function() { - this.pos[0] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); - this.pos[1] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); - }; - - /* Console output */ - LGraphNode.prototype.trace = function(msg) { - if (!this.console) { - this.console = []; - } - - this.console.push(msg); - if (this.console.length > LGraphNode.MAX_CONSOLE) { - this.console.shift(); - } - - if(this.graph.onNodeTrace) - this.graph.onNodeTrace(this, msg); - }; - - /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ - LGraphNode.prototype.setDirtyCanvas = function( - dirty_foreground, - dirty_background - ) { - if (!this.graph) { - return; - } - this.graph.sendActionToCanvas("setDirty", [ - dirty_foreground, - dirty_background - ]); - }; - - LGraphNode.prototype.loadImage = function(url) { - var img = new Image(); - img.src = LiteGraph.node_images_path + url; - img.ready = false; - - var that = this; - img.onload = function() { - this.ready = true; - that.setDirtyCanvas(true); - }; - return img; - }; - - //safe LGraphNode action execution (not sure if safe) - /* -LGraphNode.prototype.executeAction = function(action) -{ - if(action == "") return false; - - if( action.indexOf(";") != -1 || action.indexOf("}") != -1) - { - this.trace("Error: Action contains unsafe characters"); - return false; - } - - var tokens = action.split("("); - var func_name = tokens[0]; - if( typeof(this[func_name]) != "function") - { - this.trace("Error: Action not found on node: " + func_name); - return false; - } - - var code = action; - - try - { - var _foo = eval; - eval = null; - (new Function("with(this) { " + code + "}")).call(this); - eval = _foo; - } - catch (err) - { - this.trace("Error executing action {" + action + "} :" + err); - return false; - } - - return true; +var w = /* @__PURE__ */ ((e) => (e[e.UP = 1] = "UP", e[e.DOWN = 2] = "DOWN", e[e.LEFT = 3] = "LEFT", e[e.RIGHT = 4] = "RIGHT", e[e.CENTER = 5] = "CENTER", e))(w || {}), Z = /* @__PURE__ */ ((e) => (e[e.ALWAYS = 0] = "ALWAYS", e[e.ON_EVENT = 1] = "ON_EVENT", e[e.NEVER = 2] = "NEVER", e[e.ON_TRIGGER = 3] = "ON_TRIGGER", e[e.ON_REQUEST = 4] = "ON_REQUEST", e))(Z || {}); +const re = ["Always", "On Event", "Never", "On Trigger"], Oe = ["#666", "#422", "#333", "#224", "#626"]; +var k = /* @__PURE__ */ ((e) => (e[e.DEFAULT = 0] = "DEFAULT", e[e.BOX_SHAPE = 1] = "BOX_SHAPE", e[e.ROUND_SHAPE = 2] = "ROUND_SHAPE", e[e.CIRCLE_SHAPE = 3] = "CIRCLE_SHAPE", e[e.CARD_SHAPE = 4] = "CARD_SHAPE", e[e.ARROW_SHAPE = 5] = "ARROW_SHAPE", e[e.GRID_SHAPE = 6] = "GRID_SHAPE", e))(k || {}); +const Ie = ["default", "box", "round", "circle", "card", "arrow", "square"]; +var Y = /* @__PURE__ */ ((e) => (e[e.INPUT = 0] = "INPUT", e[e.OUTPUT = 1] = "OUTPUT", e))(Y || {}), de = /* @__PURE__ */ ((e) => (e[e.STRAIGHT_LINK = 0] = "STRAIGHT_LINK", e[e.LINEAR_LINK = 1] = "LINEAR_LINK", e[e.SPLINE_LINK = 2] = "SPLINE_LINK", e))(de || {}); +const Xe = ["Straight", "Linear", "Spline"]; +var se = /* @__PURE__ */ ((e) => (e[e.NORMAL_TITLE = 0] = "NORMAL_TITLE", e[e.NO_TITLE = 1] = "NO_TITLE", e[e.TRANSPARENT_TITLE = 2] = "TRANSPARENT_TITLE", e[e.AUTOHIDE_TITLE = 3] = "AUTOHIDE_TITLE", e))(se || {}), I = /* @__PURE__ */ ((e) => (e[e.EVENT = -2] = "EVENT", e[e.ACTION = -1] = "ACTION", e[e.DEFAULT = 0] = "DEFAULT", e))(I || {}); +const Ae = ["*", "array", "object", "number", "string", "enum", "boolean", "table"]; +var ue = /* @__PURE__ */ ((e) => (e.VERTICAL_LAYOUT = "vertical", e.HORIZONTAL_LAYOUT = "horizontal", e))(ue || {}); +function Te(e, t, i) { + return t > e ? t : i < e ? i : e; } -*/ - - /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ - LGraphNode.prototype.captureInput = function(v) { - if (!this.graph || !this.graph.list_of_graphcanvas) { - return; +function ve(e, t) { + return e.reduce((i, n) => { + const s = t(n); + return i[s] = n, i; + }, {}); +} +function Ce(e, t) { + return t in e ? e[t] : null; +} +function ye(e, t) { + return t in e.constructor ? e.constructor[t] : null; +} +function Ge(e, t) { + if (e.target !== t) + return; + let i = e.clientX - parseInt(window.getComputedStyle(t).left), n = e.clientY - parseInt(window.getComputedStyle(t).top); + const s = (o) => { + if (o.buttons === 0) { + r(); + return; + } + t.style.top = o.clientY - n + "px", t.style.left = o.clientX - i + "px"; + }, r = () => { + window.removeEventListener("mousemove", s), window.removeEventListener("mouseup", r); + }; + window.addEventListener("mousemove", s), window.addEventListener("mouseup", r); +} +function Ee(e) { + return e.addEventListener("mousedown", (t) => Ge(t, e)), e.classList.add("draggable"), e; +} +function J(e) { + return e === I.EVENT ? "Event" : e === I.ACTION ? "Action" : e === I.DEFAULT ? "Default" : e; +} +function Se(e) { + return e === I.EVENT || e === I.ACTION || e === I.DEFAULT || typeof e == "string"; +} +const S = class { + /** Register a node class so it can be listed when the user wants to create a new one */ + static registerNodeType(e) { + S.debug && console.log("Node registered: " + e.type); + const t = e.name, i = e.type; + if (!i) + throw console.error(e), new Error("Config has no type: " + e); + if (S.debug && console.debug(t, i), e.category == null || e.category === "") { + const s = i.lastIndexOf("/"); + e.category = i.substring(0, s); + } + e.title || (e.title = t); + const n = S.registered_node_types[i]; + if (n && console.warn("replacing node type: " + i), e.supported_extensions) + for (let s in e.supported_extensions) { + const r = e.supported_extensions[s]; + r && r.constructor === String && (S.node_types_by_file_extension[r.toLowerCase()] = e); + } + e.class.__LITEGRAPH_TYPE__ = i, S.registered_node_types[i] = e, e.class.name && (S.Nodes[t] = e), S.onNodeTypeRegistered && S.onNodeTypeRegistered(i, e), n && S.onNodeTypeReplaced && S.onNodeTypeReplaced(i, e, n); + } + /** removes a node type from the system */ + static unregisterNodeType(e) { + let t; + if (typeof e == "string" ? t = S.registered_node_types[e] : t = e, !t) + throw "node type not found: " + e; + delete S.registered_node_types[t.type], t.constructor.name && delete S.Nodes[t.constructor.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, .. + */ + static registerNodeAndSlotType(e, t, i = !1) { + let n; + if (typeof e == "string" ? n = S.registered_node_types[e] : "type" in e ? n = S.registered_node_types[e.type] : n = e, !n) + throw "Node not registered!" + e; + var s = n.class.__litegraph_type__; + if (typeof t == "string") + var r = t.split(","); + else if (t == I.EVENT || t == I.ACTION) + var r = ["_event_"]; + else + var r = ["*"]; + for (var o = 0; o < r.length; ++o) { + var a = r[o]; + a === "" && (a = "*"); + var l = i ? "registered_slot_out_types" : "registered_slot_in_types"; + typeof this[l][a] > "u" && (this[l][a] = { nodes: [] }), this[l][a].nodes.push(s), a !== "_event_" && a !== "*" && (i ? S.slot_types_out.includes(a.toLowerCase()) || (S.slot_types_out.push(a.toLowerCase()), S.slot_types_out.sort()) : S.slot_types_in.includes(a.toLowerCase()) || (S.slot_types_in.push(a.toLowerCase()), S.slot_types_in.sort())); + } + } + /** Removes all previously registered node's types. */ + static clearRegisteredTypes() { + S.registered_node_types = {}, S.node_types_by_file_extension = {}, S.Nodes = {}, S.searchbox_extras = {}; + } + /** + * Create a new node type by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. + * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. + * @param name node name with namespace (p.e.: 'math/sum') + * @param func + * @param param_types an array containing the type of every parameter, otherwise parameters will accept any type + * @param return_type string with the return type, otherwise it will be generic + * @param properties properties to be configurable + */ + // static wrapFunctionAsNode( + // name: string, + // func: (...args: any[]) => any, + // param_types?: string[], + // return_type?: string, + // properties?: object + // ): void { + // var params = Array(func.length); + // var code = ""; + // var names = LiteGraph.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"; + // } + // code += + // "this.addOutput('out'," + + // (return_type ? "'" + return_type + "'" : 0) + + // ");\n"; + // if (properties) { + // code += + // "this.properties = " + JSON.stringify(properties) + ";\n"; + // } + // var classobj = Function(code) as any; + // classobj.title = name.split("/").pop(); + // classobj.desc = "Generated from " + func.name; + // classobj.prototype.onExecute = function onExecute() { + // for (var i = 0; i < params.length; ++i) { + // params[i] = this.getInputData(i); + // } + // var r = func.apply(this, params); + // this.setOutputData(0, r); + // }; + // LiteGraph.registerNodeType(name, classobj); + // } + /** + * Adds this method to all node types, existing and to be created + * (You can add it to LGraphNode.prototype but then existing node types wont have it) + */ + // static addNodeMethod(name: string, func: (...args: any[]) => any): void { + // LGraphNode.prototype[name] = func; + // for (var i in LiteGraph.registered_node_types) { + // var type = LiteGraph.registered_node_types[i]; + // if (type.prototype[name]) { + // type.prototype["_" + name] = type.prototype[name]; + // } //keep old in case of replacing + // type.prototype[name] = func; + // } + // } + /** + * Create a node of a given type with a name. The node is not attached to any graph yet. + * @param type full name of the node class. p.e. "math/sin" + * @param name a name to distinguish from other nodes + * @param options to set options + */ + static createNode(e, t, i = {}) { + let n = null, s; + if (typeof e == "string") + s = e; + else if (s = e.__LITEGRAPH_TYPE__, !s) + throw console.error(e), "Node was not registered yet!"; + if (n = S.registered_node_types[s], !n) + return console.warn( + 'GraphNode type "' + e + '" not registered.' + ), null; + t = t || n.title || s; + var r = null; + const o = i.constructorArgs || []; + if (S.catch_exceptions) + try { + r = new n.class(t, ...o); + } catch (p) { + return console.error("Error creating node!", p), null; + } + else + r = new n.class(t, ...o); + if (r.class = n.class, r.type = s, !r.title && t && (r.title = t), r.properties || (r.properties = {}), r.properties_info || (r.properties_info = []), r.flags || (r.flags = {}), r.size || (r.size = r.computeSize()), r.pos || (r.pos = [S.DEFAULT_POSITION[0], S.DEFAULT_POSITION[1]]), r.mode || (r.mode = Z.ALWAYS), i.instanceProps) + for (var a in i.instanceProps) + r[a] = i.instanceProps[a]; + const l = Ce(n.class, "propertyLayout"); + if (l) { + S.debug && console.debug("Found property layout!", l); + for (const p of l) { + const { name: f, defaultValue: c, type: v, options: g } = p; + r.addProperty(f, c, v, g); + } + } + const h = Ce(n.class, "slotLayout"); + if (h) { + if (S.debug && console.debug("Found slot layout!", h), h.inputs) + for (const p of h.inputs) { + const { name: f, type: c, options: v } = p; + r.addInput(f, c, v); } - - var list = this.graph.list_of_graphcanvas; - - for (var i = 0; i < list.length; ++i) { - var c = list[i]; - //releasing somebody elses capture?! - if (!v && c.node_capturing_input != this) { - continue; - } - - //change - c.node_capturing_input = v ? this : null; + if (h.outputs) + for (const p of h.outputs) { + const { name: f, type: c, options: v } = p; + r.addOutput(f, c, v); } - }; - - /** - * Collapse the node to make it smaller on the canvas - * @method collapse - **/ - LGraphNode.prototype.collapse = function(force) { - this.graph._version++; - if (this.constructor.collapsable === false && !force) { - return; + } + return r.onNodeCreated && r.onNodeCreated(), r; + } + /** + * Returns a registered node type with a given name + * @param type full name of the node class. p.e. "math/sin" + */ + static getNodeType(e) { + return S.registered_node_types[e]; + } + /** + * Returns a list of node types matching one category + * @method getNodeTypesInCategory + * @param {String} category category name + * @param {String} filter only nodes with ctor.filter equal can be shown + * @return {Array} array with all the node classes + */ + static getNodeTypesInCategory(e, t) { + var i = []; + for (var n in S.registered_node_types) { + var s = S.registered_node_types[n]; + s.filter == t && (e == "" ? s.category == null && i.push(s) : s.category == e && i.push(s)); + } + return S.auto_sort_node_types && i.sort(function(r, o) { + return r.title.localeCompare(o.title); + }), i; + } + /** + * 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 + */ + static getNodeTypesCategories(e) { + var t = { "": 1 }; + for (var i in S.registered_node_types) { + var n = S.registered_node_types[i]; + if (n.category && !n.hide_in_node_lists) { + if (n.filter != e) + continue; + t[n.category] = 1; + } + } + var s = []; + for (var i in t) + s.push(i); + return S.auto_sort_node_types ? s.sort() : s; + } + /** debug purposes: reloads all the js scripts that matches a wildcard */ + static reloadNodes(e) { + for (var t = document.getElementsByTagName("script"), i = [], n = 0; n < t.length; n++) + i.push(t[n]); + var s = document.getElementsByTagName("head")[0]; + e = document.location.href + e; + for (var n = 0; n < i.length; n++) { + var r = i[n].src; + if (!(!r || r.substr(0, e.length) != e)) + try { + S.debug && console.log("Reloading: " + r); + var o = document.createElement("script"); + o.type = "text/javascript", o.src = r, s.appendChild(o), s.removeChild(i[n]); + } catch (l) { + if (S.throw_errors) + throw l; + S.debug && console.log("Error while reloading " + r); } - if (!this.flags.collapsed) { - this.flags.collapsed = true; - } else { - this.flags.collapsed = false; + } + S.debug && console.log("Nodes reloaded"); + } + // TODO move + //separated just to improve if it doesn't work + static cloneObject(e, t) { + if (e == null) + return null; + var i = JSON.parse(JSON.stringify(e)); + if (!t) + return i; + for (var n in i) + t[n] = i[n]; + return t; + } + /** + * Returns if the types of two slots are compatible (taking into account wildcards, etc) + * @method isValidConnection + * @param {String} type_a + * @param {String} type_b + * @return {Boolean} true if they can be connected + */ + static isValidConnection(e, t) { + if ((e == "" || e === "*") && (e = I.DEFAULT), (t == "" || t === "*") && (t = I.DEFAULT), !e || !t || e == t || e == I.EVENT && t == I.ACTION || e == I.ACTION && t == I.EVENT) + return !0; + if (e = String(e), t = String(t), e = e.toLowerCase(), t = t.toLowerCase(), e.indexOf(",") == -1 && t.indexOf(",") == -1) + return e == t; + for (var i = e.split(","), n = t.split(","), s = 0; s < i.length; ++s) + for (var r = 0; r < n.length; ++r) + if (this.isValidConnection(i[s], n[r])) + return !0; + return !1; + } + static getTime() { + return Date.now(); + } + // static LLink: typeof LLink; + // static LGraph: typeof LGraph; + // static DragAndScale: typeof DragAndScale; + static compareObjects(e, t) { + for (var i in e) + if (e[i] != t[i]) + return !1; + return !0; + } + static distance(e, t) { + return Math.sqrt( + (t[0] - e[0]) * (t[0] - e[0]) + (t[1] - e[1]) * (t[1] - e[1]) + ); + } + static colorToString(e) { + return "rgba(" + Math.round(e[0] * 255).toFixed() + "," + Math.round(e[1] * 255).toFixed() + "," + Math.round(e[2] * 255).toFixed() + "," + (e.length == 4 ? e[3].toFixed(2) : "1.0") + ")"; + } + static isInsideRectangle(e, t, i, n, s, r) { + return i < e && i + s > e && n < t && n + r > t; + } + // [minx,miny,maxx,maxy] + static growBounding(e, t, i) { + return t < e[0] ? e[0] = t : t > e[2] && (e[2] = t), i < e[1] ? e[1] = i : i > e[3] && (e[3] = i), e; + } + static isInsideBounding(e, t) { + return !(e[0] < t[0][0] || e[1] < t[0][1] || e[0] > t[1][0] || e[1] > t[1][1]); + } + // bounding overlap, format: [ startx, starty, width, height ] + static overlapBounding(e, t) { + var i = e[0] + e[2], n = e[1] + e[3], s = t[0] + t[2], r = t[1] + t[3]; + return !(e[0] > s || e[1] > r || i < t[0] || n < t[1]); + } + // 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. + static hex2num(e) { + e.charAt(0) == "#" && (e = e.slice(1)), e = e.toUpperCase(); + var t = "0123456789ABCDEF"; + let i; + for (var n = 0, s, r, o = 0; o < 6; o += 2) + s = t.indexOf(e.charAt(o)), r = t.indexOf(e.charAt(o + 1)), i[n] = s * 16 + r, n++; + return i; + } + //Give a array with three values as the argument and the function will return + // the corresponding hex triplet. + static num2hex(e) { + for (var t = "0123456789ABCDEF", i = "#", n, s, r = 0; r < 3; r++) + n = e[r] / 16, s = e[r] % 16, i += t.charAt(n) + t.charAt(s); + return i; + } + // ContextMenu: typeof ContextMenu; + // static extendClass(target: A, origin: B): A & B; + // static getParameterNames(func: string | Function): string[]; + /* helper for interaction: pointer, touch, mouse Listeners + used by LGraphCanvas DragAndScale ContextMenu*/ + static pointerListenerAdd(e, t, i, n = !1) { + if (!(!e || !e.addEventListener || !t || typeof i != "function")) { + var s = S.pointerevents_method, r = t; + if (s == "pointer" && !window.PointerEvent) + switch (console.warn("sMethod=='pointer' && !window.PointerEvent"), console.log("Converting pointer[" + r + "] : down move up cancel enter TO touchstart touchmove touchend, etc .."), r) { + case "down": { + s = "touch", r = "start"; + break; + } + case "move": { + s = "touch"; + break; + } + case "up": { + s = "touch", r = "end"; + break; + } + case "cancel": { + s = "touch"; + break; + } + case "enter": { + console.log("debug: Should I send a move event?"); + break; + } + default: + console.warn("PointerEvent not available in this browser ? The event " + r + " would not be called"); } - this.setDirtyCanvas(true, true); - }; - - /** - * Forces the node to do not move or realign on Z - * @method pin - **/ - - LGraphNode.prototype.pin = function(v) { - this.graph._version++; - if (v === undefined) { - this.flags.pinned = !this.flags.pinned; - } else { - this.flags.pinned = v; - } - }; - - LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) { - return [ - (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], - (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1] + switch (r) { + case "down": + case "up": + case "move": + case "over": + case "out": + case "enter": + e.addEventListener(s + r, i, n); + case "leave": + case "cancel": + case "gotpointercapture": + case "lostpointercapture": + if (s != "mouse") + return e.addEventListener(s + r, i, n); + default: + return e.addEventListener(r, i, n); + } + } + } + static pointerListenerRemove(e, t, i, n = !1) { + if (!(!e || !e.removeEventListener || !t || typeof i != "function")) + switch (t) { + case "down": + case "up": + case "move": + case "over": + case "out": + case "enter": + (S.pointerevents_method == "pointer" || S.pointerevents_method == "mouse") && e.removeEventListener(S.pointerevents_method + t, i, n); + case "leave": + case "cancel": + case "gotpointercapture": + case "lostpointercapture": + if (S.pointerevents_method == "pointer") + return e.removeEventListener(S.pointerevents_method + t, i, n); + default: + return e.removeEventListener(t, i, n); + } + } +}; +let u = S; +u.VERSION = 10; +u.CANVAS_GRID_SIZE = 10; +u.NODE_TITLE_HEIGHT = 20; +u.NODE_TITLE_TEXT_Y = 15; +u.NODE_SLOT_HEIGHT = 20; +u.NODE_WIDGET_HEIGHT = 20; +u.NODE_WIDTH = 140; +u.NODE_MIN_WIDTH = 50; +u.NODE_COLLAPSED_RADIUS = 10; +u.NODE_COLLAPSED_WIDTH = 80; +u.NODE_TITLE_COLOR = "#999"; +u.NODE_SELECTED_TITLE_COLOR = "#FFF"; +u.NODE_TEXT_SIZE = 14; +u.NODE_TEXT_COLOR = "#AAA"; +u.NODE_SUBTEXT_SIZE = 12; +u.NODE_DEFAULT_COLOR = "#333"; +u.NODE_DEFAULT_BGCOLOR = "#353535"; +u.NODE_DEFAULT_BOXCOLOR = "#666"; +u.NODE_DEFAULT_SHAPE = "box"; +u.NODE_BOX_OUTLINE_COLOR = "#FFF"; +u.DEFAULT_SHADOW_COLOR = "rgba(0,0,0,0.5)"; +u.DEFAULT_GROUP_FONT_SIZE = 24; +u.WIDGET_BGCOLOR = "#222"; +u.WIDGET_OUTLINE_COLOR = "#666"; +u.WIDGET_TEXT_COLOR = "#DDD"; +u.WIDGET_SECONDARY_TEXT_COLOR = "#999"; +u.LINK_COLOR = "#9A9"; +u.EVENT_LINK_COLOR = "#A86"; +u.ACTION_LINK_COLOR = "#86A"; +u.CONNECTING_LINK_COLOR = "#AFA"; +u.MAX_NUMBER_OF_NODES = 1e3; +u.DEFAULT_POSITION = [100, 100]; +u.proxy = null; +u.node_images_path = ""; +u.debug = !1; +u.catch_exceptions = !0; +u.throw_errors = !0; +u.allow_scripts = !1; +u.registered_node_types = {}; +u.node_types_by_file_extension = {}; +u.Nodes = {}; +u.Globals = {}; +u.searchbox_extras = {}; +u.auto_sort_node_types = !1; +u.node_box_coloured_when_on = !1; +u.node_box_coloured_by_mode = !1; +u.dialog_close_on_mouse_leave = !0; +u.dialog_close_on_mouse_leave_delay = 500; +u.shift_click_do_break_link_from = !1; +u.click_do_break_link_to = !1; +u.search_hide_on_mouse_leave = !0; +u.search_filter_enabled = !1; +u.search_show_all_on_open = !0; +u.auto_load_slot_types = !1; +u.registered_slot_in_types = {}; +u.registered_slot_out_types = {}; +u.slot_types_in = []; +u.slot_types_out = []; +u.slot_types_default_in = {}; +u.slot_types_default_out = {}; +u.alt_drag_do_clone_nodes = !1; +u.do_add_triggers_slots = !1; +u.allow_multi_output_for_events = !0; +u.middle_click_slot_add_default_node = !1; +u.release_link_on_empty_shows_menu = !1; +u.ignore_all_widget_events = !1; +u.pointerevents_method = "mouse"; +u.use_uuids = !1; +u.search_box_refresh_interval_ms = 250; +u.graph_inputs_outputs_use_combo_widget = !1; +u.serialize_slot_data = !1; +class Be { + constructor(t, i = !1) { + this.offset = [0, 0], this.scale = 1, this.max_scale = 10, this.min_scale = 0.1, this.onredraw = null, this.enabled = !0, this.last_mouse = [0, 0], this.element = null, this.visible_area = new Float32Array([0, 0, 0, 0]), this.viewport = null, this.dragging = !1, this._binded_mouse_callback = null, t && (this.element = t, i || this.bindEvents(t)); + } + bindEvents(t) { + this.last_mouse = [0, 0], this._binded_mouse_callback = this.onMouse.bind(this), u.pointerListenerAdd(t, "down", this._binded_mouse_callback), u.pointerListenerAdd(t, "move", this._binded_mouse_callback), u.pointerListenerAdd(t, "up", this._binded_mouse_callback), t.addEventListener( + "mousewheel", + this._binded_mouse_callback, + !1 + ), t.addEventListener("wheel", this._binded_mouse_callback, !1); + } + computeVisibleArea(t) { + if (!this.element) { + this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; + return; + } + var i = this.element.width, n = this.element.height, s = -this.offset[0], r = -this.offset[1]; + t && (s += t[0] / this.scale, r += t[1] / this.scale, i = t[2], n = t[3]); + var o = s + i / this.scale, a = r + n / this.scale; + this.visible_area[0] = s, this.visible_area[1] = r, this.visible_area[2] = o - s, this.visible_area[3] = a - r; + } + onMouse(t) { + if (!this.enabled) + return; + var i = this.element, n = i.getBoundingClientRect(); + let s = t; + var r = s.clientX - n.left, o = s.clientY - n.top; + s.canvasX = r, s.canvasX = o, s.dragging = this.dragging; + var a = !this.viewport || this.viewport && r >= this.viewport[0] && r < this.viewport[0] + this.viewport[2] && o >= this.viewport[1] && o < this.viewport[1] + this.viewport[3]; + if (s.type == u.pointerevents_method + "down" && a) + this.dragging = !0, u.pointerListenerRemove(i, "move", this._binded_mouse_callback), u.pointerListenerAdd(document, "move", this._binded_mouse_callback), u.pointerListenerAdd(document, "up", this._binded_mouse_callback); + else if (s.type == u.pointerevents_method + "move") { + var l = r - this.last_mouse[0], h = o - this.last_mouse[1]; + this.dragging && this.mouseDrag(l, h); + } else + s.type == u.pointerevents_method + "up" ? (this.dragging = !1, u.pointerListenerRemove(document, "move", this._binded_mouse_callback), u.pointerListenerRemove(document, "up", this._binded_mouse_callback), u.pointerListenerAdd(i, "move", this._binded_mouse_callback)) : a && (s.type == "mousewheel" || s.type == "wheel" || s.type == "DOMMouseScroll") && (s.eventType = "mousewheel", s.type == "wheel" ? s.wheel = -s.deltaY : s.wheel = s.wheelDeltaY != null ? s.wheelDeltaY : s.detail * -60, s.delta = s.wheelDelta ? s.wheelDelta / 40 : s.deltaY ? -s.deltaY / 3 : 0, this.changeDeltaScale(1 + s.delta * 0.05, [s.clientX, s.clientY])); + if (this.last_mouse[0] = r, this.last_mouse[1] = o, a) + return s.preventDefault(), s.stopPropagation(), !1; + } + toCanvasContext(t) { + t.scale(this.scale, this.scale), t.translate(this.offset[0], this.offset[1]); + } + convertOffsetToCanvas(t) { + return [ + (t[0] + this.offset[0]) * this.scale, + (t[1] + this.offset[1]) * this.scale + ]; + } + convertCanvasToOffset(t, i = [0, 0]) { + return i[0] = t[0] / this.scale - this.offset[0], i[1] = t[1] / this.scale - this.offset[1], i; + } + mouseDrag(t, i) { + this.offset[0] += t / this.scale, this.offset[1] += i / this.scale, this.onredraw && this.onredraw(this); + } + changeScale(t, i) { + if (t < this.min_scale ? t = this.min_scale : t > this.max_scale && (t = this.max_scale), t != this.scale && this.element) { + var n = this.element.getBoundingClientRect(); + if (n) { + i = i || [ + n.width * 0.5, + n.height * 0.5 + ], i[0] -= n.left, i[1] -= n.top; + var s = this.convertCanvasToOffset(i); + this.scale = t, Math.abs(this.scale - 1) < 0.01 && (this.scale = 1); + var r = this.convertCanvasToOffset(i), o = [ + r[0] - s[0], + r[1] - s[1] ]; - }; - - function LGraphGroup(title) { - this._ctor(title); + this.offset[0] += o[0], this.offset[1] += o[1], this.onredraw && this.onredraw(this); + } } - - global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup; - - LGraphGroup.prototype._ctor = function(title) { - this.title = title || "Group"; - this.font_size = 24; - this.color = LGraphCanvas.node_colors.pale_blue - ? LGraphCanvas.node_colors.pale_blue.groupcolor - : "#AAA"; - this._bounding = new Float32Array([10, 10, 140, 80]); - this._pos = this._bounding.subarray(0, 2); - this._size = this._bounding.subarray(2, 4); - this._nodes = []; - this.graph = null; - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - Object.defineProperty(this, "size", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._size[0] = Math.max(140, v[0]); - this._size[1] = Math.max(80, v[1]); - }, - get: function() { - return this._size; - }, - enumerable: true - }); - }; - - LGraphGroup.prototype.configure = function(o) { - this.title = o.title; - this._bounding.set(o.bounding); - this.color = o.color; - this.font = o.font; - }; - - LGraphGroup.prototype.serialize = function() { - var b = this._bounding; - return { - title: this.title, - bounding: [ - Math.round(b[0]), - Math.round(b[1]), - Math.round(b[2]), - Math.round(b[3]) - ], - color: this.color, - font: this.font - }; - }; - - LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { - this._pos[0] += deltax; - this._pos[1] += deltay; - if (ignore_nodes) { - return; - } - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - node.pos[0] += deltax; - node.pos[1] += deltay; - } - }; - - LGraphGroup.prototype.recomputeInsideNodes = function() { - this._nodes.length = 0; - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - node.getBounding(node_bounding); - if (!overlapBounding(this._bounding, node_bounding)) { - continue; - } //out of the visible area - this._nodes.push(node); - } - }; - - LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside; - LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; - - //**************************************** - - //Scale and Offset - function DragAndScale(element, skip_events) { - this.offset = new Float32Array([0, 0]); - this.scale = 1; - this.max_scale = 10; - this.min_scale = 0.1; - this.onredraw = null; - this.enabled = true; - this.last_mouse = [0, 0]; - this.element = null; - this.visible_area = new Float32Array(4); - - if (element) { - this.element = element; - if (!skip_events) { - this.bindEvents(element); + } + changeDeltaScale(t, i) { + this.changeScale(this.scale * t, i); + } + reset() { + this.scale = 1, this.offset[0] = 0, this.offset[1] = 0; + } +} +class ge { + processMouseDown(t) { + if (this.set_canvas_dirty_on_mouse_event && (this.dirty_canvas = !0), !this.graph) + return; + let i = t; + this.adjustMouseEvent(i); + var n = this.getCanvasWindow(); + n.document, N.active_canvas = this; + var s = i.clientX, r = i.clientY; + this.ds.viewport = this.viewport; + var o = !this.viewport || this.viewport && s >= this.viewport[0] && s < this.viewport[0] + this.viewport[2] && r >= this.viewport[1] && r < this.viewport[1] + this.viewport[3]; + if (this.skip_events || (u.pointerListenerRemove(this.canvas, "move", this._mousemove_callback), u.pointerListenerAdd(n.document, "move", this._mousemove_callback, !0), u.pointerListenerAdd(n.document, "up", this._mouseup_callback, !0)), !!o) { + var a = this.graph.getNodeOnPos(i.canvasX, i.canvasY, this.visible_nodes, 5), l = !1, h = u.getTime(), p = !(i instanceof PointerEvent) || !i.isPrimary, f = h - this.last_mouseclick < 300 && p; + if (this.mouse[0] = i.clientX, this.mouse[1] = i.clientY, this.offset_mouse[0] = i.offsetX, this.offset_mouse[1] = i.offsetY, this.graph_mouse[0] = i.canvasX, this.graph_mouse[1] = i.canvasY, this.last_click_position = [this.mouse[0], this.mouse[1]], this.last_click_position_offset = [this.offset_mouse[0], this.offset_mouse[1]], this.pointer_is_down && p ? this.pointer_is_double = !0 : this.pointer_is_double = !1, this.pointer_is_down = !0, this.canvas.focus(), X.closeAllContextMenus(n), this.search_box && this.search_box.close(), !(this.onMouse && this.onMouse(i) === !0)) { + if (i.which == 1 && !this.pointer_is_double) { + if (i.ctrlKey && this.allow_interaction && !this.read_only && (this.dragging_rectangle = new Float32Array(4), this.dragging_rectangle[0] = i.canvasX, this.dragging_rectangle[1] = i.canvasY, this.dragging_rectangle[2] = 1, this.dragging_rectangle[3] = 1, l = !0), u.alt_drag_do_clone_nodes && i.altKey && a && this.allow_interaction && !l && !this.read_only) { + let P = a.clone(); + P && (P.pos[0] += 5, P.pos[1] += 5, this.graph.add(P, { doCalcSize: !1 }), a = P, l = !0, m || (this.allow_dragnodes && (this.graph.beforeChange(), this.node_dragged = a), this.selected_nodes[a.id] || this.processNodeSelected(a, i))); + } + var c = !1; + if (a && this.allow_interaction && !l && !this.read_only) { + if (!this.live_mode && !a.flags.pinned && this.bringToFront(a), !this.connecting_node && !a.flags.collapsed && !this.live_mode) + if (!l && a.resizable !== !1 && u.isInsideRectangle( + i.canvasX, + i.canvasY, + a.pos[0] + a.size[0] - 5, + a.pos[1] + a.size[1] - 5, + 10, + 10 + )) + this.graph.beforeChange(), this.resizing_node = a, this.canvas.style.cursor = "se-resize", l = !0; + else { + if (a.outputs) + for (var v = 0, g = a.outputs.length; v < g; ++v) { + var d = a.outputs[v], _ = a.getConnectionPos(!1, v); + if (u.isInsideRectangle( + i.canvasX, + i.canvasY, + _[0] - 15, + _[1] - 10, + 30, + 20 + )) { + this.connecting_node = a, this.connecting_output = d, this.connecting_output.slot_index = v, this.connecting_pos = a.getConnectionPos(!1, v), this.connecting_slot = v, u.shift_click_do_break_link_from && i.shiftKey && a.disconnectOutput(v), f ? a.onOutputDblClick && a.onOutputDblClick(v, i) : a.onOutputClick && a.onOutputClick(v, i), l = !0; + break; + } + } + if (a.inputs) + for (var v = 0, g = a.inputs.length; v < g; ++v) { + var y = a.inputs[v], _ = a.getConnectionPos(!0, v); + if (u.isInsideRectangle( + i.canvasX, + i.canvasY, + _[0] - 15, + _[1] - 10, + 30, + 20 + )) { + if (f ? a.onInputDblClick && a.onInputDblClick(v, i) : a.onInputClick && a.onInputClick(v, i), y.link !== null) { + var b = this.graph.links[y.link]; + u.click_do_break_link_to && (a.disconnectInput(v), this.dirty_bgcanvas = !0, l = !0), (this.allow_reconnect_links || //this.move_destination_link_without_shift || + i.shiftKey) && (u.click_do_break_link_to || a.disconnectInput(v), this.connecting_node = this.graph._nodes_by_id[b.origin_id], this.connecting_slot = b.origin_slot, this.connecting_output = this.connecting_node.outputs[this.connecting_slot], this.connecting_pos = this.connecting_node.getConnectionPos(!1, this.connecting_slot), this.dirty_bgcanvas = !0, l = !0); + } + l || (this.connecting_node = a, this.connecting_input = y, this.connecting_input.slot_index = v, this.connecting_pos = a.getConnectionPos(!0, v), this.connecting_slot = v, this.dirty_bgcanvas = !0, l = !0); + } + } + } + if (!l) { + var m = !1, E = [i.canvasX - a.pos[0], i.canvasY - a.pos[1]], T = this.processNodeWidgets(a, this.graph_mouse, i); + T && (m = !0, this.node_widget = [a, T]), f && this.selected_nodes[a.id] && (a.onDblClick && a.onDblClick(i, E, this), this.processNodeDblClicked(a), m = !0), a.onMouseDown && a.onMouseDown(i, E, this) ? m = !0 : (a.subgraph && !a.skip_subgraph_button && !a.flags.collapsed && E[0] > a.size[0] - u.NODE_TITLE_HEIGHT && E[1] < 0 && setTimeout(() => { + this.openSubgraph(a.subgraph); + }, 10), this.live_mode && (c = !0, m = !0)), m || (this.allow_dragnodes && (this.graph.beforeChange(), this.node_dragged = a), this.selected_nodes[a.id] || this.processNodeSelected(a, i)), this.dirty_canvas = !0; } + } else if (!l) { + let P = !1; + if (a && a.subgraph && !a.skip_subgraph_button) { + var E = [i.canvasX - a.pos[0], i.canvasY - a.pos[1]]; + !a.flags.collapsed && E[0] > a.size[0] - u.NODE_TITLE_HEIGHT && E[1] < 0 && (P = !0, setTimeout(() => { + this.openSubgraph(a.subgraph); + }, 10)); + } + if (!P) { + if (this.allow_interaction && !this.read_only) { + const F = this.findLinkCenterAtPos(i.canvasX, i.canvasY); + F != null && (this.showLinkMenu(F, i), this.over_link_center = null); + } + if (this.selected_group = this.graph.getGroupOnPos(i.canvasX, i.canvasY), this.selected_group_resizing = !1, this.selected_group && !this.read_only && this.allow_interaction) { + i.ctrlKey && (this.dragging_rectangle = null); + var O = u.distance([i.canvasX, i.canvasY], [this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1]]); + O * this.ds.scale < 10 ? this.selected_group_resizing = !0 : this.selected_group.recomputeInsideNodes(); + } + f && !this.read_only && this.allow_searchbox && this.allow_interaction && (this.showSearchBox(i), i.preventDefault(), i.stopPropagation()), c = !0; + } + } + !l && c && this.allow_dragcanvas && (this.dragging_canvas = !0); + } else if (i.which == 2) { + if (u.middle_click_slot_add_default_node && a && this.allow_interaction && !l && !this.read_only && !this.connecting_node && !a.flags.collapsed && !this.live_mode) { + var A = null, M = null, L = null; + if (a.outputs) + for (var v = 0, g = a.outputs.length; v < g; ++v) { + var d = a.outputs[v], _ = a.getConnectionPos(!1, v); + if (u.isInsideRectangle(i.canvasX, i.canvasY, _[0] - 15, _[1] - 10, 30, 20)) { + A = d, M = v, L = !0; + break; + } + } + if (a.inputs) + for (var v = 0, g = a.inputs.length; v < g; ++v) { + var y = a.inputs[v], _ = a.getConnectionPos(!0, v); + if (u.isInsideRectangle(i.canvasX, i.canvasY, _[0] - 15, _[1] - 10, 30, 20)) { + A = y, M = v, L = !1; + break; + } + } + if (A && M !== !1) { + var B = 0.5 - (M + 1) / (L ? a.outputs.length : a.inputs.length), G = a.getBounding(), z = [ + L ? G[0] + G[2] : G[0], + i.canvasY - 80 + // + node_bounding[0]/this.canvas.width*66 // vertical "derive" + ]; + this.createDefaultNodeForSlot("AUTO", { + nodeFrom: L ? a : null, + slotFrom: L ? M : null, + nodeTo: L ? null : a, + slotTo: L ? null : M, + position: z, + posAdd: [L ? 30 : -30, -B * 130], + posSizeFix: [L ? 0 : -1, 0] + //-alphaPosY*2*/ + }); + } + } + } else if ((i.which == 3 || this.pointer_is_double) && this.allow_interaction && !l && !this.read_only) { + let P = null; + if (a) + P = { type: "node", item: a }, Object.keys(this.selected_nodes).length && (this.selected_nodes[a.id] || i.shiftKey || i.ctrlKey || i.metaKey) ? this.selected_nodes[a.id] || this.selectNodes([a], !0) : this.selectNodes([a]); + else { + const F = this.findLinkCenterAtPos(i.canvasX, i.canvasY); + F != null && (this.over_link_center = null, this.dirty_canvas = !0, P = { type: "link", item: F }); + } + this.processContextMenu(P, i); } + if (this.selected_group_moving = !1, this.selected_group && !this.selected_group_resizing) { + var pe = this.selected_group.fontSize || u.DEFAULT_GROUP_FONT_SIZE, D = pe * 1.4; + u.isInsideRectangle(i.canvasX, i.canvasY, this.selected_group.pos[0], this.selected_group.pos[1], this.selected_group.size[0], D) && (this.selected_group_moving = !0); + } + return this.last_mouse[0] = i.clientX, this.last_mouse[1] = i.clientY, this.last_mouseclick = u.getTime(), this.last_mouse_dragging = !0, this.graph.change(), (!n.document.activeElement || n.document.activeElement.nodeName.toLowerCase() != "input" && n.document.activeElement.nodeName.toLowerCase() != "textarea") && i.preventDefault(), i.stopPropagation(), this.onMouseDown && this.onMouseDown(i), !1; + } } - - LiteGraph.DragAndScale = DragAndScale; - - DragAndScale.prototype.bindEvents = function(element) { - this.last_mouse = new Float32Array(2); - - this._binded_mouse_callback = this.onMouse.bind(this); - - LiteGraph.pointerListenerAdd(element,"down", this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(element,"move", this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(element,"up", this._binded_mouse_callback); - - element.addEventListener( - "mousewheel", - this._binded_mouse_callback, - false - ); - element.addEventListener("wheel", this._binded_mouse_callback, false); - }; - - DragAndScale.prototype.computeVisibleArea = function( viewport ) { - if (!this.element) { - this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; - return; - } - var width = this.element.width; - var height = this.element.height; - var startx = -this.offset[0]; - var starty = -this.offset[1]; - if( viewport ) - { - startx += viewport[0] / this.scale; - starty += viewport[1] / this.scale; - width = viewport[2]; - height = viewport[3]; - } - var endx = startx + width / this.scale; - var endy = starty + height / this.scale; - this.visible_area[0] = startx; - this.visible_area[1] = starty; - this.visible_area[2] = endx - startx; - this.visible_area[3] = endy - starty; - }; - - DragAndScale.prototype.onMouse = function(e) { - if (!this.enabled) { - return; - } - - var canvas = this.element; - var rect = canvas.getBoundingClientRect(); - var x = e.clientX - rect.left; - var y = e.clientY - rect.top; - e.canvasx = x; - e.canvasy = y; - e.dragging = this.dragging; - - 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]) ); - - //console.log("pointerevents: DragAndScale onMouse "+e.type+" "+is_inside); - - var ignore = false; - if (this.onmouse) { - ignore = this.onmouse(e); - } - - if (e.type == LiteGraph.pointerevents_method+"down" && is_inside) { - this.dragging = true; - LiteGraph.pointerListenerRemove(canvas,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(document,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(document,"up",this._binded_mouse_callback); - } else if (e.type == LiteGraph.pointerevents_method+"move") { - if (!ignore) { - var deltax = x - this.last_mouse[0]; - var deltay = y - this.last_mouse[1]; - if (this.dragging) { - this.mouseDrag(deltax, deltay); - } - } - } else if (e.type == LiteGraph.pointerevents_method+"up") { - this.dragging = false; - LiteGraph.pointerListenerRemove(document,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerRemove(document,"up",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(canvas,"move",this._binded_mouse_callback); - } else if ( is_inside && - (e.type == "mousewheel" || - e.type == "wheel" || - e.type == "DOMMouseScroll") - ) { - e.eventType = "mousewheel"; - if (e.type == "wheel") { - e.wheel = -e.deltaY; - } else { - e.wheel = - e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - } - - //from stack overflow - e.delta = e.wheelDelta - ? e.wheelDelta / 40 - : e.deltaY - ? -e.deltaY / 3 - : 0; - this.changeDeltaScale(1.0 + e.delta * 0.05); - } - - this.last_mouse[0] = x; - this.last_mouse[1] = y; - - if(is_inside) - { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - DragAndScale.prototype.toCanvasContext = function(ctx) { - ctx.scale(this.scale, this.scale); - ctx.translate(this.offset[0], this.offset[1]); - }; - - DragAndScale.prototype.convertOffsetToCanvas = function(pos) { - //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]]; - return [ - (pos[0] + this.offset[0]) * this.scale, - (pos[1] + this.offset[1]) * this.scale + } + processMouseMove(t) { + let i = t; + if (this.autoresize && this.resize(), this.set_canvas_dirty_on_mouse_event && (this.dirty_canvas = !0), !this.graph) + return; + N.active_canvas = this, this.adjustMouseEvent(i); + let n = [i.clientX, i.clientY]; + this.mouse[0] = n[0], this.mouse[1] = n[1]; + let s = [ + n[0] - this.last_mouse[0], + n[1] - this.last_mouse[1] + ]; + if (this.last_mouse = n, this.offset_mouse[0] = i.offsetX, this.offset_mouse[1] = i.offsetY, this.graph_mouse[0] = i.canvasX, this.graph_mouse[1] = i.canvasY, this.block_click) + return i.preventDefault(), !1; + i.dragging = this.last_mouse_dragging, this.node_widget && (this.processNodeWidgets( + this.node_widget[0], + this.graph_mouse, + i, + this.node_widget[1] + ), this.dirty_canvas = !0); + const r = this.selected_group; + if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving && (this.selected_group = null), this.dragging_rectangle) + this.dragging_rectangle[2] = i.canvasX - this.dragging_rectangle[0], this.dragging_rectangle[3] = i.canvasY - this.dragging_rectangle[1], this.dirty_canvas = !0; + else if (this.selected_group && !this.read_only && this.allow_interaction) { + if (this.selected_group_resizing) + this.selected_group.size = [ + i.canvasX - this.selected_group.pos[0], + i.canvasY - this.selected_group.pos[1] ]; - }; - - DragAndScale.prototype.convertCanvasToOffset = function(pos, out) { - out = out || [0, 0]; - out[0] = pos[0] / this.scale - this.offset[0]; - out[1] = pos[1] / this.scale - this.offset[1]; - return out; - }; - - DragAndScale.prototype.mouseDrag = function(x, y) { - this.offset[0] += x / this.scale; - this.offset[1] += y / this.scale; - - if (this.onredraw) { - this.onredraw(this); + else { + var o = s[0] / this.ds.scale, a = s[1] / this.ds.scale; + this.selected_group.move(o, a, i.ctrlKey), this.selected_group._nodes.length && (this.dirty_canvas = !0); + } + this.dirty_bgcanvas = !0; + } else if (this.dragging_canvas) + this.ds.offset[0] += s[0] / this.ds.scale, this.ds.offset[1] += s[1] / this.ds.scale, this.dirty_canvas = !0, this.dirty_bgcanvas = !0; + else { + const b = this.allow_interaction && !this.read_only; + this.connecting_node && (this.dirty_canvas = !0); + var l = this.graph.getNodeOnPos(i.canvasX, i.canvasY, this.visible_nodes); + if (b) + for (var h = 0, p = this.graph._nodes.length; h < p; ++h) { + let m = this.graph._nodes[h]; + if (m.mouseOver && l != m) { + m.mouseOver = !1, this.node_over && this.node_over.onMouseLeave && this.node_over.onMouseLeave(i, [i.canvasX - this.node_over.pos[0], i.canvasY - this.node_over.pos[1]], this); + const E = this.node_over; + this.node_over = null, this.dirty_canvas = !0, E != this.node_over && this.onHoverChange && this.onHoverChange(this.node_over, E); + } } - }; - - DragAndScale.prototype.changeScale = function(value, zooming_center) { - if (value < this.min_scale) { - value = this.min_scale; - } else if (value > this.max_scale) { - value = this.max_scale; + if (l) { + if (l.redraw_on_mouse && (this.dirty_canvas = !0), b) { + if (!l.mouseOver) { + l.mouseOver = !0; + const m = this.node_over; + this.node_over = l, this.dirty_canvas = !0, m != this.node_over && this.onHoverChange && this.onHoverChange(this.node_over, m), l.onMouseEnter && l.onMouseEnter(i, [i.canvasX - l.pos[0], i.canvasY - l.pos[1]], this); + } + if (l.onMouseMove && l.onMouseMove(i, [i.canvasX - l.pos[0], i.canvasY - l.pos[1]], this), this.connecting_node) { + if (this.connecting_output) { + var f = this._highlight_input || [0, 0]; + if (!this.isOverNodeBox(l, i.canvasX, i.canvasY)) { + var c = this.isOverNodeInput(l, i.canvasX, i.canvasY, f); + if (c != -1 && l.inputs[c]) { + var v = l.inputs[c].type; + u.isValidConnection(this.connecting_output.type, v) && (this._highlight_input = f, this._highlight_input_slot = l.inputs[c]); + } else + this._highlight_input = null, this._highlight_input_slot = null; + } + } else if (this.connecting_input) { + var f = this._highlight_output || [0, 0]; + if (!this.isOverNodeBox(l, i.canvasX, i.canvasY)) { + var c = this.isOverNodeOutput(l, i.canvasX, i.canvasY, f); + if (c != -1 && l.outputs[c]) { + var v = l.outputs[c].type; + u.isValidConnection(this.connecting_input.type, v) && (this._highlight_output = f); + } else + this._highlight_output = null; + } + } + } + this.canvas && (u.isInsideRectangle( + i.canvasX, + i.canvasY, + l.pos[0] + l.size[0] - 5, + l.pos[1] + l.size[1] - 5, + 5, + 5 + ) ? this.canvas.style.cursor = "se-resize" : this.canvas.style.cursor = "crosshair"); } - - if (value == this.scale) { - return; + } else { + var g = this.findLinkCenterAtPos(i.canvasX, i.canvasY); + g != this.over_link_center && (this.over_link_center = g, this.dirty_canvas = !0), this.canvas && (this.canvas.style.cursor = ""); + } + if (b) { + if (this.node_capturing_input && this.node_capturing_input != l && this.node_capturing_input.onMouseMove && this.node_capturing_input.onMouseMove(i, [i.canvasX - this.node_capturing_input.pos[0], i.canvasY - this.node_capturing_input.pos[1]], this), this.node_dragged && !this.live_mode) { + for (const m in this.selected_nodes) { + var d = this.selected_nodes[m]; + d.pos[0] += s[0] / this.ds.scale, d.pos[1] += s[1] / this.ds.scale; + } + this.dirty_canvas = !0, this.dirty_bgcanvas = !0; } - - if (!this.element) { - return; + if (this.resizing_node && !this.live_mode) { + var _ = [i.canvasX - this.resizing_node.pos[0], i.canvasY - this.resizing_node.pos[1]], y = this.resizing_node.computeSize(); + _[0] = Math.max(y[0], _[0]), _[1] = Math.max(y[1], _[1]), this.resizing_node.setSize(_), this.canvas.style.cursor = "se-resize", this.dirty_canvas = !0, this.dirty_bgcanvas = !0; } - - var rect = this.element.getBoundingClientRect(); - if (!rect) { - return; - } - - zooming_center = zooming_center || [ - rect.width * 0.5, - rect.height * 0.5 - ]; - var center = this.convertCanvasToOffset(zooming_center); - this.scale = value; - if (Math.abs(this.scale - 1) < 0.01) { - this.scale = 1; - } - - var new_center = this.convertCanvasToOffset(zooming_center); - var delta_offset = [ - new_center[0] - center[0], - new_center[1] - center[1] - ]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - - if (this.onredraw) { - this.onredraw(this); - } - }; - - DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) { - this.changeScale(this.scale * value, zooming_center); - }; - - DragAndScale.prototype.reset = function() { - this.scale = 1; - this.offset[0] = 0; - this.offset[1] = 0; - }; - - //********************************************************************************* - // LGraphCanvas: LGraph renderer CLASS - //********************************************************************************* - - /** - * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. - * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked - * - * @class LGraphCanvas - * @constructor - * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) - * @param {LGraph} graph [optional] - * @param {Object} options [optional] { skip_rendering, autoresize, viewport } - */ - function LGraphCanvas(canvas, graph, options) { - this.options = options = options || {}; - - //if(graph === undefined) - // throw ("No graph assigned"); - this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE; - - if (canvas && canvas.constructor === String) { - canvas = document.querySelector(canvas); - } - - this.ds = new DragAndScale(); - this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much - - 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; - 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.read_only = false; //if set to true users cannot modify the graph - 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 if the mouse does anything - 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; - - //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.viewport = options.viewport || null; //to constraint render area to a portion of the canvas - - //link canvas and graph - if (graph) { - graph.attachCanvas(this); - } - - this.setCanvas(canvas,options.skip_events); - this.clear(); - - if (!options.skip_render) { - this.startRendering(); - } - - this.autoresize = options.autoresize; + } } - - global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; - - LGraphCanvas.DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; - - LGraphCanvas.link_type_colors = { - "-1": LiteGraph.EVENT_LINK_COLOR, - number: "#AAA", - node: "#DCA" - }; - LGraphCanvas.gradients = {}; //cache of gradients - - /** - * clears all the data inside - * - * @method clear - */ - LGraphCanvas.prototype.clear = function() { - 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.selected_nodes = {}; - this.selected_group = null; - - this.visible_nodes = []; - this.node_dragged = null; - this.node_over = null; - this.node_capturing_input = null; - this.connecting_node = null; - this.highlighted_links = {}; - - this.dragging_canvas = false; - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.dirty_area = 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]); - - if (this.onClear) { - this.onClear(); + return r && !this.selected_group_resizing && !this.selected_group_moving && (this.selected_group = r), i.preventDefault(), !1; + } + processMouseUp(t) { + let i = t; + var n = !(i instanceof PointerEvent) || !i.isPrimary; + if (!n) + return !1; + if (this.set_canvas_dirty_on_mouse_event && (this.dirty_canvas = !0), !!this.graph) { + var s = this.getCanvasWindow(), r = s.document; + N.active_canvas = this, this.skip_events || (u.pointerListenerRemove(r, "move", this._mousemove_callback, !0), u.pointerListenerAdd(this.canvas, "move", this._mousemove_callback, !0), u.pointerListenerRemove(r, "up", this._mouseup_callback, !0)), this.adjustMouseEvent(i); + var o = u.getTime(); + if (i.click_time = o - this.last_mouseclick, this.last_mouse_dragging = !1, this.last_click_position = null, this.block_click && (this.block_click = !1), i.which == 1) { + if (this.node_widget && this.processNodeWidgets(this.node_widget[0], this.graph_mouse, i), this.node_widget = null, this.selected_group) { + var a = this.selected_group.pos[0] - Math.round(this.selected_group.pos[0]), l = this.selected_group.pos[1] - Math.round(this.selected_group.pos[1]); + this.selected_group.move(a, l, i.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] + ), this.selected_group._nodes.length && (this.dirty_canvas = !0), this.selected_group = null; } - }; - - /** - * assigns a graph, you can reassign graphs to the same canvas - * - * @method setGraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.setGraph = function(graph, skip_clear) { - if (this.graph == graph) { - return; - } - - if (!skip_clear) { - this.clear(); - } - - if (!graph && this.graph) { - this.graph.detachCanvas(this); - return; - } - - graph.attachCanvas(this); - - //remove the graph stack in case a subgraph was open - if (this._graph_stack) - this._graph_stack = null; - - this.setDirty(true, true); - }; - - /** - * returns the top level graph (in case there are subgraphs open on the canvas) - * - * @method getTopGraph - * @return {LGraph} graph - */ - LGraphCanvas.prototype.getTopGraph = function() - { - if(this._graph_stack.length) - return this._graph_stack[0]; - return this.graph; - } - - /** - * opens a graph contained inside a node in the current graph - * - * @method openSubgraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.openSubgraph = function(graph) { - if (!graph) { - throw "graph cannot be null"; - } - - if (this.graph == graph) { - throw "graph cannot be the same"; - } - - this.clear(); - - if (this.graph) { - if (!this._graph_stack) { - this._graph_stack = []; - } - this._graph_stack.push(this.graph); - } - - graph.attachCanvas(this); - this.checkPanels(); - this.setDirty(true, true); - }; - - /** - * closes a subgraph contained inside a node - * - * @method closeSubgraph - * @param {LGraph} assigns a graph - */ - LGraphCanvas.prototype.closeSubgraph = function() { - 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 (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 - }; - - /** - * returns the visually active graph (in case there are more in the stack) - * @method getCurrentGraph - * @return {LGraph} the active graph - */ - LGraphCanvas.prototype.getCurrentGraph = function() { - return this.graph; - }; - - /** - * assigns a canvas - * - * @method setCanvas - * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) - */ - LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) { - var that = this; - - if (canvas) { - if (canvas.constructor === String) { - canvas = document.getElementById(canvas); - if (!canvas) { - throw "Error creating LiteGraph canvas: Canvas not found"; - } - } - } - - if (canvas === this.canvas) { - return; - } - - if (!canvas && this.canvas) { - //maybe detach events from old_canvas - if (!skip_events) { - this.unbindEvents(); - } - } - - this.canvas = canvas; - this.ds.element = canvas; - - 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; - if (!this.bgcanvas) { - this.bgcanvas = document.createElement("canvas"); - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - } - - if (canvas.getContext == null) { - if (canvas.localName != "canvas") { - throw "Element supplied for LGraphCanvas must be a element, you passed a " + - canvas.localName; - } - throw "This browser doesn't support Canvas"; - } - - var ctx = (this.ctx = canvas.getContext("2d")); - if (ctx == null) { - if (!canvas.webgl_enabled) { - console.warn( - "This canvas seems to be WebGL, enabling WebGL renderer" - ); - } - 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(); - } - }; - - //used in some events to capture them - LGraphCanvas.prototype._doNothing = function doNothing(e) { - //console.log("pointerevents: _doNothing "+e.type); - e.preventDefault(); - return false; - }; - LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { - e.preventDefault(); - return true; - }; - - /** - * binds mouse, keyboard, touch and drag events to the canvas - * @method bindEvents - **/ - LGraphCanvas.prototype.bindEvents = function() { - if (this._events_binded) { - console.warn("LGraphCanvas: events already binded"); - return; - } - - //console.log("pointerevents: bindEvents"); - - var canvas = this.canvas; - - var ref_window = this.getCanvasWindow(); - var 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); - // why mousemove and mouseup were not binded here? - 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,"up", this._mouseup_callback, true); // CHECK: ??? binded or not - LiteGraph.pointerListenerAdd(canvas,"move", this._mousemove_callback); - - canvas.addEventListener("contextmenu", this._doNothing); - canvas.addEventListener( - "DOMMouseScroll", - this._mousewheel_callback, - false + this.selected_group_resizing = !1; + var h = this.graph.getNodeOnPos( + i.canvasX, + i.canvasY, + this.visible_nodes ); - - //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 - - //Dropping Stuff over nodes ************************************ - 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); - - this._events_binded = true; - }; - - /** - * unbinds mouse events from the canvas - * @method unbindEvents - **/ - LGraphCanvas.prototype.unbindEvents = function() { - if (!this._events_binded) { - console.warn("LGraphCanvas: no events binded"); - return; - } - - //console.log("pointerevents: unbindEvents"); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - - LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousedown_callback); - LiteGraph.pointerListenerRemove(this.canvas,"up", this._mousedown_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; - }; - - LGraphCanvas.getFileExtension = function(url) { - var question = url.indexOf("?"); - if (question != -1) { - url = url.substr(0, question); - } - var point = url.lastIndexOf("."); - if (point == -1) { - return ""; - } - return url.substr(point + 1).toLowerCase(); - }; - - /** - * 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 - **/ - LGraphCanvas.prototype.enableWebGL = function() { - if (typeof GL === undefined) { - throw "litegl.js must be included to use a WebGL canvas"; - } - if (typeof enableWebGLCanvas === undefined) { - 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; - - /* - GL.create({ canvas: this.bgcanvas }); - this.bgctx = enableWebGLCanvas( this.bgcanvas ); - window.gl = this.gl; - */ - }; - - /** - * 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) - */ - LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) { - 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) - */ - LGraphCanvas.prototype.getCanvasWindow = function() { - if (!this.canvas) { - return window; - } - var doc = this.canvas.ownerDocument; - return doc.defaultView || doc.parentWindow; - }; - - /** - * starts rendering the content of the canvas when needed - * - * @method startRendering - */ - LGraphCanvas.prototype.startRendering = function() { - if (this.is_rendering) { - return; - } //already rendering - - this.is_rendering = true; - renderFrame.call(this); - - function renderFrame() { - if (!this.pause_rendering) { - this.draw(); + if (this.dragging_rectangle) { + if (this.graph) { + var p = this.graph._nodes, f = new Float32Array(4), c = Math.abs(this.dragging_rectangle[2]), v = Math.abs(this.dragging_rectangle[3]), g = this.dragging_rectangle[2] < 0 ? this.dragging_rectangle[0] - c : this.dragging_rectangle[0], d = this.dragging_rectangle[3] < 0 ? this.dragging_rectangle[1] - v : this.dragging_rectangle[1]; + if (this.dragging_rectangle[0] = g, this.dragging_rectangle[1] = d, this.dragging_rectangle[2] = c, this.dragging_rectangle[3] = v, !h || c > 10 && v > 10) { + for (var _ = [], y = 0; y < p.length; ++y) { + var b = p[y]; + b.getBounding(f), u.overlapBounding( + this.dragging_rectangle, + f + ) && _.push(b); + } + _.length && this.selectNodes(_, i.shiftKey); + } else + this.selectNodes([h], i.shiftKey || i.ctrlKey); + } + this.dragging_rectangle = null; + } else if (this.connecting_node) { + this.dirty_canvas = !0, this.dirty_bgcanvas = !0; + var m = this.connecting_output || this.connecting_input, E = m.type; + if (h) { + if (this.connecting_output) { + var T = this.isOverNodeInput( + h, + i.canvasX, + i.canvasY + ); + T != -1 ? this.connecting_node.connect(this.connecting_slot, h, T) : this.connecting_node.connectByTypeInput(this.connecting_slot, h, E); + } else if (this.connecting_input) { + var T = this.isOverNodeOutput( + h, + i.canvasX, + i.canvasY + ); + T != -1 ? h.connect(T, this.connecting_node, this.connecting_slot) : this.connecting_node.connectByTypeOutput(this.connecting_slot, h, E); } - - var window = this.getCanvasWindow(); - if (this.is_rendering) { - window.requestAnimationFrame(renderFrame.bind(this)); - } - } - }; - - /** - * stops rendering the content of the canvas (to save resources) - * - * @method stopRendering - */ - LGraphCanvas.prototype.stopRendering = function() { - this.is_rendering = false; - /* - if(this.rendering_timer_id) - { - clearInterval(this.rendering_timer_id); - this.rendering_timer_id = null; - } - */ - }; - - /* LiteGraphCanvas input */ - - //used to block future mouse events (because of im gui) - LGraphCanvas.prototype.blockClick = function() - { - this.block_click = true; - this.last_mouseclick = 0; - } - - LGraphCanvas.prototype.processMouseDown = function(e) { - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) { - return; - } - - this.adjustMouseEvent(e); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - LGraphCanvas.active_canvas = this; - var that = this; - - var x = e.clientX; - var 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]) ); - - //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; - } - this.pointer_is_down = true; - - - this.canvas.focus(); - - LiteGraph.closeAllContextMenus(ref_window); - - if (this.onMouse) - { - if (this.onMouse(e) == true) - return; - } - - //left button mouse / single finger - if (e.which == 1 && !this.pointer_is_double) - { - if (e.ctrlKey) - { - 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 && node && this.allow_interaction && !skip_action && !this.read_only) - { - if (cloned = node.clone()){ - 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); - } - } - } - } - - var 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); - } //if it wasn't selected? - - //not dragging mouse to connect two slots - if ( this.allow_interaction && !this.connecting_node && !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; - } 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); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - this.connecting_node = node; - this.connecting_output = output; - this.connecting_output.slot_index = i; - this.connecting_pos = node.getConnectionPos( false, i ); - this.connecting_slot = i; - - if (LiteGraph.shift_click_do_break_link_from){ - if (e.shiftKey) { - node.disconnectOutput(i); - } - } - - if (is_double_click) { - if (node.onOutputDblClick) { - node.onOutputDblClick(i, e); - } - } else { - if (node.onOutputClick) { - node.onOutputClick(i, e); - } - } - - 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); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - if (is_double_click) { - if (node.onInputDblClick) { - node.onInputDblClick(i, e); - } - } else { - if (node.onInputClick) { - node.onInputClick(i, e); - } - } - - if (input.link !== null) { - var link_info = this.graph.links[ - input.link - ]; //before disconnecting - if (LiteGraph.click_do_break_link_to){ - node.disconnectInput(i); - this.dirty_bgcanvas = true; - skip_action = true; - }else{ - // do same action as has not node ? - } - - if ( - this.allow_reconnect_links || - //this.move_destination_link_without_shift || - e.shiftKey - ) { - if (!LiteGraph.click_do_break_link_to){ - node.disconnectInput(i); - } - this.connecting_node = this.graph._nodes_by_id[ - link_info.origin_id - ]; - this.connecting_slot = - link_info.origin_slot; - this.connecting_output = this.connecting_node.outputs[ - this.connecting_slot - ]; - this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot ); - - this.dirty_bgcanvas = true; - skip_action = true; - } - - - }else{ - // has not node - } - - if (!skip_action){ - // connect from in to out, from to to from - this.connecting_node = node; - this.connecting_input = input; - this.connecting_input.slot_index = i; - this.connecting_pos = node.getConnectionPos( true, i ); - this.connecting_slot = i; - - this.dirty_bgcanvas = true; - skip_action = true; - } - } - } - } - } //not resizing - } - - //it wasn't clicked on the links boxes - if (!skip_action) { - var block_drag_node = false; - if(node && node.flags && node.flags.pinned) { - block_drag_node = true; - } - var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; - - //widgets - var widget = this.processNodeWidgets( node, this.graph_mouse, e ); - if (widget) { - block_drag_node = true; - this.node_widget = [node, widget]; - } - - //double clicking - if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) { - //double click node - if (node.onDblClick) { - 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; - } 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; - setTimeout(function() { - that.openSubgraph(node.subgraph); - }, 10); - } - } - - if (this.live_mode) { - clicking_canvas_bg = true; - block_drag_node = true; - } - } - - if (!block_drag_node) { - if (this.allow_dragnodes) { - this.graph.beforeChange(); - this.node_dragged = node; - } - 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); - } - - this.dirty_canvas = true; - } - } //clicked outside of nodes - else { - if (!skip_action){ - //search for link connector - if(!this.read_only) { - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var 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; - } - //link clicked - this.showLinkMenu(link, e); - this.over_link_center = null; //clear tooltip - break; - } - } - - 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; - } - - 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] ] ); - if (dist * this.ds.scale < 10) { - this.selected_group_resizing = true; - } else { - this.selected_group.recomputeInsideNodes(); - } - } - - if (is_double_click && !this.read_only && this.allow_searchbox) { - this.showSearchBox(e); - e.preventDefault(); - e.stopPropagation(); - } - - clicking_canvas_bg = true; - } - } - - if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) { - //console.log("pointerevents: dragging_canvas start"); - this.dragging_canvas = true; - } - - } else if (e.which == 2) { - //middle button - - if (LiteGraph.middle_click_slot_add_default_node){ - if (node && this.allow_interaction && !skip_action && !this.read_only){ - //not dragging mouse to connect two slots - if ( - !this.connecting_node && - !node.flags.collapsed && - !this.live_mode - ) { - var mClikSlot = false; - var mClikSlot_index = false; - var mClikSlot_isOut = 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); - 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; - } - } - } - - //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); - 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; - } - } - } - //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(); - // 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 - ,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical "derive" - ]; - var 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*/ - }); - - } - } - } - } - - } else if (e.which == 3 || this.pointer_is_double) { - - //right button - if (this.allow_interaction && !skip_action && !this.read_only){ - - // is it hover a node ? - if (node){ - 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 - }else{ - // update selection - this.selectNodes([node]); - } - } - - // show menu on this node - this.processContextMenu(node, e); - } - - } - - //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; - - /* - if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - 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(); - } - e.stopPropagation(); - - if (this.onMouseDown) { - this.onMouseDown(e); - } - - return false; - }; - - /** - * Called when a mouse move event has to be processed - * @method processMouseMove - **/ - LGraphCanvas.prototype.processMouseMove = function(e) { - if (this.autoresize) { - this.resize(); - } - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - 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 = [ - 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; - - //console.log("pointerevents: processMouseMove "+e.pointerId+" "+e.isPrimary); - - if(this.block_click) - { - //console.log("pointerevents: processMouseMove block_click"); - e.preventDefault(); - return false; - } - - e.dragging = this.last_mouse_dragging; - - if (this.node_widget) { - this.processNodeWidgets( - this.node_widget[0], - this.graph_mouse, - e, - this.node_widget[1] - ); - this.dirty_canvas = true; - } - - //get node over - var 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; - } - else if (this.selected_group && !this.read_only) - { - //moving/resizing a group - if (this.selected_group_resizing) { - this.selected_group.size = [ - 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); - if (this.selected_group._nodes.length) { - this.dirty_canvas = 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; - } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) { - if (this.connecting_node) { - this.dirty_canvas = true; - } - - //remove mouseover flag - for (var 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; - if (this.node_over && this.node_over.onMouseLeave) { - this.node_over.onMouseLeave(e); - } - this.node_over = null; - this.dirty_canvas = true; - } - } - - //mouse over a node - if (node) { - - if(node.redraw_on_mouse) - 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; - - if (node.onMouseEnter) { - 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 ); - } - - //if dragging a link - if (this.connecting_node) { - - if (this.connecting_output){ - - var 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 ); - if (slot != -1 && node.inputs[slot]) { - var slot_type = node.inputs[slot].type; - if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { - this._highlight_input = pos; - this._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS - } - } else { - this._highlight_input = null; - this._highlight_input_slot = null; // XXX CHECK THIS - } - } - - }else if(this.connecting_input){ - - var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput - - //on top of output - 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.isOverNodeOutput( node, e.canvasX, e.canvasY, pos ); - if (slot != -1 && node.outputs[slot]) { - var slot_type = node.outputs[slot].type; - if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) { - this._highlight_output = pos; - } - } else { - this._highlight_output = null; - } - } - } - } - - //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"; - } - } - } 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; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; - } - over_link = link; - break; - } - if( over_link != this.over_link_center ) - { - this.over_link_center = over_link; - this.dirty_canvas = true; - } - - if (this.canvas) { - 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); - } - - //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. - */ - } - - 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 ); - - this.canvas.style.cursor = "se-resize"; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - } - - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse up event has to be processed - * @method processMouseUp - **/ - LGraphCanvas.prototype.processMouseUp = function(e) { - - var 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; - } - - //console.log("pointerevents: processMouseUp "+e.pointerId+" "+e.isPrimary+" :: "+e.clientX+" "+e.clientY); - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) - return; - - var window = this.getCanvasWindow(); - var 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); - } - - this.adjustMouseEvent(e); - var 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 - } - - //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 ); - } - - //left button - 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); - 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.selected_group = null; - } - this.selected_group_resizing = false; - - var 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); - - //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 - ? this.dragging_rectangle[0] - w - : this.dragging_rectangle[0]; - var 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; - - // 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); - if ( - !overlapBounding( - this.dragging_rectangle, - node_bounding - ) - ) { - continue; - } //out of the visible area - to_select.push(nodeX); - } - if (to_select.length) { - this.selectNodes(to_select,e.shiftKey); // add to selection with shift - } - }else{ - // will select of update selection - this.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey - } - - } - this.dragging_rectangle = null; - } else if (this.connecting_node) { - //dragging a connection - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - - var connInOrOut = this.connecting_output || this.connecting_input; - var connType = connInOrOut.type; - - //node below mouse - if (node) { - - /* no need to condition on event type.. just another type - if ( - connType == LiteGraph.EVENT && - this.isOverNodeBox(node, e.canvasX, e.canvasY) - ) { - - this.connecting_node.connect( - this.connecting_slot, - node, - LiteGraph.EVENT - ); - - } else {*/ - - //slot below mouse? connect - - if (this.connecting_output){ - - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY - ); - if (slot != -1) { - this.connecting_node.connect(this.connecting_slot, node, slot); - } else { - //not on top of an input - // look for a good slot - this.connecting_node.connectByType(this.connecting_slot,node,connType); - } - - }else if (this.connecting_input){ - - var slot = this.isOverNodeOutput( - node, - e.canvasX, - e.canvasY - ); - - if (slot != -1) { - node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like - } else { - //not on top of an input - // look for a good slot - this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType); - } - - } - - - //} - - }else{ - - // add menu when releasing link in empty space - if (LiteGraph.release_link_on_empty_shows_menu){ - if (e.shiftKey && this.allow_searchbox){ - if(this.connecting_output){ - this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type}); - }else if(this.connecting_input){ - this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type}); - } - }else{ - if(this.connecting_output){ - this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e}); - }else if(this.connecting_input){ - this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e}); - } - } - } - } - - this.connecting_output = null; - this.connecting_input = null; - this.connecting_pos = null; - this.connecting_node = null; - this.connecting_slot = -1; - } //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; - } else if (this.node_dragged) { - //node being dragged? - var 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(); - } - - 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(); - } - if( this.onNodeMoved ) - 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( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - if (!node && e.click_time < 300) { - this.deselectAllNodes(); - } - - 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] - ]); - } - } - } else if (e.which == 2) { - //middle button - //trace("middle"); - 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; - } - - /* - if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - if (is_primary) - { - this.pointer_is_down = false; - this.pointer_is_double = false; - } - - this.graph.change(); - - //console.log("pointerevents: processMouseUp stopPropagation"); - e.stopPropagation(); - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse wheel event has to be processed - * @method processMouseWheel - **/ - LGraphCanvas.prototype.processMouseWheel = function(e) { - if (!this.graph || !this.allow_dragcanvas) { - return; - } - - var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - - 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; - - var scale = this.ds.scale; - - if (delta > 0) { - scale *= 1.1; - } else if (delta < 0) { - scale *= 1 / 1.1; - } - - //this.setZoom( scale, [ e.clientX, e.clientY ] ); - this.ds.changeScale(scale, [e.clientX, e.clientY]); - - this.graph.change(); - - e.preventDefault(); - return false; // prevent default - }; - - /** - * returns true if a position (in graph space) is on top of a node little corner box - * @method isOverNodeBox - **/ - LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) { - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - if ( - 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 - **/ - LGraphCanvas.prototype.isOverNodeInput = function( - node, - canvasx, - canvasy, - slot_pos - ) { - 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; - if (node.horizontal) { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 5, - link_pos[1] - 10, - 10, - 20 - ); - } else { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 10, - link_pos[1] - 5, - 40, - 10 - ); - } - if (is_inside) { - if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - } - return -1; - }; - - /** - * returns the INDEX if a position (in graph space) is on top of a node output slot - * @method isOverNodeOuput - **/ - LGraphCanvas.prototype.isOverNodeOutput = function( - node, - canvasx, - canvasy, - slot_pos - ) { - 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; - if (node.horizontal) { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 5, - link_pos[1] - 10, - 10, - 20 - ); - } else { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 10, - link_pos[1] - 5, - 40, - 10 - ); - } - if (is_inside) { - if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - } - return -1; - }; - - /** - * process a key event - * @method processKey - **/ - LGraphCanvas.prototype.processKey = function(e) { - if (!this.graph) { - return; - } - - var block_default = false; - //console.log(e); //debug - - if (e.target.localName == "input") { - return; - } - - if (e.type == "keydown") { - if (e.keyCode == 32) { - //space - this.dragging_canvas = true; - block_default = true; - } - - if (e.keyCode == 27) { - //esc - if(this.node_panel) this.node_panel.close(); - if(this.options_panel) this.options_panel.close(); - block_default = true; - } - - //select all Control A - if (e.keyCode == 65 && e.ctrlKey) { - this.selectNodes(); - block_default = true; - } - - if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) { - //copy - if (this.selected_nodes) { - this.copyToClipboard(); - block_default = true; - } - } - - if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) { - //paste - 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; - } - } - - //collapse - //... - - //TODO - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyDown) { - this.selected_nodes[i].onKeyDown(e); - } - } - } - } else if (e.type == "keyup") { - if (e.keyCode == 32) { - // space - this.dragging_canvas = false; - } - - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyUp) { - this.selected_nodes[i].onKeyUp(e); - } - } - } - } - - this.graph.change(); - - if (block_default) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } - }; - - LGraphCanvas.prototype.copyToClipboard = function() { - var clipboard_info = { - nodes: [], - links: [] - }; - var index = 0; - var selected_nodes_array = []; - for (var i in this.selected_nodes) { - var node = this.selected_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(); - 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; - } - 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) - ); - }; - - LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) { - // 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; - } - - this.graph.beforeChange(); - - //create nodes - var clipboard_info = 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) { - if (posMin){ - if(posMin[0]>clipboard_info.nodes[i].pos[0]){ - 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; - } - } - else{ - 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); - if (node) { - 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; - - this.graph.add(node,{doProcessChange:false}); - - 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; - var origin_node_relative_id = link_info[0]; - if (origin_node_relative_id != null) { - 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]; - if (origin_node_id) { - origin_node = this.graph.getNodeById(origin_node_id); - } - } - var target_node = nodes[link_info[2]]; - if( origin_node && target_node ) - origin_node.connect(link_info[1], target_node, link_info[3]); - else - console.warn("Warning, nodes missing on pasting"); - } - - this.selectNodes(nodes); - - this.graph.afterChange(); - }; - - /** - * process a item drop event on top the canvas - * @method processDrop - **/ - LGraphCanvas.prototype.processDrop = function(e) { - 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 --- - } - - var pos = [e.canvasX, e.canvasY]; - - - var 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; - } - - if (node.onDropFile || node.onDropData) { - var 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); - } - - if (node.onDropData) { - //prepare reader - var reader = new FileReader(); - reader.onload = function(event) { - //console.log(event.target); - var data = event.target.result; - node.onDropData(data, filename, file); - }; - - //read data - var type = file.type.split("/")[0]; - if (type == "text" || type == "") { - reader.readAsText(file); - } else if (type == "image") { - reader.readAsDataURL(file); - } else { - reader.readAsArrayBuffer(file); - } - } - } - } - } - - if (node.onDropItem) { - if (node.onDropItem(event)) { - return true; - } - } - - if (this.onDropItem) { - return this.onDropItem(event); - } - - return false; - }; - - //called if the graph doesn't have a default drop item behaviour - LGraphCanvas.prototype.checkDropItem = function(e) { - 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(); - } - } - }; - - LGraphCanvas.prototype.processNodeDblClicked = function(n) { - if (this.onShowNodePanel) { - this.onShowNodePanel(n); - } - - if (this.onNodeDblClicked) { - this.onNodeDblClicked(n); - } - - this.setDirty(true); - }; - - LGraphCanvas.prototype.processNodeSelected = function(node, e) { - this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select)); - if (this.onNodeSelected) { - this.onNodeSelected(node); - } - }; - - /** - * selects a given node (or adds it to the current selection) - * @method selectNode - **/ - LGraphCanvas.prototype.selectNode = function( - node, - add_to_current_selection - ) { - if (node == null) { - this.deselectAllNodes(); + } else + u.release_link_on_empty_shows_menu && (i.shiftKey && this.allow_searchbox ? this.connecting_output ? this.showSearchBox(i, { node_from: this.connecting_node, slotFrom: this.connecting_output, type_filter_in: this.connecting_output.type }) : this.connecting_input && this.showSearchBox(i, { node_to: this.connecting_node, slotFrom: this.connecting_input, type_filter_out: this.connecting_input.type }) : this.connecting_output ? this.showConnectionMenu({ nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: i }) : this.connecting_input && this.showConnectionMenu({ nodeTo: this.connecting_node, slotTo: this.connecting_input, e: i })); + this.connecting_output = null, this.connecting_input = null, this.connecting_pos = null, this.connecting_node = null, this.connecting_slot = -1; + } else if (this.resizing_node) + this.dirty_canvas = !0, this.dirty_bgcanvas = !0, this.graph.afterChange(this.resizing_node), this.resizing_node = null; + else if (this.node_dragged) { + var h = this.node_dragged; + h && i.click_time < 300 && h.isShowingTitle(!0) && u.isInsideRectangle( + i.canvasX, + i.canvasY, + h.pos[0], + h.pos[1] - u.NODE_TITLE_HEIGHT, + u.NODE_TITLE_HEIGHT, + u.NODE_TITLE_HEIGHT + ) && h.collapse(), this.dirty_canvas = !0, this.dirty_bgcanvas = !0, 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.graph.config.align_to_grid || this.align_to_grid) && this.node_dragged.alignToGrid(), this.onNodeMoved && this.onNodeMoved(this.node_dragged), this.graph.afterChange(this.node_dragged), this.node_dragged = null; } else { - this.selectNodes([node], add_to_current_selection); + var h = this.graph.getNodeOnPos( + i.canvasX, + i.canvasY, + this.visible_nodes + ); + !h && i.click_time < 300 && this.deselectAllNodes(), this.dirty_canvas = !0, this.dragging_canvas = !1, this.node_over && this.node_over.onMouseUp && this.node_over.onMouseUp(i, [i.canvasX - this.node_over.pos[0], i.canvasY - this.node_over.pos[1]], this), this.node_capturing_input && this.node_capturing_input.onMouseUp && this.node_capturing_input.onMouseUp(i, [ + i.canvasX - this.node_capturing_input.pos[0], + i.canvasY - this.node_capturing_input.pos[1] + ], this); } - }; - - /** - * selects several nodes (or adds them to the current selection) - * @method selectNodes - **/ - LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) - { - if (!add_to_current_selection) { - this.deselectAllNodes(); - } - - nodes = nodes || this.graph._nodes; - if (typeof nodes == "string") nodes = [nodes]; - for (var i in nodes) { - var node = nodes[i]; - if (node.is_selected) { - this.deselectNode(node); - continue; - } - - if (!node.is_selected && node.onSelected) { - node.onSelected(); - } - 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; - } - } - if (node.outputs) { - for (var j = 0; j < node.outputs.length; ++j) { - var out = node.outputs[j]; - if (out.links) { - for (var k = 0; k < out.links.length; ++k) { - this.highlighted_links[out.links[k]] = true; - } - } - } - } - } - - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - - this.setDirty(true); - }; - - /** - * removes a node from the current selection - * @method deselectNode - **/ - LGraphCanvas.prototype.deselectNode = function(node) { - if (!node.is_selected) { - return; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - - if (this.onNodeDeselected) { - 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]; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; ++i) { - var out = node.outputs[i]; - if (out.links) { - for (var j = 0; j < out.links.length; ++j) { - delete this.highlighted_links[out.links[j]]; - } - } - } - } - }; - - /** - * removes all nodes from the current selection - * @method deselectAllNodes - **/ - LGraphCanvas.prototype.deselectAllNodes = function() { - if (!this.graph) { - return; - } - var nodes = this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var node = nodes[i]; - if (!node.is_selected) { - continue; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - this.setDirty(true); - }; - - /** - * deletes all nodes in the current selection from the graph - * @method deleteSelectedNodes - **/ - LGraphCanvas.prototype.deleteSelectedNodes = function() { - - this.graph.beforeChange(); - - for (var i in this.selected_nodes) { - var node = this.selected_nodes[i]; - - 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(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); - } - } - 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 - **/ - LGraphCanvas.prototype.centerOnNode = function(node) { - this.ds.offset[0] = - -node.pos[0] - - node.size[0] * 0.5 + - (this.canvas.width * 0.5) / this.ds.scale; - this.ds.offset[1] = - -node.pos[1] - - node.size[1] * 0.5 + - (this.canvas.height * 0.5) / this.ds.scale; - this.setDirty(true, true); - }; - - /** - * adds some useful properties to a mouse event, like the position in graph coordinates - * @method adjustMouseEvent - **/ - LGraphCanvas.prototype.adjustMouseEvent = function(e) { - var clientX_rel = 0; - var clientY_rel = 0; - - 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; - } - - e.deltaX = clientX_rel - this.last_mouse_position[0]; - e.deltaY = clientY_rel- this.last_mouse_position[1]; - - this.last_mouse_position[0] = clientX_rel; - this.last_mouse_position[1] = clientY_rel; - - e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0]; - e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1]; - - //console.log("pointerevents: adjustMouseEvent "+e.clientX+":"+e.clientY+" "+clientX_rel+":"+clientY_rel+" "+e.canvasX+":"+e.canvasY); - }; - - /** - * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom - * @method setZoom - **/ - LGraphCanvas.prototype.setZoom = function(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]; - - var center = this.convertOffsetToCanvas( zooming_center ); - - this.ds.scale = value; - - if(this.scale > this.max_zoom) - this.scale = this.max_zoom; - else if(this.scale < this.min_zoom) - this.scale = this.min_zoom; - - var new_center = this.convertOffsetToCanvas( zooming_center ); - var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - */ - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - }; - - /** - * converts a coordinate from graph coordinates to canvas2D coordinates - * @method convertOffsetToCanvas - **/ - LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) { - return this.ds.convertOffsetToCanvas(pos, out); - }; - - /** - * converts a coordinate from Canvas2D coordinates to graph space - * @method convertCanvasToOffset - **/ - LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) { - return this.ds.convertCanvasToOffset(pos, out); - }; - - //converts event coordinates from canvas2D to graph coordinates - LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) { - var rect = this.canvas.getBoundingClientRect(); - return this.convertCanvasToOffset([ - e.clientX - rect.left, - e.clientY - rect.top - ]); - }; - - /** - * brings a node to front (above all other nodes) - * @method bringToFront - **/ - LGraphCanvas.prototype.bringToFront = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.push(node); - }; - - /** - * sends a node to the back (below all other nodes) - * @method sendToBack - **/ - LGraphCanvas.prototype.sendToBack = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.unshift(node); - }; - - /* Interaction */ - - /* LGraphCanvas render */ - var temp = new Float32Array(4); - - /** - * checks which nodes are visible (inside the camera area) - * @method computeVisibleNodes - **/ - LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) { - 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]; - - //skip rendering nodes in live mode - if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) { - continue; - } - - if (!overlapBounding(this.visible_area, n.getBounding(temp))) { - continue; - } //out of the visible area - - visible_nodes.push(n); - } - 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 - **/ - LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { - 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; - - if (this.graph) { - this.ds.computeVisibleArea(this.viewport); - } - - if ( - this.dirty_bgcanvas || - force_bgcanvas || - this.always_render_background || - (this.graph && - this.graph._last_trigger_time && - now - this.graph._last_trigger_time < 1000) - ) { - this.drawBackCanvas(); - } - - if (this.dirty_canvas || force_canvas) { - this.drawFrontCanvas(); - } - - 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 - **/ - LGraphCanvas.prototype.drawFrontCanvas = function() { - this.dirty_canvas = false; - - if (!this.ctx) { - this.ctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.ctx; - if (!ctx) { - //maybe is using webgl... - return; - } - - var canvas = this.canvas; - if ( ctx.start2D && !this.viewport ) { - 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; - if (area) { - 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] ); - else - ctx.clearRect(0, 0, canvas.width, canvas.height); - } - - //draw bg canvas - if (this.bgcanvas == this.canvas) { - this.drawBackCanvas(); - } else { - ctx.drawImage( this.bgcanvas, 0, 0 ); - } - - //rendering - if (this.onRender) { - this.onRender(canvas, ctx); - } - - //info widget - if (this.show_info) { - this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 ); - } - - if (this.graph) { - //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); - - //draw nodes - var drawn_nodes = 0; - var visible_nodes = this.computeVisibleNodes( - null, - this.visible_nodes - ); - - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - - //transform coords system - ctx.save(); - ctx.translate(node.pos[0], node.pos[1]); - - //Draw - this.drawNode(node, ctx); - drawn_nodes += 1; - - //Restore - ctx.restore(); - } - - //on top (debug) - if (this.render_execution_order) { - this.drawExecutionOrder(ctx); - } - - //connections ontop? - if (this.graph.config.links_ontop) { - if (!this.live_mode) { - this.drawConnections(ctx); - } - } - - //current connection (the one being dragged by the mouse) - if (this.connecting_pos != null) { - ctx.lineWidth = this.connections_width; - var link_color = null; - - var connInOrOut = this.connecting_output || this.connecting_input; - - var connType = connInOrOut.type; - var connDir = connInOrOut.dir; - if(connDir == null) - { - if (this.connecting_output) - connDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; - else - connDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT; - } - var connShape = connInOrOut.shape; - - switch (connType) { - case LiteGraph.EVENT: - link_color = LiteGraph.EVENT_LINK_COLOR; - break; - default: - link_color = LiteGraph.CONNECTING_LINK_COLOR; - } - - //the connection being dragged by the mouse - this.renderLink( - ctx, - this.connecting_pos, - [this.graph_mouse[0], this.graph_mouse[1]], - null, - false, - null, - link_color, - connDir, - LiteGraph.CENTER - ); - - ctx.beginPath(); - if ( - connType === LiteGraph.EVENT || - connShape === LiteGraph.BOX_SHAPE - ) { - ctx.rect( - this.connecting_pos[0] - 6 + 0.5, - this.connecting_pos[1] - 5 + 0.5, - 14, - 10 - ); - 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(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5); - ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5); - ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5); - ctx.closePath(); - } - else { - ctx.arc( - this.connecting_pos[0], - this.connecting_pos[1], - 4, - 0, - Math.PI * 2 - ); - ctx.fill(); - ctx.beginPath(); - ctx.arc( - this.graph_mouse[0], - this.graph_mouse[1], - 4, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - - 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(); - } else { - ctx.arc( - this._highlight_input[0], - this._highlight_input[1], - 6, - 0, - Math.PI * 2 - ); - } - 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(); - } else { - ctx.arc( - this._highlight_output[0], - this._highlight_output[1], - 6, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - } - } - - //the selection rectangle - if (this.dragging_rectangle) { - 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 ); - else - if(this.onDrawLinkTooltip) //to remove - this.onDrawLinkTooltip(ctx,null); - - //custom info - if (this.onDrawForeground) { - this.onDrawForeground(ctx, this.visible_rect); - } - - ctx.restore(); - } - - //draws panel in the corner - if (this._graph_stack && this._graph_stack.length) { - this.drawSubgraphPanel( ctx ); - } - - - if (this.onDrawOverlay) { - this.onDrawOverlay(ctx); - } - - if (area){ - ctx.restore(); - } - - if (ctx.finish2D) { - //this is a function I use in webgl renderer - ctx.finish2D(); - } - }; - - /** - * draws the panel in the corner that shows subgraph properties - * @method drawSubgraphPanel - **/ - LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) { - var subgraph = this.graph; - var subnode = subgraph._subgraph_node; - if (!subnode) { - console.warn("subgraph without subnode"); - return; - } - this.drawSubgraphPanelLeft(subgraph, subnode, ctx) - this.drawSubgraphPanelRight(subgraph, subnode, ctx) + } else + i.which == 2 ? (this.dirty_canvas = !0, this.dragging_canvas = !1) : i.which == 3 && (this.dirty_canvas = !0, this.dragging_canvas = !1); + return n && (this.pointer_is_down = !1, this.pointer_is_double = !1), this.graph.change(), i.stopPropagation(), i.preventDefault(), !1; } - - LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) { - 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); - // var pos = this.mouse; - - if (this.drawButton(w - 20, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; - } - - var 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; - - //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); - 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(); - } - 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); - // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - 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); - } + } + processMouseWheel(t) { + let i = t; + if (!(!this.graph || !this.allow_dragcanvas)) { + var n = i.wheelDeltaY != null ? i.wheelDeltaY : i.detail * -60; + this.adjustMouseEvent(i); + var s = i.clientX, r = i.clientY, o = !this.viewport || this.viewport && s >= this.viewport[0] && s < this.viewport[0] + this.viewport[2] && r >= this.viewport[1] && r < this.viewport[1] + this.viewport[3]; + if (o) { + var a = this.ds.scale; + return n > 0 ? a *= 1.1 : n < 0 && (a *= 1 / 1.1), this.ds.changeScale(a, [i.clientX, i.clientY]), this.graph.change(), i.preventDefault(), !1; + } } - LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) { - 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); - // var pos = this.mouse; - if (this.drawButton(canvas_w - w, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; - } - - var 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; - - //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); - 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(); - } - 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); - // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - 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); - } + } +} +const ie = class { + /** changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom */ + setZoom(e, t) { + this.ds.changeScale(e, t), this.maxZoom && this.ds.scale > this.maxZoom ? this.scale = this.maxZoom : this.minZoom && this.ds.scale < this.minZoom && (this.scale = this.minZoom); + } + /** brings a node to front (above all other nodes) */ + bringToFront(e) { + var t = this.graph._nodes.indexOf(e); + t != -1 && (this.graph._nodes.splice(t, 1), this.graph._nodes.push(e)); + } + /** sends a node to the back (below all other nodes) */ + sendToBack(e) { + var t = this.graph._nodes.indexOf(e); + t != -1 && (this.graph._nodes.splice(t, 1), this.graph._nodes.unshift(e)); + } + /** checks which nodes are visible (inside the camera area) */ + computeVisibleNodes(e, t = []) { + var i = t; + i.length = 0, e = e || this.graph._nodes; + for (var n = 0, s = e.length; n < s; ++n) { + var r = e[n]; + this.live_mode && !r.onDrawBackground && !r.onDrawForeground || u.overlapBounding(this.visible_area, r.getBounding(ie.temp)) && i.push(r); } - //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm - LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor ) - { - 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; - if(pos) { - var rect = this.canvas.getBoundingClientRect(); - pos[0] -= rect.left; - pos[1] -= rect.top; + return i; + } + /** 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(e = !1, t = !1) { + if (!(!this.canvas || this.canvas.width == 0 || this.canvas.height == 0)) { + var i = u.getTime(); + this.render_time = (i - this.last_draw_time) * 1e-3, this.last_draw_time = i, this.graph && this.ds.computeVisibleArea(this.viewport), (this.dirty_bgcanvas || t || this.always_render_background || this.graph && this.graph._last_trigger_time && i - this.graph._last_trigger_time < 1e3) && this.drawBackCanvas(), (this.dirty_canvas || e) && this.drawFrontCanvas(), this.fps = this.render_time ? 1 / this.render_time : 0, this.frame += 1; + } + } + /** draws the front canvas (the one containing all the nodes) */ + drawFrontCanvas() { + this.dirty_canvas = !1, this.ctx || (this.ctx = this.canvas.getContext("2d")); + var e = this.ctx; + if (e) { + var t = this.canvas, i = this.viewport || this.dirty_area; + if (i && (e.save(), e.beginPath(), e.rect(i[0], i[1], i[2], i[3]), e.clip()), this.clear_background && (i ? e.clearRect(i[0], i[1], i[2], i[3]) : e.clearRect(0, 0, t.width, t.height)), this.bgcanvas == this.canvas ? this.drawBackCanvas() : e.drawImage(this.bgcanvas, 0, 0), this.onRender && this.onRender(t, e), this.show_info && this.renderInfo(e, i ? i[0] : 0, i ? i[1] : 0), this.graph) { + e.save(), this.ds.toCanvasContext(e); + for (var n = this.computeVisibleNodes( + null, + this.visible_nodes + ), s = 0; s < n.length; ++s) { + var r = n[s]; + e.save(), e.translate(r.pos[0], r.pos[1]), this.drawNode(r, e), e.restore(); } - var 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(); - - 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"; - } - } - - var was_clicked = clicked && !this.block_click; - if(clicked) - this.blockClick(); - return was_clicked; - } - - LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click ) - { - 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; - } - - /** - * draws some useful stats in the corner of the canvas - * @method renderInfo - **/ - LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { - 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); - } else { - ctx.fillText("No graph selected", 5, 13 * 1); - } - ctx.restore(); - }; - - /** - * draws the back canvas (the one containing the background and the connections) - * @method drawBackCanvas - **/ - LGraphCanvas.prototype.drawBackCanvas = function() { - var canvas = this.bgcanvas; - if ( - 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(); - } - - var 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] ); - } - - //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) { - title += - this._graph_stack[i]._subgraph_node.getTitle() + " >> "; - } - ctx.fillText( - title + subgraph_node.getTitle(), - canvas.width * 0.5, - 40 - ); - ctx.restore(); - } - - var bg_already_painted = false; - if (this.onRenderBackground) { - bg_already_painted = this.onRenderBackground(canvas, ctx); - } - - //reset in case of error - if ( !this.viewport ) - { - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - this.visible_links.length = 0; - - if (this.graph) { - //apply transformations - 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.fillRect( - this.visible_area[0], - this.visible_area[1], - this.visible_area[2], - this.visible_area[3] - ); - } - - if ( - this.background_image && - this.ds.scale > 0.5 && - !bg_already_painted - ) { - if (this.zoom_modify_alpha) { - ctx.globalAlpha = - (1.0 - 0.5 / this.ds.scale) * this.editor_alpha; - } else { - ctx.globalAlpha = this.editor_alpha; - } - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = - 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.onload = function() { - 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; - } - if (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.globalAlpha = 1.0; - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled - } - - //groups - if (this.graph._groups.length && !this.live_mode) { - this.drawGroups(canvas, ctx); - } - - if (this.onDrawBackground) { - this.onDrawBackground(ctx, this.visible_area); - } - if (this.onBackgroundRender) { - //LEGACY - console.error( - "WARNING! onBackgroundRender deprecated, now is named onDrawBackground " - ); - this.onBackgroundRender = null; - } - - //DEBUG: show clipping area - //ctx.fillStyle = "red"; - //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); - } - - if (this.render_connections_shadows) { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 6; - } else { - ctx.shadowColor = "rgba(0,0,0,0)"; - } - - //draw connections - if (!this.live_mode) { - this.drawConnections(ctx); - } - - ctx.shadowColor = "rgba(0,0,0,0)"; - - //restore state - ctx.restore(); - } - - if (ctx.finish) { - ctx.finish(); - } - - this.dirty_bgcanvas = false; - this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas - }; - - var temp_vec2 = new Float32Array(2); - - /** - * draws the given node inside the canvas - * @method drawNode - **/ - LGraphCanvas.prototype.drawNode = function(node, ctx) { - 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 - - //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); - } - } - return; - } - - var 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; - } else { - 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; - } - - //clip if required (mask) - var shape = node._shape || LiteGraph.BOX_SHAPE; - var size = temp_vec2; - temp_vec2.set(node.size); - var horizontal = node.horizontal; // || node.flags.horizontal; - - if (node.flags.collapsed) { - ctx.font = this.inner_text_font; - var 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; - } - } - - if (node.clip_area) { - //Start clipping - ctx.save(); - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect(0, 0, size[0], size[1]); - } else if (shape == LiteGraph.ROUND_SHAPE) { - ctx.roundRect(0, 0, size[0], size[1], [10]); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - ctx.clip(); - } - - //draw shape - if (node.has_errors) { - bgcolor = "red"; - } - this.drawNodeShape( - node, - ctx, - size, - color, - bgcolor, - node.is_selected, - node.mouseOver - ); - ctx.shadowColor = "transparent"; - - //draw foreground - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } - - //connection slots - ctx.textAlign = horizontal ? "center" : "left"; - ctx.font = this.inner_text_font; - - var render_text = !low_quality; - - var out_slot = this.connecting_output; - var in_slot = this.connecting_input; - ctx.lineWidth = 1; - - var max_y = 0; - var 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]; - - var slot_type = slot.type; - var slot_shape = slot.shape; - - ctx.globalAlpha = editor_alpha; - //change opacity of incompatible slots when dragging a connection - if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - ctx.fillStyle = - slot.link != null - ? slot.color_on || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.input_on - : slot.color_off || - this.default_connection_color_byTypeOff[slot_type] || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.input_off; - - var 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; - } - - ctx.beginPath(); - - if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead? - } - - var doStroke = true; - - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot_shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else if (slot_shape === LiteGraph.GRID_SHAPE) { - ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); - ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); - ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); - doStroke = false; - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - ctx.fill(); - - //render name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.UP) { - ctx.fillText(text, pos[0], pos[1] - 10); - } else { - ctx.fillText(text, pos[0] + 10, pos[1] + 5); - } - } - } - } - } - - //output connection slots - - 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]; - - var slot_type = slot.type; - var slot_shape = slot.shape; - - //change opacity of incompatible slots when dragging a connection - if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - var 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; - } - - ctx.fillStyle = - slot.links && slot.links.length - ? slot.color_on || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.output_on - : slot.color_off || - this.default_connection_color_byTypeOff[slot_type] || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.output_off; - ctx.beginPath(); - //ctx.rect( node.size[0] - 14,i*14,10,10); - - if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; - } - - var doStroke = true; - - if ( - slot_type === LiteGraph.EVENT || - slot_shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot_shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else if (slot_shape === LiteGraph.GRID_SHAPE) { - ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); - ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); - ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); - doStroke = false; - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - - //trigger - //if(slot.node_id != null && slot.slot == -1) - // ctx.fillStyle = "#F85"; - - //if(slot.links != null && slot.links.length) - ctx.fill(); - if(!low_quality && doStroke) - ctx.stroke(); - - //render output name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.DOWN) { - ctx.fillText(text, pos[0], pos[1] - 8); - } else { - ctx.fillText(text, pos[0] - 10, pos[1] + 5); - } - } - } - } - } - - ctx.textAlign = "left"; - ctx.globalAlpha = 1; - - if (node.widgets) { - var widgets_y = max_y; - if (horizontal || node.widgets_up) { - widgets_y = 2; - } - if( node.widgets_start_y != null ) - widgets_y = node.widgets_start_y; - this.drawNodeWidgets( - node, - widgets_y, - ctx, - 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; - - //get first connected slot to render - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link == null) { - continue; - } - input_slot = slot; - break; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (!slot.links || !slot.links.length) { - continue; - } - output_slot = slot; - } - } - - if (input_slot) { - var x = 0; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = -LiteGraph.NODE_TITLE_HEIGHT; - } - 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); - } 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(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - } - - if (output_slot) { - var x = node._collapsed_width; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = 0; - } - 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); - } 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(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - //ctx.stroke(); - } - } - - if (node.clip_area) { - ctx.restore(); - } - - ctx.globalAlpha = 1.0; - }; - - //used by this.over_link_center - LGraphCanvas.prototype.drawLinkTooltip = function( ctx, link ) - { - var 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; - - if(this.onDrawLinkTooltip) - if( this.onDrawLinkTooltip(ctx,link,this) == true ) - return; - - var data = link.data; - var text = null; - - if( data.constructor === Number ) - text = data.toFixed(2); - else if( data.constructor === String ) - text = "\"" + data + "\""; - else if( data.constructor === Boolean ) - text = String(data); - else if (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); - } - - /** - * draws the shape of the given node in the canvas - * @method drawNodeShape - **/ - var tmp_area = new Float32Array(4); - - LGraphCanvas.prototype.drawNodeShape = function( - node, - ctx, - size, - fgcolor, - bgcolor, - selected, - mouse_over - ) { - //bg rect - ctx.strokeStyle = fgcolor; - ctx.fillStyle = bgcolor; - - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - var low_quality = this.ds.scale < 0.5; - - //render node area depending on shape - var shape = - node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; - - var title_mode = node.constructor.title_mode; - - var render_title = true; - if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) { - render_title = false; - } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) { - render_title = true; - } - - var area = 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; - - //full node shape - //if(node.flags.collapsed) - { - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.fillRect(area[0], area[1], area[2], area[3]); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - shape == LiteGraph.CARD_SHAPE - ) { - ctx.roundRect( - area[0], - area[1], - 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, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - 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"; - - if (node.onDrawBackground) { - 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 ); - } else if ( - title_mode != LiteGraph.TRANSPARENT_TITLE && - (node.constructor.title_color || this.render_title_colored) - ) { - var title_color = node.constructor.title_color || fgcolor; - - if (node.flags.collapsed) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - } - - //* gradient test - if (this.use_gradients) { - var 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"); - } - ctx.fillStyle = grad; - } else { - ctx.fillStyle = title_color; - } - - //ctx.globalAlpha = 0.5 * old_alpha; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.rect(0, -title_height, size[0] + 1, title_height); - } else if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) { - ctx.roundRect( - 0, - -title_height, - size[0] + 1, - title_height, - node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0] - ); - } - ctx.fill(); - ctx.shadowColor = "transparent"; - } - - var colState = false; - if (LiteGraph.node_box_coloured_by_mode){ - if(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); - } - - //title box - var box_size = 10; - if (node.onDrawTitleBox) { - 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.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5 + 1, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - - 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 ); - else - { - ctx.beginPath(); - ctx.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - } else { - if (low_quality) { - 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.fillRect( - (title_height - box_size) * 0.5, - (title_height + box_size) * -0.5, - box_size, - box_size - ); - } - ctx.globalAlpha = old_alpha; - - //title text - if (node.onDrawTitleText) { - node.onDrawTitleText( - ctx, - title_height, - size, - this.ds.scale, - this.title_text_font, - selected - ); - } - if (!low_quality) { - ctx.font = this.title_text_font; - var title = String(node.getTitle()); - if (title) { - if (selected) { - ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR; - } else { - ctx.fillStyle = - node.constructor.title_text_color || - this.node_title_color; - } - if (node.flags.collapsed) { - ctx.textAlign = "left"; - var 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"; - } else { - 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); - else - { - 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(); - } - - //custom title render - if (node.onDrawTitle) { - node.onDrawTitle(ctx); - } - } - - //render selection marker - if (selected) { - if (node.onBounding) { - node.onBounding(area); - } - - if (title_mode == LiteGraph.TRANSPARENT_TITLE) { - area[1] -= title_height; - area[3] += title_height; - } - ctx.lineWidth = 1; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3] - ); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) - ) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - [this.round_radius * 2] - ); - } else if (shape == LiteGraph.CARD_SHAPE) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - [this.round_radius * 2,2,this.round_radius * 2,2] - ); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5 + 6, - 0, - Math.PI * 2 - ); - } - ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR; - ctx.stroke(); - ctx.strokeStyle = fgcolor; - ctx.globalAlpha = 1; - } - - // 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--; - }; - - var margin_area = new Float32Array(4); - var link_bounding = new Float32Array(4); - var tempA = new Float32Array(2); - var tempB = new Float32Array(2); - - /** - * draws every connection visible in the canvas - * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time - * @method drawConnections - **/ - LGraphCanvas.prototype.drawConnections = function(ctx) { - var now = LiteGraph.getTime(); - var visible_area = this.visible_area; - margin_area[0] = visible_area[0] - 20; - margin_area[1] = visible_area[1] - 20; - margin_area[2] = visible_area[2] + 40; - margin_area[3] = visible_area[3] + 40; - - //draw connections - ctx.lineWidth = this.connections_width; - - 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]; - //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; - } - - 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; - } - - //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; - 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, - tempA - ); - } - var end_node_slotpos = node.getConnectionPos(true, i, tempB); - - //compute link bounding - link_bounding[0] = start_node_slotpos[0]; - link_bounding[1] = start_node_slotpos[1]; - link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0]; - link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1]; - if (link_bounding[2] < 0) { - link_bounding[0] += link_bounding[2]; - link_bounding[2] = Math.abs(link_bounding[2]); - } - if (link_bounding[3] < 0) { - link_bounding[1] += link_bounding[3]; - link_bounding[3] = Math.abs(link_bounding[3]); - } - - //skip links outside of the visible area of the canvas - if (!overlapBounding(link_bounding, 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); - - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - false, - 0, - 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; - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - true, - f, - "white", - start_dir, - end_dir - ); - ctx.globalAlpha = tmp; - } - } - } - 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) - **/ - LGraphCanvas.prototype.renderLink = function( - ctx, - a, - b, - link, - skip_border, - flow, - color, - start_dir, - end_dir, - num_sublines - ) { - if (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; - } - if (link != null && this.highlighted_links[link.id]) { - color = "#FFF"; - } - - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; - - var dist = distance(a, b); - - if (this.render_connections_border && this.ds.scale > 0.6) { - ctx.lineWidth = this.connections_width + 4; - } - ctx.lineJoin = "round"; - num_sublines = num_sublines || 1; - if (num_sublines > 1) { - ctx.lineWidth = 0.5; - } - - //begin line shape - ctx.beginPath(); - for (var i = 0; i < num_sublines; i += 1) { - var offsety = (i - (num_sublines - 1) * 0.5) * 5; - - if (this.links_render_mode == LiteGraph.SPLINE_LINK) { - ctx.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; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - start_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - start_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - start_offset_y = dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - end_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - end_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - end_offset_y = dist * 0.25; - break; - } - ctx.bezierCurveTo( - a[0] + start_offset_x, - a[1] + start_offset_y + offsety, - b[0] + end_offset_x, - b[1] + end_offset_y + offsety, - b[0], - b[1] + offsety - ); - } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { - ctx.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; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = -1; - break; - case LiteGraph.RIGHT: - start_offset_x = 1; - break; - case LiteGraph.UP: - start_offset_y = -1; - break; - case LiteGraph.DOWN: - start_offset_y = 1; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = -1; - break; - case LiteGraph.RIGHT: - end_offset_x = 1; - break; - case LiteGraph.UP: - end_offset_y = -1; - break; - case LiteGraph.DOWN: - end_offset_y = 1; - break; - } - var l = 15; - ctx.lineTo( - a[0] + start_offset_x * l, - a[1] + start_offset_y * l + offsety - ); - ctx.lineTo( - b[0] + end_offset_x * l, - b[1] + end_offset_y * l + offsety - ); - ctx.lineTo(b[0], b[1] + offsety); - } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) { - ctx.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]; - if (start_dir == LiteGraph.RIGHT) { - start_x += 10; - } else { - start_y += 10; - } - if (end_dir == LiteGraph.LEFT) { - end_x -= 10; - } else { - end_y -= 10; - } - ctx.lineTo(start_x, start_y); - ctx.lineTo((start_x + end_x) * 0.5, start_y); - ctx.lineTo((start_x + end_x) * 0.5, end_y); - ctx.lineTo(end_x, end_y); - ctx.lineTo(b[0], b[1]); - } else { - return; - } //unknown - } - - //rendering the outline of the connection can be a little bit slow - if ( - this.render_connections_border && - this.ds.scale > 0.6 && - !skip_border - ) { - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - } - - ctx.lineWidth = this.connections_width; - ctx.fillStyle = ctx.strokeStyle = color; - ctx.stroke(); - //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]; - } - - //render arrow in the middle - if ( - this.ds.scale >= 0.6 && - this.highquality_render && - end_dir != LiteGraph.CENTER - ) { - //render arrow - if (this.render_connection_arrows) { - //compute two points in the connection - var posA = this.computeConnectionPoint( - a, - b, - 0.25, - start_dir, - end_dir - ); - var posB = this.computeConnectionPoint( - a, - b, - 0.26, - start_dir, - end_dir - ); - var posC = this.computeConnectionPoint( - a, - b, - 0.75, - start_dir, - end_dir - ); - var 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; - 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]); - } else { - 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(); - } - - //circle - 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( - a, - b, - f, - start_dir, - end_dir - ); - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI); - ctx.fill(); - } - } - }; - - //returns the link center point based on curvature - LGraphCanvas.prototype.computeConnectionPoint = function( - a, - b, - t, - start_dir, - end_dir - ) { - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = 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; - - switch (start_dir) { - case LiteGraph.LEFT: - p1[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p1[0] += dist * 0.25; - break; - case LiteGraph.UP: - p1[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - p1[1] += dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - p2[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p2[0] += dist * 0.25; - break; - case LiteGraph.UP: - p2[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - 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; - - 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]; - }; - - LGraphCanvas.prototype.drawExecutionOrder = function(ctx) { - ctx.shadowColor = "transparent"; - ctx.globalAlpha = 0.25; - - 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"; - 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.fillText( - node.order, - node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5, - node.pos[1] - 6 - ); - } - ctx.globalAlpha = 1; - }; - - /** - * draws the widgets stored inside a node - * @method drawNodeWidgets - **/ - LGraphCanvas.prototype.drawNodeWidgets = function( - node, - posY, - ctx, - active_widget - ) { - 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; - } - 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; - - switch (w.type) { - case "button": - ctx.fillStyle = background_color; - if (w.clicked) { - ctx.fillStyle = "#AAA"; - w.clicked = false; - this.dirty_canvas = true; - } - ctx.fillRect(margin, y, widget_width - margin * 2, H); - if(show_text && !w.disabled) - 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); - } - break; - case "toggle": - 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(); - 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(); - if (show_text) { - 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 = 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); - if(show_text && !w.disabled) - 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 ); - } - if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText( - w.label || w.name + " " + Number(w.value).toFixed( - w.options.precision != null - ? w.options.precision - : 3 - ), - widget_width * 0.5, - y + H * 0.7 - ); - } - break; - case "number": - case "combo": - 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(); - if (show_text) { - if(!w.disabled) - 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.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( - w.options.precision !== undefined - ? w.options.precision - : 3 - ), - widget_width - margin * 2 - 20, - y + H * 0.7 - ); - } else { - var v = w.value; - if( w.options.values ) - { - var values = w.options.values; - if( values.constructor === Function ) - values = values(); - if(values && values.constructor !== Array) - v = values[ w.value ]; - } - ctx.fillText( - v, - widget_width - margin * 2 - 20, - y + H * 0.7 - ); - } - } - 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]); - else - 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.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; - default: - if (w.draw) { - w.draw(ctx, node, widget_width, y, H); - } - break; - } - posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4; - ctx.globalAlpha = this.editor_alpha; - - } - ctx.restore(); - ctx.textAlign = "left"; - }; - - /** - * process an event on widgets - * @method processNodeWidgets - **/ - LGraphCanvas.prototype.processNodeWidgets = function( - node, - pos, - event, - active_widget - ) { - if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { - 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(); - - for (var i = 0; i < node.widgets.length; ++i) { - var 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; - //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; - - var 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": - if (event.type === LiteGraph.pointerevents_method+"down") { - if (w.callback) { - setTimeout(function() { - w.callback(w, that, node, pos, event); - }, 20); - } - 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; - if (old_value != w.value) { - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - 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; - } - } - 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); - if ( w.options.min != null && 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; - } - } else if (event.type == LiteGraph.pointerevents_method+"down") { - var values = w.options.values; - if (values && values.constructor === Function) { - values = w.options.values(w, node); - } - var values_list = null; - - if( w.type != "number") - values_list = values.constructor === Array ? values : Object.keys(values); - - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; - if (w.type == "number") { - w.value += delta * 0.1 * (w.options.step || 1); - if ( w.options.min != null && 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; - } - } 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; - } else { //combo clicked - var text_values = values != values_list ? Object.values(values) : values; - var menu = 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) { - if(values != values_list) - 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; - 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) { } - } - this.value = Number(v); - inner_value_change(this, this.value); - }.bind(w), - event); - } - } - - if( old_value != w.value ) - setTimeout( - function() { - inner_value_change(this, this.value); - }.bind(w), - 20 - ); - this.dirty_canvas = true; - break; - case "toggle": - if (event.type == LiteGraph.pointerevents_method+"down") { - w.value = !w.value; - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - break; - case "string": - case "text": - if (event.type == LiteGraph.pointerevents_method+"down") { - this.prompt("Value",w.value,function(v) { - inner_value_change(this, v); - }.bind(w), - event,w.options ? w.options.multiline : false ); - } - break; - default: - 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++; - } - - 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); - } - } - - return null; - }; - - /** - * draws every group area in the background - * @method drawGroups - **/ - LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { - if (!this.graph) { - return; - } - - var groups = this.graph._groups; - - ctx.save(); - ctx.globalAlpha = 0.5 * this.editor_alpha; - - for (var i = 0; i < groups.length; ++i) { - var group = groups[i]; - - if (!overlapBounding(this.visible_area, group._bounding)) { - continue; - } //out of the visible area - - ctx.fillStyle = group.color || "#335"; - ctx.strokeStyle = group.color || "#335"; - var pos = group._pos; - var size = group._size; - ctx.globalAlpha = 0.25 * this.editor_alpha; - ctx.beginPath(); - ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]); - ctx.fill(); - ctx.globalAlpha = this.editor_alpha; - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(pos[0] + size[0], pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10); - ctx.fill(); - - var font_size = - group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; - ctx.font = font_size + "px Arial"; - ctx.textAlign = "left"; - ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size); - } - - ctx.restore(); - }; - - LGraphCanvas.prototype.adjustNodesSize = function() { - var nodes = this.graph._nodes; - for (var i = 0; i < nodes.length; ++i) { - nodes[i].size = nodes[i].computeSize(); - } - 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 - **/ - LGraphCanvas.prototype.resize = function(width, height) { - 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); - }; - - /** - * 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 - **/ - LGraphCanvas.prototype.switchLiveMode = function(transition) { - if (!transition) { - 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; - if (this.live_mode) { - 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; - - if (delta < 1 && self.editor_alpha < 0.01) { - clearInterval(t); - if (delta < 1) { - self.live_mode = true; - } - } - if (delta > 1 && self.editor_alpha > 0.99) { - clearInterval(t); - self.editor_alpha = 1; - } - }, 1); - }; - - LGraphCanvas.prototype.onNodeSelectionChange = function(node) { - return; //disabled - }; - - /* this is an implementation for touch not in production and not ready - */ - /*LGraphCanvas.prototype.touchHandler = function(event) { - //alert("foo"); - var touches = event.changedTouches, - first = touches[0], - type = ""; - - switch (event.type) { - case "touchstart": - type = "mousedown"; - break; - case "touchmove": - type = "mousemove"; - break; - case "touchend": - type = "mouseup"; - break; + if (this.render_execution_order && this.drawExecutionOrder(e), this.graph.config.links_ontop && (this.live_mode || this.drawConnections(e)), this.connecting_pos != null) { + e.lineWidth = this.connections_width; + var o = null, a = this.connecting_output || this.connecting_input, l = a.type, h = a.dir; + h == null && (this.connecting_output ? h = this.connecting_node.horizontal ? w.DOWN : w.RIGHT : h = this.connecting_node.horizontal ? w.UP : w.LEFT); + var p = a.shape; + switch (l) { + case I.EVENT: + o = u.EVENT_LINK_COLOR; + break; default: - return; - } - - //initMouseEvent(type, canBubble, cancelable, view, clickCount, - // screenX, screenY, clientX, clientY, ctrlKey, - // altKey, shiftKey, metaKey, button, relatedTarget); - - // this is eventually a Dom object, get the LGraphCanvas back - if(typeof this.getCanvasWindow == "undefined"){ - var window = this.lgraphcanvas.getCanvasWindow(); - }else{ - var window = this.getCanvasWindow(); - } - - var document = window.document; - - var simulatedEvent = document.createEvent("MouseEvent"); - simulatedEvent.initMouseEvent( - type, - true, - true, - window, - 1, - first.screenX, - first.screenY, - first.clientX, - first.clientY, - false, - false, - false, - false, - 0, //left - null - ); - first.target.dispatchEvent(simulatedEvent); - event.preventDefault(); - };*/ - - /* CONTEXT MENU ********************/ - - LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) { - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var 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}} - */ - LGraphCanvas.getBoundaryNodes = function(nodes) { - 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; - - if (top === null || y < top.pos[1]) { - top = node; - } - if (right === null || x + width > right.pos[0] + right.size[0]) { - right = node; - } - if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) { - bottom = node; - } - if (left === null || x < left.pos[0]) { - left = node; - } - } - - return { - "top": top, - "right": right, - "bottom": bottom, - "left": left - }; - } - /** - * Determines the furthest nodes in each direction for the currently selected nodes - * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} - */ - LGraphCanvas.prototype.boundaryNodesForSelection = function() { - return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)); - } - - /** - * - * @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) - */ - LGraphCanvas.alignNodes = function (nodes, direction, align_to) { - if (!nodes) { - return; - } - - const canvas = LGraphCanvas.active_canvas; - let boundaryNodes = [] - if (align_to === undefined) { - 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)) { - switch (direction) { - case "right": - 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; - case "top": - 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; - } - } - - canvas.dirty_canvas = true; - canvas.dirty_bgcanvas = true; - }; - - LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) { - 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); - } - } - - LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) { - 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()); - } - } - - LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) { - - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - var graph = canvas.graph; - if (!graph) - return; - - function inner_onMenuAdded(base_category ,prev_menu){ - - var categories = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)}); - var entries = []; - - categories.map(function(category){ - - if (!category) - 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 + '/'; - - var 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]; - - var 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) - }}); - } - - }); - - var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter ); - nodes.map(function(node){ - - if (node.skip_list) - return; - - var 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); - if (node) { - node.pos = canvas.convertEventToCanvasOffset(first_event); - canvas.graph.add(node); - } - if(callback) - callback(node); - canvas.graph.afterChange(); - - } - } - - entries.push(entry); - - }); - - new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window ); - - } - - inner_onMenuAdded('',prev_menu); - return false; - - }; - - LGraphCanvas.onMenuCollapseAll = function() {}; - - LGraphCanvas.onMenuNodeEdit = function() {}; - - LGraphCanvas.showMenuNodeOptionalInputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_inputs; - if (node.onGetInputs) { - options = node.onGetInputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - entries.push(null); - continue; - } - var label = entry[0]; - if(!entry[2]) - entry[2] = {}; - - if (entry[2].label) { - label = entry[2].label; - } - - entry[2].removable = true; - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.ACTION) { - data.className = "event"; - } - entries.push(data); - } - } - - if (node.onMenuNodeInputs) { - var retEntries = node.onMenuNodeInputs(entries); - if(retEntries) entries = retEntries; - } - - if (!entries.length) { - console.log("no input entries"); - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - 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]); - - if (node.onNodeInputAdd) { // callback to the node when adding a slot - node.onNodeInputAdd(v.value); - } - node.setDirtyCanvas(true, true); - node.graph.afterChange(); - } - } - - return false; - }; - - LGraphCanvas.showMenuNodeOptionalOutputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_outputs; - if (node.onGetOutputs) { - options = node.onGetOutputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - //separator? - entries.push(null); - continue; - } - - if ( - node.flags && - node.flags.skip_repeated_outputs && - node.findOutputSlot(entry[0]) != -1 - ) { - continue; - } //skip the ones already on - var label = entry[0]; - if(!entry[2]) - entry[2] = {}; - if (entry[2].label) { - label = entry[2].label; - } - entry[2].removable = true; - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.EVENT) { - data.className = "event"; - } - entries.push(data); - } - } - - 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: {} - } - } - // add callback for modifing the menu elements onMenuNodeOutputs - if (node.onMenuNodeOutputs) { - var retEntries = node.onMenuNodeOutputs(entries); - if(retEntries) entries = retEntries; - } - - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } - - if (!v.value) { - return; - } - - var 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] }); - } - new LiteGraph.ContextMenu(entries, { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }); - return false; - } else { - 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(); - } - } - - return false; - }; - - LGraphCanvas.onShowMenuNodeProperties = function( - value, - options, - e, - prev_menu, - node - ) { - if (!node || !node.properties) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var entries = []; - for (var i in node.properties) { - var value = node.properties[i] !== undefined ? node.properties[i] : " "; - if( typeof value == "object" ) - value = JSON.stringify(value); - var info = node.getPropertyInfo(i); - if(info.type == "enum" || info.type == "combo") - value = LGraphCanvas.getPropertyPrintableValue( value, info.values ); - - //value could contain invalid html characters, clean that - value = LGraphCanvas.decodeHTML(value); - entries.push({ - content: - "" + - (info.label ? info.label : i) + - "" + - "" + - value + - "", - value: i - }); - } - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - allow_html: true, - node: node - }, - ref_window - ); - - function inner_clicked(v, options, e, prev) { - if (!node) { - return; - } - var rect = this.getBoundingClientRect(); - canvas.showEditPropertyValue(node, v.value, { - position: [rect.left, rect.top] - }); - } - - return false; - }; - - LGraphCanvas.decodeHTML = function(str) { - var e = document.createElement("div"); - e.innerText = str; - return e.innerHTML; - }; - - LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) { - if (!node) { - return; - } - - var fApplyMultiNode = function(node){ - node.size = node.computeSize(); - if (node.onResize) - node.onResize(node.size); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.prototype.showLinkMenu = function(link, e) { - 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, { - event: e, - title: link.data != null ? link.data.constructor.name : null, - callback: inner_clicked - }); - - function inner_clicked(v,options,e) { - 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; - } - // leave the connection type checking inside connectByType - 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; - } - }); - break; - - case "Delete": - that.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; - }; - - LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection - var optPass = optPass || {}; - var opts = Object.assign({ nodeFrom: null // input - ,slotFrom: null // input - ,nodeTo: null // output - ,slotTo: null // output - ,position: [] // pass the event coords - ,nodeType: null // choose a nodetype to add, AUTO to set at first good - ,posAdd:[0,0] // adjust x,y - ,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h - } - ,optPass - ); - var that = this; - - var isFrom = opts.nodeFrom && opts.slotFrom!==null; - var 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; - } - if (!opts.nodeType){ - console.warn("No type to createDefaultNodeForSlot"); - return false; - } - - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; - - var iSlotConn = 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; - case "object": - // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; - case "number": - 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; - } - - if (slotX===false || iSlotConn===false){ - 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 - } - nodeNewType = false; - if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ - for(var typeX in slotTypesDefault[fromSlotType]){ - if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){ - nodeNewType = slotTypesDefault[fromSlotType][typeX]; - // console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType); - break; // -------- - } - } - }else{ - if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType]; - } - if (nodeNewType) { - var nodeNewOpts = false; - if (typeof nodeNewType == "object" && nodeNewType.node){ - nodeNewOpts = nodeNewType; - nodeNewType = nodeNewType.node; - } - - //that.graph.beforeChange(); - - var 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] ); - } - } - if (nodeNewOpts.inputs) { - newNode.inputs = []; - for (var 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.addOutput( - nodeNewOpts.outputs[i][0], - nodeNewOpts.outputs[i][1] - ); - } - } - if (nodeNewOpts.title) { - newNode.title = nodeNewOpts.title; - } - if (nodeNewOpts.json) { - newNode.configure(nodeNewOpts.json); - } - - } - - // add the node - 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];*/ - - //that.graph.afterChange(); - - // connect the two! - if (isFrom){ - opts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType ); - }else{ - opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType ); - } - - // if connecting in between - if (isFrom && isTo){ - // TODO - } - - return true; - - }else{ - console.log("failed creating "+nodeNewType); - } - } - } - return false; - } - - LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection - var optPass = optPass || {}; - var opts = Object.assign({ nodeFrom: null // input - ,slotFrom: null // input - ,nodeTo: null // output - ,slotTo: null // output - ,e: null - } - ,optPass - ); - var that = this; - - var isFrom = opts.nodeFrom && opts.slotFrom; - var isTo = !isFrom && opts.nodeTo && opts.slotTo; - - if (!isFrom && !isTo){ - console.warn("No data passed to showConnectionMenu"); - return false; - } - - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; - - var iSlotConn = 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; - case "object": - // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; - case "number": - iSlotConn = slotX; - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - default: - // bad ? - //iSlotConn = 0; - console.warn("Cant get slot information "+slotX); - return false; - } - - var options = ["Add Node",null]; - - if (that.allow_searchbox){ - 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]); - } - }else{ - options.push(slotTypesDefault[fromSlotType]); - } - } - - // build menu - var 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) { - //console.log("Process showConnectionMenu selection"); - switch (v) { - case "Add Node": - LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - if (isFrom){ - opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType ); - }else{ - opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType ); - } - }); - break; - case "Search": - if(isFrom){ - that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType}); - }else{ - that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType}); - } - break; - default: - // check for defaults nodes for this slottype - var 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; - } - } - - return false; - }; - - // TODO refactor :: this is used fot title but not for properties! - LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) { - var input_html = ""; - var property = item.property || "title"; - var value = node[property]; - - // TODO refactor :: use createDialog ? - - var dialog = 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"); - if (input) { - input.value = value; - input.addEventListener("blur", function(e) { - this.focus(); - }); - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - inner(); // save - } else if (e.keyCode != 13 && e.target.localName != "textarea") { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - } - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - 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"; - } - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - canvas.parentNode.appendChild(dialog); - - if(input) input.focus(); - - var dialogCloseTimer = null; - dialog.addEventListener("mouseleave", function(e) { - 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) { - if(LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - - function inner() { - if(input) setValue(input.value); - } - - function setValue(value) { - if (item.type == "Number") { - value = Number(value); - } else if (item.type == "Boolean") { - value = Boolean(value); - } - node[property] = value; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - node.setDirtyCanvas(true, true); - } - }; - - // refactor: there are different dialogs, some uses createDialog some dont - LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) { - 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 = " "; - dialog.close = function() { - that.prompt_box = null; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - canvas.parentNode.appendChild(dialog); - - 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) { - if (prevent_timeout) - 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"); - 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(); - } - that.prompt_box = dialog; - - var first = null; - var timeout = null; - var selected = null; - - var name_element = dialog.querySelector(".name"); - name_element.innerText = title; - var value_element = dialog.querySelector(".value"); - value_element.value = value; - - var input = value_element; - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13 && e.target.localName != "textarea") { - if (callback) { - callback(this.value); - } - 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; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - 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"; - } - - setTimeout(function() { - input.focus(); - }, 10); - - return dialog; - }; - - LGraphCanvas.search_limit = -1; - LGraphCanvas.prototype.showSearchBox = function(event, options) { - // proposed defaults - var def_options = { 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 - ,type_filter_in: false // these are default: pass to set initially set values - ,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 || {}); - - //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 "; - if (options.do_type_filter){ - dialog.innerHTML += ""; - dialog.innerHTML += ""; - } - dialog.innerHTML += "
"; - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(dialog); - else - { - root_document.body.appendChild(dialog); - root_document.body.style.overflow = "hidden"; - } - // dialog element has been appended - - if (options.do_type_filter){ - var selIn = dialog.querySelector(".slot_in_type_filter"); - var selOut = dialog.querySelector(".slot_out_type_filter"); - } - - dialog.close = function() { - 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); - } - }; - - if (this.ds.scale > 1) { - 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) { - if (timeout_close) { - clearTimeout(timeout_close); - timeout_close = null; - } - }); - LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { - if (prevent_timeout){ - return; - } - timeout_close = setTimeout(function() { - dialog.close(); - }, 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; - }); - } - } - - if (that.search_box) { - that.search_box.close(); - } - that.search_box = dialog; - - var helper = dialog.querySelector(".helper"); - - var first = null; - var timeout = null; - var selected = null; - - var input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - this.focus(); - }); - input.addEventListener("keydown", function(e) { - if (e.keyCode == 38) { - //UP - changeSelection(false); - } else if (e.keyCode == 40) { - //DOWN - changeSelection(true); - } else if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - if (selected) { - select(selected.innerHTML); - } else if (first) { - select(first); - } else { - dialog.close(); - } - } else { - if (timeout) { - clearInterval(timeout); - } - timeout = setTimeout(refreshHelper, 250); - return; - } - 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; - - if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION) - 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 (rect.height - 200)) - helper.style.maxHeight = (rect.height - event.layerY - 20) + "px"; - - /* - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - 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"; - } - canvas.parentNode.appendChild(dialog); - */ - - input.focus(); - if (options.show_all_on_open) refreshHelper(); - - function select(name) { - if (name) { - if (that.onSearchBoxSelection) { - that.onSearchBoxSelection(name, event, graphcanvas); - } else { - var extra = LiteGraph.searchbox_extras[name.toLowerCase()]; - if (extra) { - name = extra.type; - } - - graphcanvas.graph.beforeChange(); - var node = LiteGraph.createNode(name); - if (node) { - node.pos = graphcanvas.convertEventToCanvasOffset( - event - ); - graphcanvas.graph.add(node, false); - } - - if (extra && extra.data) { - if (extra.data.properties) { - for (var 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.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.addOutput( - extra.data.outputs[i][0], - extra.data.outputs[i][1] - ); - } - } - if (extra.data.title) { - node.title = extra.data.title; - } - if (extra.data.json) { - node.configure(extra.data.json); - } - - } - - // join node after inserting - if (options.node_from){ - var iS = false; - switch (typeof options.slot_from){ - case "string": - 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; - case "number": - iS = options.slot_from; - break; - default: - 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 ); - } - }else{ - // console.warn("cant find slot " + options.slot_from); - } - } - if (options.node_to){ - var iS = false; - switch (typeof options.slot_from){ - case "string": - 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; - case "number": - iS = options.slot_from; - break; - default: - 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); - } - }else{ - // console.warn("cant find slot_nodeTO " + options.slot_from); - } - } - - graphcanvas.graph.afterChange(); - } - } - - dialog.close(); - } - - function changeSelection(forward) { - var prev = selected; - if (selected) { - selected.classList.remove("selected"); - } - if (!selected) { - selected = forward - ? helper.childNodes[0] - : helper.childNodes[helper.childNodes.length]; - } else { - selected = forward - ? selected.nextSibling - : selected.previousSibling; - if (!selected) { - selected = prev; - } - } - 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; - } - - if (that.onSearchBox) { - var list = that.onSearchBox(helper, str, graphcanvas); - if (list) { - for (var i = 0; i < list.length; ++i) { - addResult(list[i]); - } - } - } else { - var c = 0; - str = str.toLowerCase(); - var filter = graphcanvas.filter || graphcanvas.graph.filter; - - // filter by type preprocess - 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; - } - - //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 ]; - if( ctor && ctor.filter != filter ) - continue; - if( ! inner_test_filter(extra.type) ) - continue; - addResult( extra.desc, "searchbox_extra" ); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - - var filtered = null; - if (Array.prototype.filter) { //filter supported - var keys = Object.keys( LiteGraph.registered_node_types ); //types - var filtered = keys.filter( inner_test_filter ); - } else { - filtered = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(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; - } - } - - // 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) { - if( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?"*":false, outTypeOverride: sOut&&sOut.value?"*":false}) ) - 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; - } - } - } - - // 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) { - if( inner_test_filter(i, {skipFilter: true}) ) - 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; - } - } - } - - function inner_test_filter( type, optsIn ) - { - var optsIn = optsIn || {}; - var optsDef = { skipFilter: false - ,inTypeOverride: false - ,outTypeOverride: false - }; - var opts = Object.assign(optsDef,optsIn); - var ctor = LiteGraph.registered_node_types[ type ]; - if(filter && ctor.filter != filter ) - return false; - if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1) - return false; - - // filter by slot IN, OUT types - if(options.do_type_filter && !opts.skipFilter){ - var sType = type; - - var sV = sIn.value; - if (opts.inTypeOverride!==false) sV = opts.inTypeOverride; - //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 - //console.debug("check "+sType+" in "+LiteGraph.registered_slot_in_types[sV].nodes); - var 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; - } - } - } - - var 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 - //console.debug("check "+sType+" in "+LiteGraph.registered_slot_out_types[sV].nodes); - var 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 true; - } - } - - function addResult(type, className) { - var help = document.createElement("div"); - if (!first) { - first = type; - } - help.innerText = type; - help.dataset["type"] = escape(type); - help.className = "litegraph lite-search-item"; - if (className) { - help.className += " " + className; - } - help.addEventListener("click", function(e) { - select(unescape(this.dataset["type"])); - }); - helper.appendChild(help); - } - } - - return dialog; - }; - - LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) { - if (!node || node.properties[property] === undefined) { - return; - } - - options = options || {}; - var that = this; - - var info = node.getPropertyInfo(property); - var type = info.type; - - var input_html = ""; - - if (type == "string" || type == "number" || type == "array" || type == "object") { - input_html = ""; - } else if ( (type == "enum" || type == "combo") && info.values) { - input_html = ""; - } else if (type == "boolean" || type == "toggle") { - input_html = - ""; - } else { - console.warn("unknown type: " + type); - return; - } - - var dialog = this.createDialog( - "" + - (info.label ? info.label : property) + - "" + - input_html + - "", - options - ); - - var input = false; - if ((type == "enum" || type == "combo") && info.values) { - input = dialog.querySelector("select"); - input.addEventListener("change", function(e) { - dialog.modified(); - setValue(e.target.value); - //var index = e.target.value; - //setValue( e.options[e.selectedIndex].value ); - }); - } else if (type == "boolean" || type == "toggle") { - input = dialog.querySelector("input"); - if (input) { - input.addEventListener("click", function(e) { - dialog.modified(); - setValue(!!input.checked); - }); - } - } else { - input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - this.focus(); - }); - - var v = node.properties[property] !== undefined ? node.properties[property] : ""; - if (type !== 'string') { - v = JSON.stringify(v); - } - - input.value = v; - input.addEventListener("keydown", function(e) { - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - // ENTER - inner(); // save - } else if (e.keyCode != 13) { - dialog.modified(); - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - } - } - if (input) input.focus(); - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - - function inner() { - setValue(input.value); - } - - function setValue(value) { - - if(info && info.values && info.values.constructor === Object && info.values[value] != undefined ) - value = info.values[value]; - - if (typeof node.properties[property] == "number") { - value = Number(value); - } - if (type == "array" || type == "object") { - value = JSON.parse(value); - } - node.properties[property] = value; - if (node.graph) { - node.graph._version++; - } - if (node.onPropertyChanged) { - node.onPropertyChanged(property, value); - } - if(options.onclose) - options.onclose(); - dialog.close(); - node.setDirtyCanvas(true, true); - } - - return dialog; - }; - - // TODO refactor, theer are different dialog, some uses createDialog, some dont - LGraphCanvas.prototype.createDialog = function(html, options) { - var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true }; - options = Object.assign(def_options, options || {}); - - var dialog = document.createElement("div"); - dialog.className = "graphdialog"; - dialog.innerHTML = html; - dialog.is_modified = false; - - var rect = this.canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (options.position) { - offsetx += options.position[0]; - offsety += options.position[1]; - } else if (options.event) { - offsetx += options.event.clientX; - offsety += options.event.clientY; - } //centered - else { - offsetx += this.canvas.width * 0.5; - offsety += this.canvas.height * 0.5; - } - - dialog.style.left = offsetx + "px"; - dialog.style.top = offsety + "px"; - - this.canvas.parentNode.appendChild(dialog); - - // acheck for input and use default behaviour: save on enter, close on esc - if (options.checkForInput){ - var aI = []; - var focused = false; - if (aI = dialog.querySelectorAll("input")){ - aI.forEach(function(iX) { - iX.addEventListener("keydown",function(e){ - dialog.modified(); - if (e.keyCode == 27) { - dialog.close(); - } else if (e.keyCode != 13) { - return; - } - // set value ? - e.preventDefault(); - e.stopPropagation(); - }); - if (!focused) iX.focus(); - }); - } - } - - dialog.modified = function(){ - dialog.is_modified = true; - } - dialog.close = function() { - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - var dialogCloseTimer = null; - var prevent_timeout = false; - dialog.addEventListener("mouseleave", function(e) { - if (prevent_timeout) - return; - if(options.closeOnLeave || 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) { - if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - var 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; - }); - }); - } - - return dialog; - }; - - LGraphCanvas.prototype.createPanel = function(title, options) { - options = options || {}; - - var ref_window = options.window || window; - var root = document.createElement("div"); - root.className = "litegraph dialog"; - root.innerHTML = "
"; - root.header = root.querySelector(".dialog-header"); - - if(options.width) - root.style.width = options.width + (options.width.constructor === Number ? "px" : ""); - if(options.height) - root.style.height = options.height + (options.height.constructor === Number ? "px" : ""); - if(options.closable) - { - var close = document.createElement("span"); - close.innerHTML = "✕"; - close.classList.add("close"); - close.addEventListener("click",function(){ - root.close(); - }); - root.header.appendChild(close); - } - root.title_element = root.querySelector(".dialog-title"); - root.title_element.innerText = title; - root.content = root.querySelector(".dialog-content"); - root.alt_content = root.querySelector(".dialog-alt-content"); - root.footer = root.querySelector(".dialog-footer"); - - root.close = function() - { - if (root.onClose && typeof root.onClose == "function"){ - root.onClose(); - } - if(root.parentNode) - root.parentNode.removeChild(root); - /* XXX CHECK THIS */ - if(this.parentNode){ - this.parentNode.removeChild(this); - } - /* XXX this was not working, was fixed with an IF, check this */ - } - - // function to swap panel content - root.toggleAltContent = function(force){ - if (typeof force != "undefined"){ - var vTo = force ? "block" : "none"; - var vAlt = force ? "none" : "block"; - }else{ - var vTo = root.alt_content.style.display != "block" ? "block" : "none"; - var vAlt = root.alt_content.style.display != "block" ? "none" : "block"; - } - root.alt_content.style.display = vTo; - root.content.style.display = vAlt; - } - - root.toggleFooterVisibility = function(force){ - if (typeof force != "undefined"){ - var vTo = force ? "block" : "none"; - }else{ - var vTo = root.footer.style.display != "block" ? "block" : "none"; - } - root.footer.style.display = vTo; - } - - root.clear = function() - { - this.content.innerHTML = ""; - } - - root.addHTML = function(code, classname, on_footer) - { - var elem = document.createElement("div"); - if(classname) - elem.className = classname; - elem.innerHTML = code; - if(on_footer) - root.footer.appendChild(elem); - else - root.content.appendChild(elem); - return elem; - } - - root.addButton = function( name, callback, options ) - { - var elem = document.createElement("button"); - elem.innerText = name; - elem.options = options; - elem.classList.add("btn"); - elem.addEventListener("click",callback); - root.footer.appendChild(elem); - return elem; - } - - root.addSeparator = function() - { - var elem = document.createElement("div"); - elem.className = "separator"; - root.content.appendChild(elem); - } - - root.addWidget = function( type, name, value, options, callback ) - { - options = options || {}; - var str_value = String(value); - type = type.toLowerCase(); - if(type == "number") - str_value = value.toFixed(3); - - var elem = document.createElement("div"); - elem.className = "property"; - elem.innerHTML = ""; - elem.querySelector(".property_name").innerText = options.label || name; - var value_element = elem.querySelector(".property_value"); - value_element.innerText = str_value; - elem.dataset["property"] = name; - elem.dataset["type"] = options.type || type; - elem.options = options; - elem.value = value; - - if( type == "code" ) - elem.addEventListener("click", function(e){ root.inner_showCodePad( this.dataset["property"] ); }); - else if (type == "boolean") - { - elem.classList.add("boolean"); - if(value) - elem.classList.add("bool-on"); - elem.addEventListener("click", function(){ - //var v = node.properties[this.dataset["property"]]; - //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; - var propname = this.dataset["property"]; - this.value = !this.value; - this.classList.toggle("bool-on"); - this.querySelector(".property_value").innerText = this.value ? "true" : "false"; - innerChange(propname, this.value ); - }); - } - else if (type == "string" || type == "number") - { - value_element.setAttribute("contenteditable",true); - value_element.addEventListener("keydown", function(e){ - if(e.code == "Enter" && (type != "string" || !e.shiftKey)) // allow for multiline - { - e.preventDefault(); - this.blur(); - } - }); - value_element.addEventListener("blur", function(){ - var v = this.innerText; - var propname = this.parentNode.dataset["property"]; - var proptype = this.parentNode.dataset["type"]; - if( proptype == "number") - v = Number(v); - innerChange(propname, v); - }); - } - else if (type == "enum" || type == "combo") { - var str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values ); - value_element.innerText = str_value; - - value_element.addEventListener("click", function(event){ - var values = options.values || []; - var propname = this.parentNode.dataset["property"]; - var elem_that = this; - var menu = new LiteGraph.ContextMenu(values,{ - event: event, - className: "dark", - callback: inner_clicked - }, - ref_window); - function inner_clicked(v, option, event) { - //node.setProperty(propname,v); - //graphcanvas.dirty_canvas = true; - elem_that.innerText = v; - innerChange(propname,v); - return false; - } - }); - } - - root.content.appendChild(elem); - - function innerChange(name, value) - { - //console.log("change",name,value); - //that.dirty_canvas = true; - if(options.callback) - options.callback(name,value,options); - if(callback) - callback(name,value,options); - } - - return elem; - } - - if (root.onOpen && typeof root.onOpen == "function") root.onOpen(); - - return root; - }; - - LGraphCanvas.getPropertyPrintableValue = function(value, values) - { - if(!values) - return String(value); - - if(values.constructor === Array) - { - return String(value); - } - - if(values.constructor === Object) - { - var desc_value = ""; - for(var k in values) - { - if(values[k] != value) - continue; - desc_value = k; - break; - } - return String(value) + " ("+desc_value+")"; - } - } - - LGraphCanvas.prototype.closePanels = function(){ - var panel = document.querySelector("#node-panel"); - if(panel) - panel.close(); - var panel = document.querySelector("#option-panel"); - if(panel) - panel.close(); - } - - LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){ - if(this.constructor && this.constructor.name == "HTMLDivElement"){ - // assume coming from the menu event click - if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){ - console.warn("Canvas not found"); // need a ref to canvas obj - /*console.debug(event); - console.debug(event.target);*/ - return; - } - var graphcanvas = obEv.event.target.lgraphcanvas; - }else{ - // assume called internally - var graphcanvas = this; - } - graphcanvas.closePanels(); - var ref_window = graphcanvas.getCanvasWindow(); - panel = graphcanvas.createPanel("Options",{ - closable: true - ,window: ref_window - ,onOpen: function(){ - graphcanvas.OPTIONPANEL_IS_OPEN = true; - } - ,onClose: function(){ - graphcanvas.OPTIONPANEL_IS_OPEN = false; - graphcanvas.options_panel = null; - } - }); - graphcanvas.options_panel = panel; - panel.id = "option-panel"; - panel.classList.add("settings"); - - function inner_refresh(){ - - panel.content.innerHTML = ""; //clear - - var fUpdate = function(name, value, options){ - switch(name){ - /*case "Render mode": - // Case "".. - if (options.values && options.key){ - var kV = Object.values(options.values).indexOf(value); - if (kV>=0 && options.values[kV]){ - console.debug("update graph options: "+options.key+": "+kV); - graphcanvas[options.key] = kV; - //console.debug(graphcanvas); - break; - } - } - console.warn("unexpected options"); - console.debug(options); - break;*/ - default: - //console.debug("want to update graph options: "+name+": "+value); - if (options && options.key){ - name = options.key; - } - if (options.values){ - value = Object.values(options.values).indexOf(value); - } - //console.debug("update graph option: "+name+": "+value); - graphcanvas[name] = value; - break; - } - }; - - // panel.addWidget( "string", "Graph name", "", {}, fUpdate); // implement - - var aProps = LiteGraph.availableCanvasOptions; - aProps.sort(); - for(var pI in aProps){ - var pX = aProps[pI]; - panel.addWidget( "boolean", pX, graphcanvas[pX], {key: pX, on: "True", off: "False"}, fUpdate); - } - - var aLinks = [ graphcanvas.links_render_mode ]; - panel.addWidget( "combo", "Render mode", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: "links_render_mode", values: LiteGraph.LINK_RENDER_MODES}, fUpdate); - - panel.addSeparator(); - - panel.footer.innerHTML = ""; // clear - - } - inner_refresh(); - - graphcanvas.canvas.parentNode.appendChild( panel ); - } - - LGraphCanvas.prototype.showShowNodePanel = function( node ) - { - this.SELECTED_NODE = node; - this.closePanels(); - var ref_window = this.getCanvasWindow(); - var that = this; - var graphcanvas = this; - var panel = this.createPanel(node.title || "",{ - closable: true - ,window: ref_window - ,onOpen: function(){ - graphcanvas.NODEPANEL_IS_OPEN = true; - } - ,onClose: function(){ - graphcanvas.NODEPANEL_IS_OPEN = false; - graphcanvas.node_panel = null; - } - }); - graphcanvas.node_panel = panel; - panel.id = "node-panel"; - panel.node = node; - panel.classList.add("settings"); - - function inner_refresh() - { - panel.content.innerHTML = ""; //clear - panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); - - panel.addHTML("

Properties

"); - - var fUpdate = function(name,value){ - graphcanvas.graph.beforeChange(node); - switch(name){ - case "Title": - node.title = value; - break; - case "Mode": - var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value); - if (kV>=0 && LiteGraph.NODE_MODES[kV]){ - node.changeMode(kV); - }else{ - console.warn("unexpected mode: "+value); - } - break; - case "Color": - if (LGraphCanvas.node_colors[value]){ - node.color = LGraphCanvas.node_colors[value].color; - node.bgcolor = LGraphCanvas.node_colors[value].bgcolor; - }else{ - console.warn("unexpected color: "+value); - } - break; - default: - node.setProperty(name,value); - break; - } - graphcanvas.graph.afterChange(); - graphcanvas.dirty_canvas = true; - }; - - panel.addWidget( "string", "Title", node.title, {}, fUpdate); - - panel.addWidget( "combo", "Mode", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate); - - var nodeCol = ""; - if (node.color !== undefined){ - nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; }); - } - - panel.addWidget( "combo", "Color", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate); - - for(var pName in node.properties) - { - var value = node.properties[pName]; - var info = node.getPropertyInfo(pName); - var type = info.type || "string"; - - //in case the user wants control over the side panel widget - if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) ) - continue; - - panel.addWidget( info.widget || info.type, pName, value, info, fUpdate); - } - - panel.addSeparator(); - - if(node.onShowCustomPanelInfo) - node.onShowCustomPanelInfo(panel); - - panel.footer.innerHTML = ""; // clear - panel.addButton("Delete",function(){ - if(node.block_delete) - return; - node.graph.remove(node); - panel.close(); - }).classList.add("delete"); - } - - panel.inner_showCodePad = function( propname ) - { - panel.classList.remove("settings"); - panel.classList.add("centered"); - - - /*if(window.CodeFlask) //disabled for now - { - panel.content.innerHTML = "
"; - var flask = new CodeFlask( "div.code", { language: 'js' }); - flask.updateCode(node.properties[propname]); - flask.onUpdate( function(code) { - node.setProperty(propname, code); - }); - } - else - {*/ - panel.alt_content.innerHTML = ""; - var textarea = panel.alt_content.querySelector("textarea"); - var fDoneWith = function(){ - panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = "block"; // panel.close(); - panel.toggleFooterVisibility(true); - textarea.parentNode.removeChild(textarea); - panel.classList.add("settings"); - panel.classList.remove("centered"); - inner_refresh(); - } - textarea.value = node.properties[propname]; - textarea.addEventListener("keydown", function(e){ - if(e.code == "Enter" && e.ctrlKey ) - { - node.setProperty(propname, textarea.value); - fDoneWith(); - } - }); - panel.toggleAltContent(true); - panel.toggleFooterVisibility(false); - textarea.style.height = "calc(100% - 40px)"; - /*}*/ - var assign = panel.addButton( "Assign", function(){ - node.setProperty(propname, textarea.value); - fDoneWith(); - }); - panel.alt_content.appendChild(assign); //panel.content.appendChild(assign); - var button = panel.addButton( "Close", fDoneWith); - button.style.float = "right"; - panel.alt_content.appendChild(button); // panel.content.appendChild(button); - } - - inner_refresh(); - - this.canvas.parentNode.appendChild( panel ); - } - - LGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node) - { - console.log("showing subgraph properties dialog"); - - var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); - if(old_panel) - old_panel.close(); - - var panel = this.createPanel("Subgraph Inputs",{closable:true, width: 500}); - panel.node = node; - panel.classList.add("subgraph_dialog"); - - function inner_refresh() - { - panel.clear(); - - //show currents - if(node.inputs) - for(var i = 0; i < node.inputs.length; ++i) - { - var input = node.inputs[i]; - if(input.not_subgraph_input) - continue; - var html = " "; - var elem = panel.addHTML(html,"subgraph_property"); - elem.dataset["name"] = input.name; - elem.dataset["slot"] = i; - elem.querySelector(".name").innerText = input.name; - elem.querySelector(".type").innerText = input.type; - elem.querySelector("button").addEventListener("click",function(e){ - node.removeInput( Number( this.parentNode.dataset["slot"] ) ); - inner_refresh(); - }); - } - } - - //add extra - var html = " + NameType"; - var elem = panel.addHTML(html,"subgraph_property extra", true); - elem.querySelector("button").addEventListener("click", function(e){ - var elem = this.parentNode; - var name = elem.querySelector(".name").value; - var type = elem.querySelector(".type").value; - if(!name || node.findInputSlot(name) != -1) - return; - node.addInput(name,type); - elem.querySelector(".name").value = ""; - elem.querySelector(".type").value = ""; - inner_refresh(); - }); - - inner_refresh(); - this.canvas.parentNode.appendChild(panel); - return panel; - } - LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) { - - // console.log("showing subgraph properties dialog"); - var that = this; - // old_panel if old_panel is exist close it - var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); - if (old_panel) - old_panel.close(); - // new panel - var panel = this.createPanel("Subgraph Outputs", { closable: true, width: 500 }); - panel.node = node; - panel.classList.add("subgraph_dialog"); - - function inner_refresh() { - panel.clear(); - //show currents - if (node.outputs) - for (var i = 0; i < node.outputs.length; ++i) { - var input = node.outputs[i]; - if (input.not_subgraph_output) - continue; - var html = " "; - var elem = panel.addHTML(html, "subgraph_property"); - elem.dataset["name"] = input.name; - elem.dataset["slot"] = i; - elem.querySelector(".name").innerText = input.name; - elem.querySelector(".type").innerText = input.type; - elem.querySelector("button").addEventListener("click", function (e) { - node.removeOutput(Number(this.parentNode.dataset["slot"])); - inner_refresh(); - }); - } - } - - //add extra - var html = " + NameType"; - var elem = panel.addHTML(html, "subgraph_property extra", true); - elem.querySelector(".name").addEventListener("keydown", function (e) { - if (e.keyCode == 13) { - addOutput.apply(this) - } - }) - elem.querySelector("button").addEventListener("click", function (e) { - addOutput.apply(this) - }); - function addOutput() { - var elem = this.parentNode; - var name = elem.querySelector(".name").value; - var type = elem.querySelector(".type").value; - if (!name || node.findOutputSlot(name) != -1) - return; - node.addOutput(name, type); - elem.querySelector(".name").value = ""; - elem.querySelector(".type").value = ""; - inner_refresh(); - } - - inner_refresh(); - this.canvas.parentNode.appendChild(panel); - return panel; - } - LGraphCanvas.prototype.checkPanels = function() - { - if(!this.canvas) - return; - var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); - for(var i = 0; i < panels.length; ++i) - { - var panel = panels[i]; - if( !panel.node ) - continue; - if( !panel.node.graph || panel.graph != this.graph ) - panel.close(); - } - } - - LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { - node.graph.beforeChange(/*?*/); - - var fApplyMultiNode = function(node){ - node.collapse(); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.graph.afterChange(/*?*/); - }; - - LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) { - node.pin(); - }; - - LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) { - 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 (kV>=0 && LiteGraph.NODE_MODES[kV]) - node.changeMode(kV); - else{ - console.warn("unexpected mode: "+v); - node.changeMode(LiteGraph.ALWAYS); - } - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - } - - return false; - }; - - LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) { - if (!node) { - throw "no node for color"; - } - - var values = []; - values.push({ - value: null, - content: - "No color" - }); - - for (var i in LGraphCanvas.node_colors) { - var color = LGraphCanvas.node_colors[i]; - var value = { - value: i, - content: - "" + - i + - "" - }; - values.push(value); - } - new LiteGraph.ContextMenu(values, { - event: e, - callback: inner_clicked, - parentMenu: menu, - node: node - }); - - function inner_clicked(v) { - if (!node) { - return; - } - - var color = v.value ? LGraphCanvas.node_colors[v.value] : null; - - var fApplyColor = function(node){ - if (color) { - if (node.constructor === LiteGraph.LGraphGroup) { - node.color = color.groupcolor; - } else { - node.color = color.color; - node.bgcolor = color.bgcolor; - } - } else { - delete node.color; - delete node.bgcolor; - } - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyColor(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyColor(graphcanvas.selected_nodes[i]); - } - } - node.setDirtyCanvas(true, true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) { - 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 - - var fApplyMultiNode = function(node){ - node.shape = v; - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.graph.afterChange(/*?*/); //node - node.setDirtyCanvas(true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) { - if (!node) { - throw "no node passed"; - } - - var graph = node.graph; - graph.beforeChange(); - - - var fApplyMultiNode = function(node){ - if (node.removable === false) { - return; - } - graph.remove(node); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - graph.afterChange(); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) { - var graph = node.graph; - var graphcanvas = LGraphCanvas.active_canvas; - if(!graphcanvas) //?? - return; - - var nodes_list = Object.values( graphcanvas.selected_nodes || {} ); - if( !nodes_list.length ) - nodes_list = [ node ]; - - var subgraph_node = LiteGraph.createNode("graph/subgraph"); - subgraph_node.pos = node.pos.concat(); - graph.add(subgraph_node); - - subgraph_node.buildFromNodes( nodes_list ); - - graphcanvas.deselectAllNodes(); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) { - - node.graph.beforeChange(); - - var newSelected = {}; - - 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; - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - if(Object.keys(newSelected).length){ - graphcanvas.selectNodes(newSelected); - } - - node.graph.afterChange(); - - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.node_colors = { - red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" }, - brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, - green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" }, - blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" }, - pale_blue: { - color: "#2a363b", - bgcolor: "#3f5159", - groupcolor: "#3f789e" - }, - cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" }, - purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, - yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, - black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } - }; - - LGraphCanvas.prototype.getCanvasMenuOptions = function() { - var options = null; - var that = this; - if (this.getMenuOptions) { - options = this.getMenuOptions(); - } else { - options = [ - { - content: "Add Node", - has_submenu: true, - callback: LGraphCanvas.onMenuAdd - }, - { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, - //{ content: "Arrange", callback: that.graph.arrange }, - //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } - ]; - /*if (LiteGraph.showCanvasOptions){ - options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); - }*/ - - if (Object.keys(this.selected_nodes).length > 1) { - options.push({ - content: "Align", - has_submenu: true, - callback: LGraphCanvas.onGroupAlign, - }) - } - - if (this._graph_stack && this._graph_stack.length > 0) { - options.push(null, { - content: "Close subgraph", - callback: this.closeSubgraph.bind(this) - }); - } - } - - if (this.getExtraMenuOptions) { - var extra = this.getExtraMenuOptions(this, options); - if (extra) { - options = options.concat(extra); - } - } - - return options; - }; - - //called by processContextMenu to extract the menu list - LGraphCanvas.prototype.getNodeMenuOptions = function(node) { - var options = null; - - if (node.getMenuOptions) { - options = node.getMenuOptions(this); - } else { - options = [ - { - content: "Inputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalInputs - }, - { - content: "Outputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalOutputs - }, - null, - { - content: "Properties", - has_submenu: true, - callback: LGraphCanvas.onShowMenuNodeProperties - }, - { - content: "Properties Panel", - callback: function(item, options, e, menu, node) { LGraphCanvas.active_canvas.showShowNodePanel(node) } - }, - null, - { - content: "Title", - callback: LGraphCanvas.onShowPropertyEditor - }, - { - content: "Mode", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeMode - }]; - if(node.resizable !== false){ - options.push({ - content: "Resize", callback: LGraphCanvas.onMenuResizeNode - }); - } - options.push( - { - content: "Collapse", - callback: LGraphCanvas.onMenuNodeCollapse - }, - { content: "Pin", callback: LGraphCanvas.onMenuNodePin }, - { - content: "Colors", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Shapes", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeShapes - }, - null - ); - } - - if (node.onGetInputs) { - var inputs = node.onGetInputs(); - if (inputs && inputs.length) { - options[0].disabled = false; - } - } - - if (node.onGetOutputs) { - var outputs = node.onGetOutputs(); - if (outputs && outputs.length) { - options[1].disabled = false; - } - } - - if (node.getExtraMenuOptions) { - var extra = node.getExtraMenuOptions(this, options); - if (extra) { - extra.push(null); - options = extra.concat(options); - } - } - - if (node.clonable !== false) { - options.push({ - content: "Clone", - callback: LGraphCanvas.onMenuNodeClone - }); - } - - if(0) //TODO - options.push({ - content: "To Subgraph", - callback: LGraphCanvas.onMenuNodeToSubgraph - }); - - if (Object.keys(this.selected_nodes).length > 1) { - options.push({ - content: "Align Selected To", - has_submenu: true, - callback: LGraphCanvas.onNodeAlign, - }) - } - - options.push(null, { - content: "Remove", - disabled: !(node.removable !== false && !node.block_delete ), - callback: LGraphCanvas.onMenuNodeRemove - }); - - if (node.graph && node.graph.onGetNodeMenuOptions) { - node.graph.onGetNodeMenuOptions(options, node); - } - - return options; - }; - - LGraphCanvas.prototype.getGroupMenuOptions = function(node) { - var o = [ - { content: "Title", callback: LGraphCanvas.onShowPropertyEditor }, - { - content: "Color", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Font size", - property: "font_size", - type: "Number", - callback: LGraphCanvas.onShowPropertyEditor - }, + o = u.CONNECTING_LINK_COLOR; + } + if (this.renderLink( + e, + this.connecting_pos, + [this.graph_mouse[0], this.graph_mouse[1]], null, - { content: "Remove", callback: LGraphCanvas.onMenuNodeRemove } - ]; - - return o; - }; - - LGraphCanvas.prototype.processContextMenu = function(node, event) { - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var menu_info = null; - var options = { - event: event, - callback: inner_option_clicked, - extra: node - }; - - if(node) - options.title = node.type; - - //check if mouse is in input - var slot = null; - if (node) { - slot = node.getSlotInPosition(event.canvasX, event.canvasY); - LGraphCanvas.active_node = node; + !1, + null, + o, + h, + w.CENTER + ), e.beginPath(), p === k.BOX_SHAPE ? (e.rect( + this.connecting_pos[0] - 6 + 0.5, + this.connecting_pos[1] - 5 + 0.5, + 14, + 10 + ), e.fill(), e.beginPath(), e.rect( + this.graph_mouse[0] - 6 + 0.5, + this.graph_mouse[1] - 5 + 0.5, + 14, + 10 + )) : p === k.ARROW_SHAPE ? (e.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5), e.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5), e.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5), e.closePath()) : (e.arc( + this.connecting_pos[0], + this.connecting_pos[1], + 4, + 0, + Math.PI * 2 + ), e.fill(), e.beginPath(), e.arc( + this.graph_mouse[0], + this.graph_mouse[1], + 4, + 0, + Math.PI * 2 + )), e.fill(), e.fillStyle = "#ffcc00", this._highlight_input) { + e.beginPath(); + var f = this._highlight_input_slot.shape; + f === k.ARROW_SHAPE ? (e.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5), e.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5), e.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5), e.closePath()) : e.arc( + this._highlight_input[0], + this._highlight_input[1], + 6, + 0, + Math.PI * 2 + ), e.fill(); + } + this._highlight_output && (e.beginPath(), f === k.ARROW_SHAPE ? (e.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5), e.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5), e.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5), e.closePath()) : e.arc( + this._highlight_output[0], + this._highlight_output[1], + 6, + 0, + Math.PI * 2 + ), e.fill()); } - - if (slot) { - //on slot - menu_info = []; - if (node.getSlotMenuOptions) { - menu_info = node.getSlotMenuOptions(slot); - } else { - if ( - slot && - slot.output && - slot.output.links && - slot.output.links.length - ) { - menu_info.push({ content: "Disconnect Links", slot: slot }); - } - var _slot = slot.input || slot.output; - if (_slot.removable){ - menu_info.push( - _slot.locked - ? "Cannot remove" - : { content: "Remove Slot", slot: slot } - ); - } - if (!_slot.nameLocked){ - menu_info.push({ content: "Rename Slot", slot: slot }); - } - - } - options.title = - (slot.input ? slot.input.type : slot.output.type) || "*"; - if (slot.input && slot.input.type == LiteGraph.ACTION) { - options.title = "Action"; - } - if (slot.output && slot.output.type == LiteGraph.EVENT) { - options.title = "Event"; - } - } else { - if (node) { - //on node - menu_info = this.getNodeMenuOptions(node); - } else { - menu_info = this.getCanvasMenuOptions(); - var group = this.graph.getGroupOnPos( - event.canvasX, - event.canvasY - ); - if (group) { - //on group - menu_info.push(null, { - content: "Edit Group", - has_submenu: true, - submenu: { - title: "Group", - extra: group, - options: this.getGroupMenuOptions(group) - } - }); - } - } - } - - //show menu - if (!menu_info) { - return; - } - - var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window); - - function inner_option_clicked(v, options, e) { - if (!v) { - return; - } - - if (v.content == "Remove Slot") { - var info = v.slot; - node.graph.beforeChange(); - if (info.input) { - node.removeInput(info.slot); - } else if (info.output) { - node.removeOutput(info.slot); - } - node.graph.afterChange(); - return; - } else if (v.content == "Disconnect Links") { - var info = v.slot; - node.graph.beforeChange(); - if (info.output) { - node.disconnectOutput(info.slot); - } else if (info.input) { - node.disconnectInput(info.slot); - } - node.graph.afterChange(); - return; - } else if (v.content == "Rename Slot") { - var info = v.slot; - var slot_info = info.input - ? node.getInputInfo(info.slot) - : node.getOutputInfo(info.slot); - var dialog = that.createDialog( - "Name", - options - ); - var input = dialog.querySelector("input"); - if (input && slot_info) { - input.value = slot_info.label || ""; - } - var inner = function(){ - node.graph.beforeChange(); - if (input.value) { - if (slot_info) { - slot_info.label = input.value; - } - that.setDirty(true); - } - dialog.close(); - node.graph.afterChange(); - } - dialog.querySelector("button").addEventListener("click", inner); - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - inner(); // save - } else if (e.keyCode != 13 && e.target.localName != "textarea") { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - input.focus(); - } - - //if(v.callback) - // return v.callback.call(that, node, options, e, menu, that, event ); - } - }; - - //API ************************************************* - //like rect but rounded corners - if (typeof(window) != "undefined" && window.CanvasRenderingContext2D && !window.CanvasRenderingContext2D.prototype.roundRect) { - window.CanvasRenderingContext2D.prototype.roundRect = function( - x, - y, - w, - h, - radius, - radius_low - ) { - var top_left_radius = 0; - var top_right_radius = 0; - var bottom_left_radius = 0; - var bottom_right_radius = 0; - - if ( radius === 0 ) - { - this.rect(x,y,w,h); - return; - } - - if(radius_low === undefined) - radius_low = radius; - - //make it compatible with official one - if(radius != null && radius.constructor === Array) - { - if(radius.length == 1) - top_left_radius = top_right_radius = bottom_left_radius = bottom_right_radius = radius[0]; - else if(radius.length == 2) - { - top_left_radius = bottom_right_radius = radius[0]; - top_right_radius = bottom_left_radius = radius[1]; - } - else if(radius.length == 4) - { - top_left_radius = radius[0]; - top_right_radius = radius[1]; - bottom_left_radius = radius[2]; - bottom_right_radius = radius[3]; - } - else - return; - } - else //old using numbers - { - top_left_radius = radius || 0; - top_right_radius = radius || 0; - bottom_left_radius = radius_low || 0; - bottom_right_radius = radius_low || 0; - } - - //top right - this.moveTo(x + top_left_radius, y); - this.lineTo(x + w - top_right_radius, y); - this.quadraticCurveTo(x + w, y, x + w, y + top_right_radius); - - //bottom right - this.lineTo(x + w, y + h - bottom_right_radius); - this.quadraticCurveTo( - x + w, - y + h, - x + w - bottom_right_radius, - y + h - ); - - //bottom left - this.lineTo(x + bottom_right_radius, y + h); - this.quadraticCurveTo(x, y + h, x, y + h - bottom_left_radius); - - //top left - this.lineTo(x, y + bottom_left_radius); - this.quadraticCurveTo(x, y, x + top_left_radius, y); - }; - }//if - - function compareObjects(a, b) { - for (var i in a) { - if (a[i] != b[i]) { - return false; - } - } - return true; + this.dragging_rectangle && (e.strokeStyle = "#FFF", e.strokeRect( + this.dragging_rectangle[0], + this.dragging_rectangle[1], + this.dragging_rectangle[2], + this.dragging_rectangle[3] + )), this.over_link_center && this.render_link_tooltip ? this.drawLinkTooltip(e, this.over_link_center) : this.onDrawLinkTooltip && this.onDrawLinkTooltip(e, null, this), this.onDrawForeground && this.onDrawForeground(e, this.visible_area), e.restore(); + } + this._graph_stack && this._graph_stack.length && this.render_subgraph_panels && this.drawSubgraphPanel(e), this.onDrawOverlay && this.onDrawOverlay(e), i && e.restore(); } - LiteGraph.compareObjects = compareObjects; - - function distance(a, b) { - return Math.sqrt( - (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) - ); + } + /** + * draws the panel in the corner that shows subgraph properties + * @method drawSubgraphPanel + **/ + drawSubgraphPanel(e) { + var t = this.graph, i = t._subgraph_node; + if (!i) { + console.warn("subgraph without subnode"); + return; } - LiteGraph.distance = distance; - - function colorToString(c) { - return ( - "rgba(" + - Math.round(c[0] * 255).toFixed() + - "," + - Math.round(c[1] * 255).toFixed() + - "," + - Math.round(c[2] * 255).toFixed() + - "," + - (c.length == 4 ? c[3].toFixed(2) : "1.0") + - ")" - ); + this.drawSubgraphPanelLeft(t, i, e), this.drawSubgraphPanelRight(t, i, e); + } + drawSubgraphPanelLeft(e, t, i) { + var n = t.inputs ? t.inputs.length : 0, s = 200, r = Math.floor(u.NODE_SLOT_HEIGHT * 1.6); + if (i.fillStyle = "#111", i.globalAlpha = 0.8, i.beginPath(), i.roundRect(10, 10, s, (n + 1) * r + 50, [8]), i.fill(), i.globalAlpha = 1, i.fillStyle = "#888", i.font = "14px Arial", i.textAlign = "left", i.fillText("Graph Inputs", 20, 34), this.drawButton(s - 20, 20, 20, 20, "X", "#151515", void 0, void 0, !0)) { + this.closeSubgraph(); + return; } - LiteGraph.colorToString = colorToString; - - function isInsideRectangle(x, y, left, top, width, height) { - if (left < x && left + width > x && top < y && top + height > y) { - return true; - } - return false; + var o = 50; + if (i.font = "14px Arial", t.inputs) + for (var a = 0; a < t.inputs.length; ++a) { + var l = t.inputs[a]; + l.not_subgraph_input || (i.fillStyle = "#9C9", i.beginPath(), i.arc(s - 16, o, 5, 0, 2 * Math.PI), i.fill(), i.fillStyle = "#AAA", i.fillText(l.name, 30, o + r * 0.75), i.fillStyle = "#777", i.fillText(J(l.type), 130, o + r * 0.75), o += r); + } + this.drawButton(20, o + 2, s - 20, r - 2, "+", "#151515", "#222") && this.showSubgraphPropertiesDialog(t); + } + drawSubgraphPanelRight(e, t, i) { + var n = t.outputs ? t.outputs.length : 0, s = this.bgcanvas.width, r = 200, o = Math.floor(u.NODE_SLOT_HEIGHT * 1.6); + i.fillStyle = "#111", i.globalAlpha = 0.8, i.beginPath(), i.roundRect(s - r - 10, 10, r, (n + 1) * o + 50, [8]), i.fill(), i.globalAlpha = 1, i.fillStyle = "#888", i.font = "14px Arial", i.textAlign = "left"; + var a = "Graph Outputs", l = i.measureText(a).width; + if (i.fillText(a, s - l - 20, 34), this.drawButton(s - r, 20, 20, 20, "X", "#151515", void 0, void 0, !0)) { + this.closeSubgraph(); + return; } - LiteGraph.isInsideRectangle = isInsideRectangle; - - //[minx,miny,maxx,maxy] - function growBounding(bounding, x, y) { - if (x < bounding[0]) { - bounding[0] = x; - } else if (x > bounding[2]) { - bounding[2] = x; - } - - if (y < bounding[1]) { - bounding[1] = y; - } else if (y > bounding[3]) { - bounding[3] = y; + var h = 50; + if (i.font = "14px Arial", t.outputs) + for (var p = 0; p < t.outputs.length; ++p) { + var f = t.outputs[p]; + f.not_subgraph_output || (i.fillStyle = "#9C9", i.beginPath(), i.arc(s - r + 16, h, 5, 0, 2 * Math.PI), i.fill(), i.fillStyle = "#AAA", i.fillText(f.name, s - r + 30, h + o * 0.75), i.fillStyle = "#777", i.fillText(J(f.type), s - r + 130, h + o * 0.75), h += o); + } + this.drawButton(s - r, h + 2, r - 20, o - 2, "+", "#151515", "#222") && this.showSubgraphPropertiesDialogRight(t); + } + //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm + drawButton(e, t, i, n, s, r = u.NODE_DEFAULT_COLOR, o = "#555", a = u.NODE_TEXT_COLOR, l = !1) { + const h = !this.block_click && (l || this.allow_interaction && !this.read_only); + var p = this.ctx, f = this.offset_mouse, c = h && u.isInsideRectangle(f[0], f[1], e, t, i, n); + f = this.last_click_position_offset; + var v = h && f && this.pointer_is_down && u.isInsideRectangle(f[0], f[1], e, t, i, n); + p.fillStyle = c ? o : r, v && (p.fillStyle = "#AAA"), p.beginPath(), p.roundRect(e, t, i, n, [4]), p.fill(), s != null && s.constructor == String && (p.fillStyle = a, p.textAlign = "center", p.font = (n * 0.65 | 0) + "px Arial", p.fillText(s, e + i * 0.5, t + n * 0.75), p.textAlign = "left"); + var g = v && h; + return v && this.blockClick(), g; + } + /** draws every group area in the background */ + drawGroups(e, t) { + if (this.graph) { + var i = this.graph._groups; + t.save(), t.globalAlpha = 0.5 * this.editor_alpha; + for (var n = 0; n < i.length; ++n) { + var s = i[n]; + if (u.overlapBounding(this.visible_area, s._bounding)) { + t.fillStyle = s.color || "#335", t.strokeStyle = s.color || "#335"; + var r = s._pos, o = s._size; + t.globalAlpha = 0.25 * this.editor_alpha, t.beginPath(), t.rect(r[0] + 0.5, r[1] + 0.5, o[0], o[1]), t.fill(), t.globalAlpha = this.editor_alpha, t.stroke(), t.beginPath(), t.moveTo(r[0] + o[0], r[1] + o[1]), t.lineTo(r[0] + o[0] - 10, r[1] + o[1]), t.lineTo(r[0] + o[0], r[1] + o[1] - 10), t.fill(); + var a = s.font_size || u.DEFAULT_GROUP_FONT_SIZE; + t.font = a + "px Arial", t.textAlign = "left", t.fillText(s.title, r[0] + 4, r[1] + a); } + } + t.restore(); } - LiteGraph.growBounding = growBounding; - - //point inside bounding box - function isInsideBounding(p, bb) { - if ( - p[0] < bb[0][0] || - p[1] < bb[0][1] || - p[0] > bb[1][0] || - p[1] > bb[1][1] - ) { - return false; - } - return true; + } + /** draws some useful stats in the corner of the canvas */ + renderInfo(e, t = 10, i) { + i = i || this.canvas.height - 80, e.save(), e.translate(t, i), e.font = "10px Arial", e.fillStyle = "#888", e.textAlign = "left", this.graph ? (e.fillText("T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13 * 1), e.fillText("I: " + this.graph.iteration, 5, 13 * 2), e.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 13 * 3), e.fillText("V: " + this.graph._version, 5, 13 * 4), e.fillText("FPS:" + this.fps.toFixed(2), 5, 13 * 5)) : e.fillText("No graph selected", 5, 13 * 1), e.restore(); + } + /** draws the back canvas (the one containing the background and the connections) */ + drawBackCanvas() { + var e = this.bgcanvas; + (e.width != this.canvas.width || e.height != this.canvas.height) && (e.width = this.canvas.width, e.height = this.canvas.height), this.bgctx || (this.bgctx = this.bgcanvas.getContext("2d")); + var t = this.bgctx; + let i = this.viewport || [0, 0, t.canvas.width, t.canvas.height]; + if (this.clear_background && t.clearRect(i[0], i[1], i[2], i[3]), this._graph_stack && this._graph_stack.length && this.render_subgraph_stack_header) { + t.save(); + const o = this._graph_stack[this._graph_stack.length - 1].graph, a = this.graph._subgraph_node; + t.strokeStyle = a.bgcolor, t.lineWidth = 10, t.strokeRect(1, 1, e.width - 2, e.height - 2), t.lineWidth = 1, t.font = "40px Arial", t.textAlign = "center", t.fillStyle = a.bgcolor || "#AAA"; + let l = ""; + for (let h = 1; h < this._graph_stack.length; ++h) + l += o._subgraph_node.getTitle() + " >> "; + t.fillText( + l + a.getTitle(), + e.width * 0.5, + 40 + ), t.restore(); } - LiteGraph.isInsideBounding = isInsideBounding; - - //bounding overlap, format: [ startx, starty, width, height ] - function overlapBounding(a, b) { - var A_end_x = a[0] + a[2]; - var A_end_y = a[1] + a[3]; - var B_end_x = b[0] + b[2]; - var B_end_y = b[1] + b[3]; - - if ( - a[0] > B_end_x || - a[1] > B_end_y || - A_end_x < b[0] || - A_end_y < b[1] - ) { - return false; - } - return true; - } - LiteGraph.overlapBounding = overlapBounding; - - //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. - function hex2num(hex) { - if (hex.charAt(0) == "#") { - 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) { - int1 = hex_alphabets.indexOf(hex.charAt(i)); - int2 = hex_alphabets.indexOf(hex.charAt(i + 1)); - value[k] = int1 * 16 + int2; - k++; - } - return value; - } - - LiteGraph.hex2num = hex2num; - - //Give a array with three values as the argument and the function will return - // the corresponding hex triplet. - function num2hex(triplet) { - var hex_alphabets = "0123456789ABCDEF"; - var hex = "#"; - var int1, int2; - for (var i = 0; i < 3; i++) { - int1 = triplet[i] / 16; - int2 = triplet[i] % 16; - - hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); - } - return hex; - } - - LiteGraph.num2hex = num2hex; - - /* LiteGraph GUI elements used for canvas editing *************************************/ - - /** - * ContextMenu from LiteGUI - * - * @class ContextMenu - * @constructor - * @param {Array} values (allows object { title: "Nice text", callback: function ... }) - * @param {Object} options [optional] Some options:\ - * - title: title to show on top of the menu - * - callback: function to call when an option is clicked, it receives the item information - * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback - * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position - */ - function ContextMenu(values, options) { - options = options || {}; - this.options = options; - var that = this; - - //to link a menu with its parent - if (options.parentMenu) { - if (options.parentMenu.constructor !== this.constructor) { - console.error( - "parentMenu must be of class ContextMenu, ignoring it" - ); - options.parentMenu = null; - } else { - this.parentMenu = options.parentMenu; - this.parentMenu.lock = true; - this.parentMenu.current_submenu = this; - } - } - - var eventClass = null; - if(options.event) //use strings because comparing classes between windows doesnt work - eventClass = options.event.constructor.name; - if ( eventClass !== "MouseEvent" && - eventClass !== "CustomEvent" && - eventClass !== "PointerEvent" - ) { - console.error( - "Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. ("+eventClass+")" - ); - options.event = null; - } - - var root = document.createElement("div"); - root.className = "litegraph litecontextmenu litemenubar-panel"; - if (options.className) { - root.className += " " + options.className; - } - root.style.minWidth = 100; - root.style.minHeight = 100; - root.style.pointerEvents = "none"; - setTimeout(function() { - root.style.pointerEvents = "auto"; - }, 100); //delay so the mouse up event is not caught by this element - - //this prevents the default context browser menu to open in case this menu was created when pressing right button - LiteGraph.pointerListenerAdd(root,"up", - function(e) { - //console.log("pointerevents: ContextMenu up root prevent"); - e.preventDefault(); - return true; - }, - true - ); - root.addEventListener( - "contextmenu", - function(e) { - if (e.button != 2) { - //right button - return false; - } - e.preventDefault(); - return false; - }, - true - ); - - LiteGraph.pointerListenerAdd(root,"down", - function(e) { - //console.log("pointerevents: ContextMenu down"); - if (e.button == 2) { - that.close(); - e.preventDefault(); - return true; - } - }, - true - ); - - function on_mouse_wheel(e) { - var pos = parseInt(root.style.top); - root.style.top = - (pos + e.deltaY * options.scroll_speed).toFixed() + "px"; - e.preventDefault(); - return true; - } - - if (!options.scroll_speed) { - options.scroll_speed = 0.1; - } - - root.addEventListener("wheel", on_mouse_wheel, true); - root.addEventListener("mousewheel", on_mouse_wheel, true); - - this.root = root; - - //title - if (options.title) { - var element = document.createElement("div"); - element.className = "litemenu-title"; - element.innerHTML = options.title; - root.appendChild(element); - } - - //entries - var num = 0; - for (var i=0; i < values.length; i++) { - var name = values.constructor == Array ? values[i] : i; - if (name != null && name.constructor !== String) { - name = name.content === undefined ? String(name) : name.content; - } - var value = values[i]; - this.addItem(name, value, options); - num++; - } - - //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that - /*LiteGraph.pointerListenerAdd(root,"leave", function(e) { - console.log("pointerevents: ContextMenu leave"); - if (that.lock) { - return; - } - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } - root.closing_timer = setTimeout(that.close.bind(that, e), 500); - //that.close(e); - });*/ - - LiteGraph.pointerListenerAdd(root,"enter", function(e) { - //console.log("pointerevents: ContextMenu enter"); - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } + let n = !1; + if (this.onRenderBackground && this.onRenderBackground(e, t) && (n = !0), this.viewport || (t.restore(), t.setTransform(1, 0, 0, 1, 0, 0)), this.visible_links.length = 0, this.graph) { + if (t.save(), this.ds.toCanvasContext(t), this.ds.scale < 1.5 && !n && this.clear_background_color && (t.fillStyle = this.clear_background_color, t.fillRect( + this.visible_area[0], + this.visible_area[1], + this.visible_area[2], + this.visible_area[3] + )), this.background_image && this.ds.scale > 0.5 && !n) { + this.zoom_modify_alpha ? t.globalAlpha = (1 - 0.5 / this.ds.scale) * this.editor_alpha : t.globalAlpha = this.editor_alpha, t.imageSmoothingEnabled = t.imageSmoothingEnabled = !1, (!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, this._bg_img.onload = () => { + this.draw(!0, !0); }); - - //insert before checking position - var root_document = document; - if (options.event) { - root_document = options.event.target.ownerDocument; + var s = null; + this._pattern == null && this._bg_img.width > 0 ? (s = t.createPattern(this._bg_img, "repeat"), this._pattern_img = this._bg_img, this._pattern = s) : s = this._pattern, s && (t.fillStyle = s, t.fillRect( + this.visible_area[0], + this.visible_area[1], + this.visible_area[2], + this.visible_area[3] + ), t.fillStyle = "transparent"), t.globalAlpha = 1, t.imageSmoothingEnabled = t.imageSmoothingEnabled = !0; + } + this.graph._groups.length && !this.live_mode && this.drawGroups(e, t), this.onDrawBackground && this.onDrawBackground(t, this.visible_area), u.debug && (t.fillStyle = "red", t.fillRect(this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20)), this.render_canvas_border && (t.strokeStyle = "#235", t.strokeRect(0, 0, e.width, e.height)), this.render_connections_shadows ? (t.shadowColor = "#000", t.shadowOffsetX = 0, t.shadowOffsetY = 0, t.shadowBlur = 6) : t.shadowColor = "rgba(0,0,0,0)", !this.live_mode && this.render_connections && this.drawConnections(t), t.shadowColor = "rgba(0,0,0,0)", t.restore(); + } + this.dirty_bgcanvas = !1, this.dirty_canvas = !0; + } + /** draws the given node inside the canvas */ + drawNode(e, t) { + this.current_node = e; + var i = e.color || e.constructor.color || u.NODE_DEFAULT_COLOR, n = e.bgcolor || e.constructor.bgcolor || u.NODE_DEFAULT_BGCOLOR; + e.mouseOver; + var s = this.ds.scale < 0.6; + if (this.live_mode) { + e.flags.collapsed || (t.shadowColor = "transparent", e.onDrawForeground && e.onDrawForeground(t, this, this.canvas)); + return; + } + var r = this.editor_alpha; + if (t.globalAlpha = r, this.render_shadows && !s ? (t.shadowColor = u.DEFAULT_SHADOW_COLOR, t.shadowOffsetX = 2 * this.ds.scale, t.shadowOffsetY = 2 * this.ds.scale, t.shadowBlur = 3 * this.ds.scale) : t.shadowColor = "transparent", !(e.flags.collapsed && e.onDrawCollapsed && e.onDrawCollapsed(t, this) == !0)) { + var o = e.shape || k.BOX_SHAPE, a = ie.temp_vec2; + ie.temp_vec2.set(e.size); + var l = e.horizontal; + if (e.flags.collapsed) { + t.font = this.inner_text_font; + var h = e.getTitle ? e.getTitle() : e.title; + h != null && (e._collapsed_width = Math.min( + e.size[0], + t.measureText(h).width + u.NODE_TITLE_HEIGHT * 2 + ), a[0] = e._collapsed_width, a[1] = 0); + } + e.clip_area && (t.save(), t.beginPath(), o == k.BOX_SHAPE ? t.rect(0, 0, a[0], a[1]) : o == k.ROUND_SHAPE ? t.roundRect(0, 0, a[0], a[1], [10]) : o == k.CIRCLE_SHAPE && t.arc( + a[0] * 0.5, + a[1] * 0.5, + a[0] * 0.5, + 0, + Math.PI * 2 + ), t.clip()), e.has_errors && (n = "red"), this.drawNodeShape( + e, + t, + [a[0], a[1]], + i, + n, + e.is_selected, + e.mouseOver + ), t.shadowColor = "transparent", e.onDrawForeground && e.onDrawForeground(t, this, this.canvas), t.textAlign = l ? "center" : "left", t.font = this.inner_text_font; + var p = !s, f = this.connecting_output, c = this.connecting_input; + t.lineWidth = 1; + var v = 0, g = [0, 0]; + if (e.flags.collapsed) { + if (this.render_collapsed_slots) { + var A = null, M = null; + if (e.inputs) + for (let G = 0; G < e.inputs.length; G++) { + let z = e.inputs[G]; + if (z.link != null) { + A = z; + break; + } + } + if (e.outputs) + for (let G = 0; G < e.outputs.length; G++) { + let z = e.outputs[G]; + !z.links || !z.links.length || (M = z); + } + if (A) { + var L = 0, B = u.NODE_TITLE_HEIGHT * -0.5; + l && (L = e._collapsed_width * 0.5, B = -u.NODE_TITLE_HEIGHT), t.fillStyle = "#686", t.beginPath(), A.shape === k.BOX_SHAPE ? t.rect(L - 7 + 0.5, B - 4, 14, 8) : A.shape === k.ARROW_SHAPE ? (t.moveTo(L + 8, B), t.lineTo(L + -4, B - 4), t.lineTo(L + -4, B + 4), t.closePath()) : t.arc(L, B, 4, 0, Math.PI * 2), t.fill(); + } + if (M) { + var L = e._collapsed_width, B = u.NODE_TITLE_HEIGHT * -0.5; + l && (L = e._collapsed_width * 0.5, B = 0), t.fillStyle = "#686", t.strokeStyle = "black", t.beginPath(), M.shape === k.BOX_SHAPE ? t.rect(L - 7 + 0.5, B - 4, 14, 8) : M.shape === k.ARROW_SHAPE ? (t.moveTo(L + 6, B), t.lineTo(L - 6, B - 4), t.lineTo(L - 6, B + 4), t.closePath()) : t.arc(L, B, 4, 0, Math.PI * 2), t.fill(); + } } - - if (!root_document) { - root_document = document; + } else { + if (e.inputs) + for (var d = 0; d < e.inputs.length; d++) { + var _ = e.inputs[d], y = _.type, b = _.shape; + t.globalAlpha = r, this.connecting_output && !u.isValidConnection(_.type, f.type) ? t.globalAlpha = 0.4 * r : t.globalAlpha = r, t.fillStyle = _.link != null ? _.color_on || N.DEFAULT_CONNECTION_COLORS_BY_TYPE[y] || N.DEFAULT_CONNECTION_COLORS.input_on : _.color_off || N.DEFAULT_CONNECTION_COLORS_BY_TYPE_OFF[y] || N.DEFAULT_CONNECTION_COLORS_BY_TYPE[y] || N.DEFAULT_CONNECTION_COLORS.input_off; + var m = e.getConnectionPos(!0, d, [g[0], g[1]]); + m[0] -= e.pos[0], m[1] -= e.pos[1], v < m[1] + u.NODE_SLOT_HEIGHT * 0.5 && (v = m[1] + u.NODE_SLOT_HEIGHT * 0.5), t.beginPath(); + var E = !0; + if (_.shape === k.BOX_SHAPE ? l ? t.rect( + m[0] - 5 + 0.5, + m[1] - 8 + 0.5, + 10, + 14 + ) : t.rect( + m[0] - 6 + 0.5, + m[1] - 5 + 0.5, + 14, + 10 + ) : b === k.ARROW_SHAPE ? (t.moveTo(m[0] + 8, m[1] + 0.5), t.lineTo(m[0] - 4, m[1] + 6 + 0.5), t.lineTo(m[0] - 4, m[1] - 6 + 0.5), t.closePath()) : b === k.GRID_SHAPE ? (t.rect(m[0] - 4, m[1] - 4, 2, 2), t.rect(m[0] - 1, m[1] - 4, 2, 2), t.rect(m[0] + 2, m[1] - 4, 2, 2), t.rect(m[0] - 4, m[1] - 1, 2, 2), t.rect(m[0] - 1, m[1] - 1, 2, 2), t.rect(m[0] + 2, m[1] - 1, 2, 2), t.rect(m[0] - 4, m[1] + 2, 2, 2), t.rect(m[0] - 1, m[1] + 2, 2, 2), t.rect(m[0] + 2, m[1] + 2, 2, 2), E = !1) : s ? t.rect(m[0] - 4, m[1] - 4, 8, 8) : t.arc(m[0], m[1], 4, 0, Math.PI * 2), t.fill(), p) { + var T = _.label != null ? _.label : _.name; + T && (t.fillStyle = u.NODE_TEXT_COLOR, l || _.dir == w.UP ? t.fillText(T, m[0], m[1] - 10) : t.fillText(T, m[0] + 10, m[1] + 5)); + } + } + if (t.textAlign = l ? "center" : "right", t.strokeStyle = "black", e.outputs) + for (let G = 0; G < e.outputs.length; G++) { + let z = e.outputs[G]; + var y = z.type, b = z.shape; + this.connecting_input && !u.isValidConnection(c.type, y) ? t.globalAlpha = 0.4 * r : t.globalAlpha = r; + var m = e.getConnectionPos(!1, G, g); + m[0] -= e.pos[0], m[1] -= e.pos[1], v < m[1] + u.NODE_SLOT_HEIGHT * 0.5 && (v = m[1] + u.NODE_SLOT_HEIGHT * 0.5), t.fillStyle = z.links && z.links.length ? z.color_on || N.DEFAULT_CONNECTION_COLORS_BY_TYPE[y] || N.DEFAULT_CONNECTION_COLORS.output_on : z.color_off || N.DEFAULT_CONNECTION_COLORS_BY_TYPE_OFF[y] || N.DEFAULT_CONNECTION_COLORS_BY_TYPE[y] || N.DEFAULT_CONNECTION_COLORS.output_off, t.beginPath(); + var E = !0; + if (b === k.BOX_SHAPE ? l ? t.rect( + m[0] - 5 + 0.5, + m[1] - 8 + 0.5, + 10, + 14 + ) : t.rect( + m[0] - 6 + 0.5, + m[1] - 5 + 0.5, + 14, + 10 + ) : b === k.ARROW_SHAPE ? (t.moveTo(m[0] + 8, m[1] + 0.5), t.lineTo(m[0] - 4, m[1] + 6 + 0.5), t.lineTo(m[0] - 4, m[1] - 6 + 0.5), t.closePath()) : b === k.GRID_SHAPE ? (t.rect(m[0] - 4, m[1] - 4, 2, 2), t.rect(m[0] - 1, m[1] - 4, 2, 2), t.rect(m[0] + 2, m[1] - 4, 2, 2), t.rect(m[0] - 4, m[1] - 1, 2, 2), t.rect(m[0] - 1, m[1] - 1, 2, 2), t.rect(m[0] + 2, m[1] - 1, 2, 2), t.rect(m[0] - 4, m[1] + 2, 2, 2), t.rect(m[0] - 1, m[1] + 2, 2, 2), t.rect(m[0] + 2, m[1] + 2, 2, 2), E = !1) : s ? t.rect(m[0] - 4, m[1] - 4, 8, 8) : t.arc(m[0], m[1], 4, 0, Math.PI * 2), t.fill(), !s && E && t.stroke(), p) { + var T = z.label != null ? z.label : z.name; + T && (t.fillStyle = u.NODE_TEXT_COLOR, l || z.dir == w.DOWN ? t.fillText(T, m[0], m[1] - 8) : t.fillText(T, m[0] - 10, m[1] + 5)); + } + } + if (t.textAlign = "left", t.globalAlpha = 1, e.widgets) { + var O = v; + (l || e.widgets_up) && (O = 2), e.widgets_start_y != null && (O = e.widgets_start_y), this.drawNodeWidgets( + e, + O, + t, + this.node_widget && this.node_widget[0] == e ? this.node_widget[1] : null + ); } - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(root); - else - root_document.body.appendChild(root); - - //compute best position - var left = options.left || 0; - var top = options.top || 0; - if (options.event) { - left = options.event.clientX - 10; - top = options.event.clientY - 10; - if (options.title) { - top -= 20; + } + e.clip_area && t.restore(), t.globalAlpha = 1; + } + } + /** used by this.over_link_center */ + drawLinkTooltip(e, t) { + var i = t._pos; + if (this.allow_interaction && !this.read_only && (e.fillStyle = "black", e.beginPath(), e.arc(i[0], i[1], 3, 0, Math.PI * 2), e.fill()), t.data != null && !(this.onDrawLinkTooltip && this.onDrawLinkTooltip(e, t, this) == !0)) { + var n = t.data, s = null; + if (n.constructor === Number ? s = n.toFixed(2) : n.constructor === String ? s = '"' + n + '"' : n.constructor === Boolean ? s = String(n) : n.toToolTip ? s = n.toToolTip() : s = "[" + n.constructor.name + "]", s != null) { + s = s.substr(0, 30), e.font = "14px Courier New"; + var r = e.measureText(s), o = r.width + 20, a = 24; + e.shadowColor = "black", e.shadowOffsetX = 2, e.shadowOffsetY = 2, e.shadowBlur = 3, e.fillStyle = "#454", e.beginPath(), e.roundRect(i[0] - o * 0.5, i[1] - 15 - a, o, a, [3]), e.moveTo(i[0] - 10, i[1] - 15), e.lineTo(i[0] + 10, i[1] - 15), e.lineTo(i[0], i[1] - 5), e.fill(), e.shadowColor = "transparent", e.textAlign = "center", e.fillStyle = "#CEC", e.fillText(s, i[0], i[1] - 15 - a * 0.3); + } + } + } + /** draws the shape of the given node in the canvas */ + drawNodeShape(e, t, i, n, s, r, o) { + t.strokeStyle = n, t.fillStyle = s; + var a = u.NODE_TITLE_HEIGHT, l = this.ds.scale < 0.5, h = e.shape || e.constructor.shape || k.ROUND_SHAPE, p = e.titleMode, f = e.isShowingTitle(o), c = ie.tmp_area; + c[0] = 0, c[1] = f ? -a : 0, c[2] = i[0] + 1, c[3] = f ? i[1] + a : i[1]; + var v = t.globalAlpha; + if (t.beginPath(), h == k.BOX_SHAPE || l ? t.fillRect(c[0], c[1], c[2], c[3]) : h == k.ROUND_SHAPE || h == k.CARD_SHAPE ? t.roundRect( + c[0], + c[1], + c[2], + c[3], + h == k.CARD_SHAPE ? [this.round_radius, this.round_radius, 0, 0] : [this.round_radius] + ) : h == k.CIRCLE_SHAPE && t.arc( + i[0] * 0.5, + i[1] * 0.5, + i[0] * 0.5, + 0, + Math.PI * 2 + ), t.fill(), !e.flags.collapsed && f && (t.shadowColor = "transparent", t.fillStyle = "rgba(0,0,0,0.2)", t.fillRect(0, -1, c[2], 2)), t.shadowColor = "transparent", e.onDrawBackground && e.onDrawBackground(t, this, this.canvas, this.graph_mouse), f || p == se.TRANSPARENT_TITLE) { + if (e.onDrawTitleBar) + e.onDrawTitleBar(t, this, a, i, this.ds.scale, n); + else if (p != se.TRANSPARENT_TITLE && (e.constructor.title_color || this.render_title_colored)) { + var g = e.constructor.title_color || n; + if (e.flags.collapsed && (t.shadowColor = u.DEFAULT_SHADOW_COLOR), this.use_gradients) { + var d = N.gradients[g]; + d || (d = N.gradients[g] = t.createLinearGradient(0, 0, 400, 0), d.addColorStop(0, g), d.addColorStop(1, "#000")), t.fillStyle = d; + } else + t.fillStyle = g; + t.beginPath(), h == k.BOX_SHAPE || l ? t.rect(0, -a, i[0] + 1, a) : (h == k.ROUND_SHAPE || h == k.CARD_SHAPE) && t.roundRect( + 0, + -a, + i[0] + 1, + a, + e.flags.collapsed ? [this.round_radius] : [this.round_radius, this.round_radius, 0, 0] + ), t.fill(), t.shadowColor = "transparent"; + } + var _ = null; + u.node_box_coloured_by_mode && Oe[e.mode] && (_ = Oe[e.mode]), u.node_box_coloured_when_on && (_ = e.action_triggered ? "#FFF" : e.execute_triggered ? "#AAA" : _); + var y = 10; + if (e.onDrawTitleBox ? e.onDrawTitleBox(t, this, a, i, this.ds.scale) : h == k.ROUND_SHAPE || h == k.CIRCLE_SHAPE || h == k.CARD_SHAPE ? (l && (t.fillStyle = "black", t.beginPath(), t.arc( + a * 0.5, + a * -0.5, + y * 0.5 + 1, + 0, + Math.PI * 2 + ), t.fill()), t.fillStyle = e.boxcolor || _ || u.NODE_DEFAULT_BOXCOLOR, l ? t.fillRect(a * 0.5 - y * 0.5, a * -0.5 - y * 0.5, y, y) : (t.beginPath(), t.arc( + a * 0.5, + a * -0.5, + y * 0.5, + 0, + Math.PI * 2 + ), t.fill())) : (l && (t.fillStyle = "black", t.fillRect( + (a - y) * 0.5 - 1, + (a + y) * -0.5 - 1, + y + 2, + y + 2 + )), t.fillStyle = e.boxcolor || _ || u.NODE_DEFAULT_BOXCOLOR, t.fillRect( + (a - y) * 0.5, + (a + y) * -0.5, + y, + y + )), t.globalAlpha = v, e.onDrawTitleText && e.onDrawTitleText( + t, + this, + a, + i, + this.ds.scale, + this.title_text_font, + r + ), !l) { + t.font = this.title_text_font; + var b = String(e.getTitle()); + b && (r ? t.fillStyle = u.NODE_SELECTED_TITLE_COLOR : t.fillStyle = e.constructor.title_text_color || this.node_title_color, e.flags.collapsed ? (t.textAlign = "left", t.fillText( + b.substr(0, 20), + //avoid urls too long + a, + // + measure.width * 0.5, + u.NODE_TITLE_TEXT_Y - a + ), t.textAlign = "left") : (t.textAlign = "left", t.fillText( + b, + a, + u.NODE_TITLE_TEXT_Y - a + ))); + } + if (!e.flags.collapsed && e.subgraph && !e.skip_subgraph_button) { + var m = u.NODE_TITLE_HEIGHT, E = e.size[0] - m, T = u.isInsideRectangle(this.graph_mouse[0] - e.pos[0], this.graph_mouse[1] - e.pos[1], E + 2, -m + 2, m - 4, m - 4); + t.fillStyle = T ? "#888" : "#555", h == k.BOX_SHAPE || l ? t.fillRect(E + 2, -m + 2, m - 4, m - 4) : (t.beginPath(), t.roundRect(E + 2, -m + 2, m - 4, m - 4, [4]), t.fill()), t.fillStyle = "#333", t.beginPath(), t.moveTo(E + m * 0.2, -m * 0.6), t.lineTo(E + m * 0.8, -m * 0.6), t.lineTo(E + m * 0.5, -m * 0.3), t.fill(); + } + e.onDrawTitle && e.onDrawTitle(t, this); + } + r && (e.onBounding && e.onBounding(c), p == se.TRANSPARENT_TITLE && (c[1] -= a, c[3] += a), t.lineWidth = 1, t.globalAlpha = 0.8, t.beginPath(), h == k.BOX_SHAPE ? t.rect( + -6 + c[0], + -6 + c[1], + 12 + c[2], + 12 + c[3] + ) : h == k.ROUND_SHAPE || h == k.CARD_SHAPE && e.flags.collapsed ? t.roundRect( + -6 + c[0], + -6 + c[1], + 12 + c[2], + 12 + c[3], + [this.round_radius * 2] + ) : h == k.CARD_SHAPE ? t.roundRect( + -6 + c[0], + -6 + c[1], + 12 + c[2], + 12 + c[3], + [this.round_radius * 2, 2, this.round_radius * 2, 2] + ) : h == k.CIRCLE_SHAPE && t.arc( + i[0] * 0.5, + i[1] * 0.5, + i[0] * 0.5 + 6, + 0, + Math.PI * 2 + ), t.strokeStyle = u.NODE_BOX_OUTLINE_COLOR, t.stroke(), t.strokeStyle = n, t.globalAlpha = 1), e.execute_triggered > 0 && e.execute_triggered--, e.action_triggered > 0 && e.action_triggered--; + } + /** draws every connection visible in the canvas */ + drawConnections(e) { + var t = u.getTime(), i = this.visible_area; + let n = ie.margin_area; + n[0] = i[0] - 20, n[1] = i[1] - 20, n[2] = i[2] + 40, n[3] = i[3] + 40, e.lineWidth = this.connections_width, e.fillStyle = "#AAA", e.strokeStyle = "#AAA", e.globalAlpha = this.editor_alpha; + for (var s = this.graph._nodes, r = 0, o = s.length; r < o; ++r) { + var a = s[r]; + if (!(!a.inputs || !a.inputs.length)) + for (var l = 0; l < a.inputs.length; ++l) { + var h = a.inputs[l]; + if (!h || h.link == null) + continue; + var p = h.link, f = this.graph.links[p]; + if (!f) + continue; + var c = this.graph.getNodeById(f.origin_id); + if (c == null) + continue; + var v = f.origin_slot, g = null; + v == -1 ? g = [ + c.pos[0] + 10, + c.pos[1] + 10 + ] : g = c.getConnectionPos( + !1, + v, + ie.tempA + ); + var d = a.getConnectionPos(!0, l, ie.tempB); + let O = ie.link_bounding; + if (O[0] = g[0], O[1] = g[1], O[2] = d[0] - g[0], O[3] = d[1] - g[1], O[2] < 0 && (O[0] += O[2], O[2] = Math.abs(O[2])), O[3] < 0 && (O[1] += O[3], O[3] = Math.abs(O[3])), !!u.overlapBounding(O, n)) { + var _ = c.outputs[v], y = a.inputs[l]; + if (!(!_ || !y)) { + var b = _.dir || (c.horizontal ? w.DOWN : w.RIGHT), m = y.dir || (a.horizontal ? w.UP : w.LEFT); + if (this.renderLink( + e, + g, + d, + f, + !1, + !1, + null, + b, + m + ), f && f._last_time && t - f._last_time < 1e3) { + var E = 2 - (t - f._last_time) * 2e-3, T = e.globalAlpha; + e.globalAlpha = T * E, this.renderLink( + e, + g, + d, + f, + !0, + !0, + "white", + b, + m + ), e.globalAlpha = T; + } } - - if (options.parentMenu) { - var rect = options.parentMenu.root.getBoundingClientRect(); - left = rect.left + rect.width; - } - - var body_rect = document.body.getBoundingClientRect(); - var root_rect = root.getBoundingClientRect(); - if(body_rect.height == 0) - console.error("document.body height is 0. That is dangerous, set html,body { height: 100%; }"); - - if (body_rect.width && left > body_rect.width - root_rect.width - 10) { - left = body_rect.width - root_rect.width - 10; - } - if (body_rect.height && top > body_rect.height - root_rect.height - 10) { - top = body_rect.height - root_rect.height - 10; - } - } - - root.style.left = left + "px"; - root.style.top = top + "px"; - - if (options.scale) { - root.style.transform = "scale(" + options.scale + ")"; + } } } - - ContextMenu.prototype.addItem = function(name, value, options) { - var that = this; - options = options || {}; - - var element = document.createElement("div"); - element.className = "litemenu-entry submenu"; - - var disabled = false; - - if (value === null) { - element.classList.add("separator"); - //element.innerHTML = "
" - //continue; - } else { - element.innerHTML = value && value.title ? value.title : name; - element.value = value; - - if (value) { - if (value.disabled) { - disabled = true; - element.classList.add("disabled"); - } - if (value.submenu || value.has_submenu) { - element.classList.add("has_submenu"); - } - } - - if (typeof value == "function") { - element.dataset["value"] = name; - element.onclick_callback = value; - } else { - element.dataset["value"] = value; - } - - if (value.className) { - element.className += " " + value.className; - } + e.globalAlpha = 1; + } + /** + * draws a link between two points + * @param a start pos + * @param b end pos + * @param link the link object with all the link info + * @param skipBorder ignore the shadow of the link + * @param flow show flow animation (for events) + * @param color the color for the link + * @param startDir the direction enum + * @param endDir the direction enum + * @param numSublines number of sublines (useful to represent vec3 or rgb) + **/ + renderLink(e, t, i, n, s, r, o, a, l, h) { + n && this.visible_links.push(n), !o && n && (o = n.color || this.link_type_colors[n.type]), o || (o = this.default_link_color), n != null && this.highlighted_links[n.id] && (o = "#FFF"), a = a || w.RIGHT, l = l || w.LEFT; + var p = u.distance(t, i); + this.render_connections_border && this.ds.scale > 0.6 && (e.lineWidth = this.connections_width + 4), e.lineJoin = "round", h = h || 1, h > 1 && (e.lineWidth = 0.5), e.beginPath(); + for (var f = 0; f < h; f += 1) { + var c = (f - (h - 1) * 0.5) * 5; + if (this.links_render_mode == de.SPLINE_LINK) { + e.moveTo(t[0], t[1] + c); + var v = 0, g = 0, d = 0, _ = 0; + switch (a) { + case w.LEFT: + v = p * -0.25; + break; + case w.RIGHT: + v = p * 0.25; + break; + case w.UP: + g = p * -0.25; + break; + case w.DOWN: + g = p * 0.25; + break; } - - this.root.appendChild(element); - if (!disabled) { - element.addEventListener("click", inner_onclick); + switch (l) { + case w.LEFT: + d = p * -0.25; + break; + case w.RIGHT: + d = p * 0.25; + break; + case w.UP: + _ = p * -0.25; + break; + case w.DOWN: + _ = p * 0.25; + break; } - if (!disabled && options.autoopen) { - LiteGraph.pointerListenerAdd(element,"enter",inner_over); + e.bezierCurveTo( + t[0] + v, + t[1] + g + c, + i[0] + d, + i[1] + _ + c, + i[0], + i[1] + c + ); + } else if (this.links_render_mode == de.LINEAR_LINK) { + e.moveTo(t[0], t[1] + c); + var v = 0, g = 0, d = 0, _ = 0; + switch (a) { + case w.LEFT: + v = -1; + break; + case w.RIGHT: + v = 1; + break; + case w.UP: + g = -1; + break; + case w.DOWN: + g = 1; + break; } - - function inner_over(e) { - var value = this.value; - if (!value || !value.has_submenu) { - return; - } - //if it is a submenu, autoopen like the item was clicked - inner_onclick.call(this, e); + switch (l) { + case w.LEFT: + d = -1; + break; + case w.RIGHT: + d = 1; + break; + case w.UP: + _ = -1; + break; + case w.DOWN: + _ = 1; + break; } - - //menu option clicked - function inner_onclick(e) { - var value = this.value; - var close_parent = true; - - if (that.current_submenu) { - that.current_submenu.close(e); - } - - //global callback - if (options.callback) { - var r = options.callback.call( - this, - value, - options, - e, - that, - options.node - ); - if (r === true) { - close_parent = false; - } - } - - //special cases - if (value) { - if ( - value.callback && - !options.ignore_item_callbacks && - value.disabled !== true - ) { - //item callback - var r = value.callback.call( - this, - value, - options, - e, - that, - options.extra - ); - if (r === true) { - close_parent = false; - } - } - if (value.submenu) { - if (!value.submenu.options) { - throw "ContextMenu submenu needs options"; - } - var submenu = new that.constructor(value.submenu.options, { - callback: value.submenu.callback, - event: e, - parentMenu: that, - ignore_item_callbacks: - value.submenu.ignore_item_callbacks, - title: value.submenu.title, - extra: value.submenu.extra, - autoopen: options.autoopen - }); - close_parent = false; - } - } - - if (close_parent && !that.lock) { - that.close(); - } - } - - return element; - }; - - ContextMenu.prototype.close = function(e, ignore_parent_menu) { - if (this.root.parentNode) { - this.root.parentNode.removeChild(this.root); - } - if (this.parentMenu && !ignore_parent_menu) { - this.parentMenu.lock = false; - this.parentMenu.current_submenu = null; - if (e === undefined) { - this.parentMenu.close(); - } else if ( - e && - !ContextMenu.isCursorOverElement(e, this.parentMenu.root) - ) { - ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+"leave", e); - } - } - if (this.current_submenu) { - this.current_submenu.close(e, true); - } - - if (this.root.closing_timer) { - clearTimeout(this.root.closing_timer); - } - - // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu - // on key press, allow filtering/selecting the context menu elements - }; - - //this code is used to trigger events easily (used in the context menu mouseleave - ContextMenu.trigger = function(element, event_name, params, origin) { - var evt = document.createEvent("CustomEvent"); - evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail - evt.srcElement = origin; - if (element.dispatchEvent) { - element.dispatchEvent(evt); - } else if (element.__events) { - element.__events.dispatchEvent(evt); - } - //else nothing seems binded here so nothing to do - return evt; - }; - - //returns the top most menu - ContextMenu.prototype.getTopMenu = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getTopMenu(); - } - return this; - }; - - ContextMenu.prototype.getFirstEvent = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getFirstEvent(); - } - return this.options.event; - }; - - ContextMenu.isCursorOverElement = function(event, element) { - var left = event.clientX; - var top = event.clientY; - var rect = element.getBoundingClientRect(); - if (!rect) { - return false; - } - if ( - top > rect.top && - top < rect.top + rect.height && - left > rect.left && - left < rect.left + rect.width - ) { - return true; - } - return false; - }; - - LiteGraph.ContextMenu = ContextMenu; - - LiteGraph.closeAllContextMenus = function(ref_window) { - ref_window = ref_window || window; - - var elements = ref_window.document.querySelectorAll(".litecontextmenu"); - if (!elements.length) { - return; - } - - var result = []; - for (var i = 0; i < elements.length; i++) { - result.push(elements[i]); - } - - for (var i=0; i < result.length; i++) { - if (result[i].close) { - result[i].close(); - } else if (result[i].parentNode) { - result[i].parentNode.removeChild(result[i]); - } - } - }; - - LiteGraph.extendClass = function(target, origin) { - for (var i in origin) { - //copy class properties - if (target.hasOwnProperty(i)) { - continue; - } - target[i] = origin[i]; - } - - if (origin.prototype) { - //copy prototype properties - for (var i in origin.prototype) { - //only enumerable - if (!origin.prototype.hasOwnProperty(i)) { - continue; - } - - if (target.prototype.hasOwnProperty(i)) { - //avoid overwriting existing ones - continue; - } - - //copy getters - if (origin.prototype.__lookupGetter__(i)) { - target.prototype.__defineGetter__( - i, - origin.prototype.__lookupGetter__(i) - ); - } else { - target.prototype[i] = origin.prototype[i]; - } - - //and setters - if (origin.prototype.__lookupSetter__(i)) { - target.prototype.__defineSetter__( - i, - origin.prototype.__lookupSetter__(i) - ); - } - } - } - }; - - //used by some widgets to render a curve editor - function CurveEditor( points ) - { - this.points = points; - this.selected = -1; - this.nearest = -1; - this.size = null; //stores last size used - this.must_update = true; - this.margin = 5; - } - - CurveEditor.sampleCurve = function(f,points) - { - if(!points) - return; - for(var i = 0; i < points.length - 1; ++i) - { - var p = points[i]; - var pn = points[i+1]; - if(pn[0] < f) - continue; - var r = (pn[0] - p[0]); - if( Math.abs(r) < 0.00001 ) - return p[1]; - var local_f = (f - p[0]) / r; - return p[1] * (1.0 - local_f) + pn[1] * local_f; - } - return 0; - } - - CurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive ) - { - var points = this.points; - if(!points) - return; - this.size = size; - var w = size[0] - this.margin * 2; - var h = size[1] - this.margin * 2; - - line_color = line_color || "#666"; - - ctx.save(); - ctx.translate(this.margin,this.margin); - - if(background_color) - { - ctx.fillStyle = "#111"; - ctx.fillRect(0,0,w,h); - ctx.fillStyle = "#222"; - ctx.fillRect(w*0.5,0,1,h); - ctx.strokeStyle = "#333"; - ctx.strokeRect(0,0,w,h); - } - ctx.strokeStyle = line_color; - if(inactive) - ctx.globalAlpha = 0.5; - ctx.beginPath(); - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.lineTo( p[0] * w, (1.0 - p[1]) * h ); - } - ctx.stroke(); - ctx.globalAlpha = 1; - if(!inactive) - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.fillStyle = this.selected == i ? "#FFF" : (this.nearest == i ? "#DDD" : "#AAA"); - ctx.beginPath(); - ctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 ); - ctx.fill(); - } - ctx.restore(); - } - - //localpos is mouse in curve editor space - CurveEditor.prototype.onMouseDown = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - if( localpos[1] < 0 ) - return; - - //this.captureInput(true); - var w = this.size[0] - this.margin * 2; - var h = this.size[1] - this.margin * 2; - var x = localpos[0] - this.margin; - var y = localpos[1] - this.margin; - var pos = [x,y]; - var max_dist = 30 / graphcanvas.ds.scale; - //search closer one - this.selected = this.getCloserPoint(pos, max_dist); - //create one - if(this.selected == -1) - { - var point = [x / w, 1 - y / h]; - points.push(point); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - if(this.selected != -1) - return true; - } - - CurveEditor.prototype.onMouseMove = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - var s = this.selected; - if(s < 0) - return; - var x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 ); - var y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 ); - var curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)]; - var max_dist = 30 / graphcanvas.ds.scale; - this._nearest = this.getCloserPoint(curvepos, max_dist); - var point = points[s]; - if(point) - { - var is_edge_point = s == 0 || s == points.length - 1; - if( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) ) - { - points.splice(s,1); - this.selected = -1; - return; - } - if( !is_edge_point ) //not edges - point[0] = clamp(x, 0, 1); - else - point[0] = s == 0 ? 0 : 1; - point[1] = 1.0 - clamp(y, 0, 1); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - } - - CurveEditor.prototype.onMouseUp = function( localpos, graphcanvas ) - { - this.selected = -1; - return false; - } - - CurveEditor.prototype.getCloserPoint = function(pos, max_dist) - { - var points = this.points; - if(!points) - return -1; - max_dist = max_dist || 30; - var w = (this.size[0] - this.margin * 2); - var h = (this.size[1] - this.margin * 2); - var num = points.length; - var p2 = [0,0]; - var min_dist = 1000000; - var closest = -1; - var last_valid = -1; - for(var i = 0; i < num; ++i) - { - var p = points[i]; - p2[0] = p[0] * w; - p2[1] = (1.0 - p[1]) * h; - if(p2[0] < pos[0]) - last_valid = i; - var dist = vec2.distance(pos,p2); - if(dist > min_dist || dist > max_dist) - continue; - closest = i; - min_dist = dist; - } - return closest; - } - - LiteGraph.CurveEditor = CurveEditor; - - //used to create nodes from wrapping functions - LiteGraph.getParameterNames = function(func) { - return (func + "") - .replace(/[/][/].*$/gm, "") // strip single-line comments - .replace(/\s+/g, "") // strip white space - .replace(/[/][*][^/*]*[*][/]/g, "") // strip multi-line comments /**/ - .split("){", 1)[0] - .replace(/^[^(]*[(]/, "") // extract the parameters - .replace(/=[^,]+/g, "") // strip any ES6 defaults - .split(",") - .filter(Boolean); // split & filter [""] - }; - - /* helper for interaction: pointer, touch, mouse Listeners - used by LGraphCanvas DragAndScale ContextMenu*/ - LiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) { - if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!=="function"){ - //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- - } - - var sMethod = LiteGraph.pointerevents_method; - var sEvent = sEvIn; - - // UNDER CONSTRUCTION - // convert pointerevents to touch event when not available - if (sMethod=="pointer" && !window.PointerEvent){ - console.warn("sMethod=='pointer' && !window.PointerEvent"); - console.log("Converting pointer["+sEvent+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."); - switch(sEvent){ - case "down":{ - sMethod = "touch"; - sEvent = "start"; - break; - } - case "move":{ - sMethod = "touch"; - //sEvent = "move"; - break; - } - case "up":{ - sMethod = "touch"; - sEvent = "end"; - break; - } - case "cancel":{ - sMethod = "touch"; - //sEvent = "cancel"; - break; - } - case "enter":{ - console.log("debug: Should I send a move event?"); // ??? - break; - } - // case "over": case "out": not used at now - default:{ - console.warn("PointerEvent not available in this browser ? The event "+sEvent+" would not be called"); - } - } - } - - switch(sEvent){ - //both pointer and move events - case "down": case "up": case "move": case "over": case "out": case "enter": - { - oDOM.addEventListener(sMethod+sEvent, fCall, capture); - } - // only pointerevents - case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (sMethod!="mouse"){ - return oDOM.addEventListener(sMethod+sEvent, fCall, capture); - } - } - // not "pointer" || "mouse" - default: - return oDOM.addEventListener(sEvent, fCall, capture); - } - } - LiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) { - if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!=="function"){ - //console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- - } - switch(sEvent){ - //both pointer and move events - case "down": case "up": case "move": case "over": case "out": case "enter": - { - if (LiteGraph.pointerevents_method=="pointer" || LiteGraph.pointerevents_method=="mouse"){ - oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); - } - } - // only pointerevents - case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (LiteGraph.pointerevents_method=="pointer"){ - return oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); - } - } - // not "pointer" || "mouse" - default: - return oDOM.removeEventListener(sEvent, fCall, capture); - } - } - - function clamp(v, a, b) { - return a > v ? a : b < v ? b : v; - }; - global.clamp = clamp; - - if (typeof window != "undefined" && !window["requestAnimationFrame"]) { - window.requestAnimationFrame = - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - function(callback) { - window.setTimeout(callback, 1000 / 60); - }; + var y = 15; + e.lineTo( + t[0] + v * y, + t[1] + g * y + c + ), e.lineTo( + i[0] + d * y, + i[1] + _ * y + c + ), e.lineTo(i[0], i[1] + c); + } else if (this.links_render_mode == de.STRAIGHT_LINK) { + e.moveTo(t[0], t[1]); + var b = t[0], m = t[1], E = i[0], T = i[1]; + a == w.RIGHT ? b += 10 : m += 10, l == w.LEFT ? E -= 10 : T -= 10, e.lineTo(b, m), e.lineTo((b + E) * 0.5, m), e.lineTo((b + E) * 0.5, T), e.lineTo(E, T), e.lineTo(i[0], i[1]); + } else + return; } -})(this); - -if (typeof exports != "undefined") { - exports.LiteGraph = this.LiteGraph; - exports.LGraph = this.LGraph; - exports.LLink = this.LLink; - exports.LGraphNode = this.LGraphNode; - exports.LGraphGroup = this.LGraphGroup; - exports.DragAndScale = this.DragAndScale; - exports.LGraphCanvas = this.LGraphCanvas; - exports.ContextMenu = this.ContextMenu; + this.render_connections_border && this.ds.scale > 0.6 && !s && (e.strokeStyle = "rgba(0,0,0,0.5)", e.stroke()), e.lineWidth = this.connections_width, e.fillStyle = e.strokeStyle = o, e.stroke(); + var O = this.computeConnectionPoint(t, i, 0.5, a, l); + if (n && n._pos && (n._pos[0] = O[0], n._pos[1] = O[1]), this.ds.scale >= 0.6 && this.highquality_render && l != w.CENTER) { + if (this.render_connection_arrows) { + var A = this.computeConnectionPoint( + t, + i, + 0.25, + a, + l + ), M = this.computeConnectionPoint( + t, + i, + 0.26, + a, + l + ), L = this.computeConnectionPoint( + t, + i, + 0.75, + a, + l + ), B = this.computeConnectionPoint( + t, + i, + 0.76, + a, + l + ), G = 0, z = 0; + this.render_curved_connections ? (G = -Math.atan2(M[0] - A[0], M[1] - A[1]), z = -Math.atan2(B[0] - L[0], B[1] - L[1])) : z = G = i[1] > t[1] ? 0 : Math.PI, e.save(), e.translate(A[0], A[1]), e.rotate(G), e.beginPath(), e.moveTo(-5, -3), e.lineTo(0, 7), e.lineTo(5, -3), e.fill(), e.restore(), e.save(), e.translate(L[0], L[1]), e.rotate(z), e.beginPath(), e.moveTo(-5, -3), e.lineTo(0, 7), e.lineTo(5, -3), e.fill(), e.restore(); + } + e.beginPath(), e.arc(O[0], O[1], 5, 0, Math.PI * 2), e.fill(); + } + if (r) { + e.fillStyle = o; + for (var f = 0; f < 5; ++f) { + var pe = (u.getTime() * 1e-3 + f * 0.2) % 1, O = this.computeConnectionPoint( + t, + i, + pe, + a, + l + ); + e.beginPath(), e.arc(O[0], O[1], 5, 0, 2 * Math.PI), e.fill(); + } + } + } + computeConnectionPoint(e, t, i, n = w.RIGHT, s = w.LEFT) { + var r = u.distance(e, t), o = e, a = [e[0], e[1]], l = [t[0], t[1]], h = t; + switch (n) { + case w.LEFT: + a[0] += r * -0.25; + break; + case w.RIGHT: + a[0] += r * 0.25; + break; + case w.UP: + a[1] += r * -0.25; + break; + case w.DOWN: + a[1] += r * 0.25; + break; + } + switch (s) { + case w.LEFT: + l[0] += r * -0.25; + break; + case w.RIGHT: + l[0] += r * 0.25; + break; + case w.UP: + l[1] += r * -0.25; + break; + case w.DOWN: + l[1] += r * 0.25; + break; + } + var p = (1 - i) * (1 - i) * (1 - i), f = 3 * ((1 - i) * (1 - i)) * i, c = 3 * (1 - i) * (i * i), v = i * i * i, g = p * o[0] + f * a[0] + c * l[0] + v * h[0], d = p * o[1] + f * a[1] + c * l[1] + v * h[1]; + return [g, d]; + } + drawExecutionOrder(e) { + e.shadowColor = "transparent", e.globalAlpha = 0.25, e.textAlign = "center", e.strokeStyle = "white", e.globalAlpha = 0.75; + for (var t = this.visible_nodes, i = 0; i < t.length; ++i) { + var n = t[i]; + e.fillStyle = "black", e.fillRect( + n.pos[0] - u.NODE_TITLE_HEIGHT, + n.pos[1] - u.NODE_TITLE_HEIGHT, + u.NODE_TITLE_HEIGHT, + u.NODE_TITLE_HEIGHT + ), n.order == 0 && e.strokeRect( + n.pos[0] - u.NODE_TITLE_HEIGHT + 0.5, + n.pos[1] - u.NODE_TITLE_HEIGHT + 0.5, + u.NODE_TITLE_HEIGHT, + u.NODE_TITLE_HEIGHT + ), e.fillStyle = "#FFF", e.fillText( + "" + n.order, + n.pos[0] + u.NODE_TITLE_HEIGHT * -0.5, + n.pos[1] - 6 + ); + } + e.globalAlpha = 1; + } + /** draws the widgets stored inside a node */ + drawNodeWidgets(e, t, i, n) { + if (!(!e.widgets || !e.widgets.length)) { + var s = e.size[0], r = e.widgets; + t += 2; + var o = u.NODE_WIDGET_HEIGHT, a = this.ds.scale > 0.5; + i.save(), i.globalAlpha = this.editor_alpha; + for (var l = u.WIDGET_OUTLINE_COLOR, h = u.WIDGET_BGCOLOR, p = u.WIDGET_TEXT_COLOR, f = u.WIDGET_SECONDARY_TEXT_COLOR, c = 15, v = 0; v < r.length; ++v) { + var g = r[v]; + if (!g.hidden) { + var d = t; + g.y && (d = g.y), g.last_y = d, i.strokeStyle = l, i.fillStyle = "#222", i.textAlign = "left", g.disabled && (i.globalAlpha *= 0.5); + var _ = g.width || s; + switch (g.type) { + case "button": + g.clicked && (i.fillStyle = "#AAA", g.clicked = !1, this.dirty_canvas = !0), i.fillRect(c, d, _ - c * 2, o), a && !g.disabled && !u.ignore_all_widget_events && i.strokeRect(c, d, _ - c * 2, o), a && (i.textAlign = "center", i.fillStyle = p, i.fillText(g.name, _ * 0.5, d + o * 0.7)); + break; + case "toggle": + i.textAlign = "left", i.strokeStyle = l, i.fillStyle = h, i.beginPath(), a ? i.roundRect(c, d, _ - c * 2, o, [o * 0.5]) : i.rect(c, d, _ - c * 2, o), i.fill(), a && !g.disabled && !u.ignore_all_widget_events && i.stroke(), i.fillStyle = g.value ? "#89A" : "#333", i.beginPath(), i.arc(_ - c * 2, d + o * 0.5, o * 0.36, 0, Math.PI * 2), i.fill(), a && (i.fillStyle = f, g.name != null && i.fillText(g.name, c * 2, d + o * 0.7), i.fillStyle = g.value ? p : f, i.textAlign = "right", i.fillText( + g.value ? g.options.on || "true" : g.options.off || "false", + _ - 40, + d + o * 0.7 + )); + break; + case "slider": + i.fillStyle = h, i.fillRect(c, d, _ - c * 2, o); + var y = g.options.max - g.options.min, b = (g.value - g.options.min) / y; + if (i.fillStyle = n == g ? "#89A" : "#678", i.fillRect(c, d, b * (_ - c * 2), o), a && !g.disabled && i.strokeRect(c, d, _ - c * 2, o), g.marker) { + var m = (+g.marker - g.options.min) / y; + i.fillStyle = "#AA9", i.fillRect(c + m * (_ - c * 2), d, 2, o); + } + a && (i.textAlign = "center", i.fillStyle = p, i.fillText( + g.name + " " + Number(g.value).toFixed(3), + _ * 0.5, + d + o * 0.7 + )); + break; + case "number": + case "combo": + if (i.textAlign = "left", i.strokeStyle = l, i.fillStyle = h, i.beginPath(), a ? i.roundRect(c, d, _ - c * 2, o, [o * 0.5]) : i.rect(c, d, _ - c * 2, o), i.fill(), a) + if (!g.disabled && !u.ignore_all_widget_events && i.stroke(), i.fillStyle = p, !g.disabled && !u.ignore_all_widget_events && (i.beginPath(), i.moveTo(c + 16, d + 5), i.lineTo(c + 6, d + o * 0.5), i.lineTo(c + 16, d + o - 5), i.fill(), i.beginPath(), i.moveTo(_ - c - 16, d + 5), i.lineTo(_ - c - 6, d + o * 0.5), i.lineTo(_ - c - 16, d + o - 5), i.fill()), i.fillStyle = f, i.fillText(g.name, c * 2 + 5, d + o * 0.7), i.fillStyle = p, i.textAlign = "right", g.type == "number") + i.fillText( + Number(g.value).toFixed( + g.options.precision !== void 0 ? g.options.precision : 3 + ), + _ - c * 2 - 20, + d + o * 0.7 + ); + else { + var E = g.value; + if (g.options.values) { + var T = g.options.values; + T.constructor === Function && (T = T()), T && T.constructor !== Array && (E = T[g.value]); + } + i.fillText( + E, + _ - c * 2 - 20, + d + o * 0.7 + ); + } + break; + case "string": + case "text": + i.textAlign = "left", i.strokeStyle = l, i.fillStyle = h, i.beginPath(), a ? i.roundRect(c, d, _ - c * 2, o, [o * 0.5]) : i.rect(c, d, _ - c * 2, o), i.fill(), a && (g.disabled || i.stroke(), i.save(), i.beginPath(), i.rect(c, d, _ - c * 2, o), i.clip(), i.fillStyle = f, g.name != null && i.fillText(g.name, c * 2, d + o * 0.7), i.fillStyle = p, i.textAlign = "right", i.fillText(String(g.value).substr(0, g.options.max_length || 30), _ - c * 2, d + o * 0.7), i.restore()); + break; + default: + g.draw && g.draw(i, e, _, d, o); + break; + } + t += (g.computeSize ? g.computeSize(_)[1] : o) + 4, i.globalAlpha = this.editor_alpha; + } + } + i.restore(), i.textAlign = "left"; + } + } +}; +let U = ie; +U.temp = new Float32Array(4); +U.temp_vec2 = new Float32Array(2); +U.tmp_area = new Float32Array(4); +U.margin_area = new Float32Array(4); +U.link_bounding = new Float32Array(4); +U.tempA = [0, 0]; +U.tempB = [0, 0]; +class me { + constructor(t = "Group") { + this.fontSize = u.DEFAULT_GROUP_FONT_SIZE, this._nodes = [], this.graph = null, this._bounding = new Float32Array([10, 10, 140, 80]), this.title = t, this.color = N.node_colors.pale_blue ? N.node_colors.pale_blue.groupcolor : "#AAA", this._pos = this._bounding.subarray(0, 2), this._size = this._bounding.subarray(2, 4); + } + get bounding() { + return this._bounding; + } + get pos() { + return [this._pos[0], this._pos[1]]; + } + set pos(t) { + !t || t.length < 2 || (this._pos[0] = t[0], this._pos[1] = t[1]); + } + get size() { + return [this._size[0], this._size[1]]; + } + set size(t) { + !t || t.length < 2 || (this._size[0] = Math.max(140, t[0]), this._size[1] = Math.max(80, t[1])); + } + configure(t) { + t.bounding, this.title = t.title, this._bounding.set(t.bounding), this.color = t.color, this.font = t.font; + } + serialize() { + const t = this._bounding; + return { + title: this.title, + bounding: [ + Math.round(t[0]), + Math.round(t[1]), + Math.round(t[2]), + Math.round(t[3]) + ], + color: this.color, + font: this.font + }; + } + move(t, i, n) { + if (this._pos[0] += t, this._pos[1] += i, !n) + for (var s = 0; s < this._nodes.length; ++s) { + var r = this._nodes[s]; + r.pos[0] += t, r.pos[1] += i; + } + } + recomputeInsideNodes() { + this._nodes.length = 0; + for (var t = this.graph._nodes, i = new Float32Array(4), n = 0; n < t.length; ++n) { + var s = t[n]; + s.getBounding(i), u.overlapBounding(this._bounding, i) && this._nodes.push(s); + } + } + /** checks if a point is inside the shape of a node */ + isPointInside(t, i, n = 0, s = !1) { + var r = this.graph && this.graph.isLive() ? 0 : u.NODE_TITLE_HEIGHT; + return s && (r = 0), this.pos[0] - 4 - n < t && this.pos[0] + this.size[0] + 4 + n > t && this.pos[1] - r - n < i && this.pos[1] + this.size[1] + n > i; + } + /** Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ + setDirtyCanvas(t, i = !1) { + this.graph && this.graph.sendActionToCanvas("setDirty", [t, i]); + } } - - +class he { + constructor(t, i, n, s, r, o) { + this.data = null, this._pos = [0, 0], this._last_time = 0, this.id = t, this.type = i, this.origin_id = n, this.origin_slot = s, this.target_id = r, this.target_slot = o; + } + static configure(t) { + return t instanceof Array ? new he(t[0], t[5], t[1], t[2], t[3], t[4]) : new he(t.id, t.type, t.origin_id, t.origin_slot, t.target_id, t.target_slot); + } + serialize() { + return [ + this.id, + this.origin_id, + this.origin_slot, + this.target_id, + this.target_slot, + this.type + ]; + } +} +let _e; +const He = new Uint8Array(16); +function Fe() { + if (!_e && (_e = typeof crypto < "u" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto), !_e)) + throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); + return _e(He); +} +const K = []; +for (let e = 0; e < 256; ++e) + K.push((e + 256).toString(16).slice(1)); +function Ue(e, t = 0) { + return (K[e[t + 0]] + K[e[t + 1]] + K[e[t + 2]] + K[e[t + 3]] + "-" + K[e[t + 4]] + K[e[t + 5]] + "-" + K[e[t + 6]] + K[e[t + 7]] + "-" + K[e[t + 8]] + K[e[t + 9]] + "-" + K[e[t + 10]] + K[e[t + 11]] + K[e[t + 12]] + K[e[t + 13]] + K[e[t + 14]] + K[e[t + 15]]).toLowerCase(); +} +const ze = typeof crypto < "u" && crypto.randomUUID && crypto.randomUUID.bind(crypto), ke = { + randomUUID: ze +}; +function oe(e, t, i) { + if (ke.randomUUID && !t && !e) + return ke.randomUUID(); + e = e || {}; + const n = e.random || (e.rng || Fe)(); + if (n[6] = n[6] & 15 | 64, n[8] = n[8] & 63 | 128, t) { + i = i || 0; + for (let s = 0; s < 16; ++s) + t[i + s] = n[s]; + return t; + } + return Ue(n); +} +const Ne = class { + constructor(e) { + this.desc = "", this.pos = [0, 0], this.subgraph = null, this.skip_subgraph_button = !1, this.priority = 0, this.removable = !0, this.clonable = !0, this.collapsable = !0, this.titleMode = se.NORMAL_TITLE, this.serialize_widgets = !1, this.hide_in_node_lists = !1, this.block_delete = !1, this.ignore_remove = !1, this.last_serialization = null, this._relative_id = null, this.exec_version = 0, this.action_call = null, this.execute_triggered = 0, this.action_triggered = 0, this.console = [], this.title = e || "Unnamed", this.size = [u.NODE_WIDTH, 60], this.graph = null, this.pos = [10, 10], u.use_uuids ? this.id = oe() : this.id = -1, this.type = null, this.inputs = [], this.outputs = [], this.connections = [], this.properties = {}, this.properties_info = [], this.flags = {}; + } + get slotLayout() { + return "slotLayout" in this.constructor ? this.constructor.slotLayout : null; + } + /** configure a node from an object containing the serialized info */ + configure(e) { + this.graph && this.graph._version++; + for (var t in e) { + if (t == "properties") { + for (var i in e.properties) + this.properties[i] = e.properties[i], this.onPropertyChanged && this.onPropertyChanged(i, e.properties[i]); + continue; + } + e[t] != null && (typeof e[t] == "object" ? this[t] && this[t].configure ? this[t].configure(e[t]) : this[t] = u.cloneObject(e[t], this[t]) : this[t] = e[t]); + } + e.title || (this.title = ye(this, "title") || this.title); + const n = e.bgColor; + if (n != null && (this.bgcolor || (this.bgcolor = n)), this.inputs) + for (let o = 0; o < this.inputs.length; ++o) { + let a = this.inputs[o], l = this.graph ? this.graph.links[a.link] : null; + a.properties || (a.properties = {}), this.onConnectionsChange && this.onConnectionsChange(Y.INPUT, o, !0, l, a), this.onInputAdded && this.onInputAdded(a); + } + if (this.outputs) + for (var s = 0; s < this.outputs.length; ++s) { + let o = this.outputs[s]; + if (o.properties || (o.properties = {}), !!o.links) { + for (let a = 0; a < o.links.length; ++a) { + let l = this.graph ? this.graph.links[o.links[a]] : null; + this.onConnectionsChange && this.onConnectionsChange(Y.OUTPUT, s, !0, l, o); + } + this.onOutputAdded && this.onOutputAdded(o); + } + } + if (this.widgets) { + for (var s = 0; s < this.widgets.length; ++s) { + var r = this.widgets[s]; + r && r.options && r.options.property && this.properties[r.options.property] && (r.value = JSON.parse(JSON.stringify(this.properties[r.options.property]))); + } + if (e.widgets_values) + for (var s = 0; s < e.widgets_values.length; ++s) + this.widgets[s] && (this.widgets[s].value = e.widgets_values[s]); + } + this.onConfigure && this.onConfigure(e); + } + /** serialize the content */ + serialize() { + let e = { + id: this.id, + type: this.type, + pos: this.pos, + size: this.size, + flags: u.cloneObject(this.flags), + order: this.order, + mode: this.mode + }; + if (this.constructor === Ne && this.last_serialization) + return this.last_serialization; + if (this.inputs && (e.inputs = this.inputs), this.outputs) { + for (var t = 0; t < this.outputs.length; t++) + delete this.outputs[t]._data; + e.outputs = this.outputs; + } + if (this.title && this.title != this.constructor.title && (e.title = this.title), this.properties && (e.properties = u.cloneObject(this.properties)), this.widgets && this.serialize_widgets) { + e.widgets_values = []; + for (var t = 0; t < this.widgets.length; ++t) + this.widgets[t] ? e.widgets_values[t] = this.widgets[t].value : e.widgets_values[t] = null; + } + return e.type || (e.type = this.constructor.type), this.color && (e.color = this.color), this.bgcolor && (e.bgcolor = this.bgcolor), this.boxcolor && (e.boxcolor = this.boxcolor), this.shape && (e.shape = this.shape), this.onSerialize && this.onSerialize(e), e; + } + /** Creates a clone of this node */ + clone(e = { forNode: {} }) { + var t = u.createNode(this.type); + if (!t) + return null; + var i = u.cloneObject(this.serialize()); + if (i.inputs) + for (var n = 0; n < i.inputs.length; ++n) + i.inputs[n].link = null; + if (i.outputs) + for (var n = 0; n < i.outputs.length; ++n) + i.outputs[n].links && (i.outputs[n].links.length = 0); + return delete i.id, u.use_uuids && (i.id = oe()), t.configure(i), t; + } + /** serialize and stringify */ + toString() { + return JSON.stringify(this.serialize()); + } + /** get the title string */ + getTitle() { + return this.title || this.constructor.title; + } + getRootGraph() { + var t; + let e = this.graph; + for (; e && e._is_subgraph; ) + e = (t = e._subgraph_node) == null ? void 0 : t.graph; + return e == null || e._is_subgraph ? null : e; + } + *iterateParentSubgraphNodes() { + var t; + let e = this.graph._subgraph_node; + for (; e; ) + yield e, e = (t = e.graph) == null ? void 0 : t._subgraph_node; + } + /** sets the value of a property */ + setProperty(e, t) { + if (this.properties || (this.properties = {}), t !== this.properties[e]) { + var i = this.properties[e]; + if (this.properties[e] = t, this.graph && this.graph._version++, this.onPropertyChanged && this.onPropertyChanged(e, t, i) === !1 && (this.properties[e] = i), this.widgets) + for (var n = 0; n < this.widgets.length; ++n) { + var s = this.widgets[n]; + if (s && s.options.property == e) { + s.value = t; + break; + } + } + } + } + getInputSlotProperty(e, t) { + if (!(!this.inputs || !this.graph) && !(e == -1 || e >= this.inputs.length)) { + var i = this.inputs[e]; + if (i) + return i.properties || (i.properties = {}), i.properties[t]; + } + } + getOutputSlotProperty(e, t) { + if (!(!this.outputs || !this.graph) && !(e == -1 || e >= this.outputs.length)) { + var i = this.outputs[e]; + if (i) + return i.properties || (i.properties = {}), i.properties[t]; + } + } + setInputSlotProperty(e, t, i) { + if (!(!this.inputs || !this.graph) && !(e == -1 || e >= this.inputs.length)) { + var n = this.inputs[e]; + if (n && (n.properties || (n.properties = {}), i !== n.properties[t])) { + var s = n.properties[t]; + n.properties[t] = i, this.graph && this.graph._version++, this.onSlotPropertyChanged && this.onSlotPropertyChanged(Y.INPUT, e, n, t, i, s) === !1 && (n.properties[t] = s); + } + } + } + setOutputSlotProperty(e, t, i) { + if (!(!this.outputs || !this.graph) && !(e == -1 || e >= this.outputs.length)) { + var n = this.outputs[e]; + if (n && (n.properties || (n.properties = {}), i !== n.properties[t])) { + var s = n.properties[t]; + n.properties[t] = i, this.graph && this.graph._version++, this.onSlotPropertyChanged && this.onSlotPropertyChanged(Y.OUTPUT, e, n, t, i, s) === !1 && (n.properties[t] = s); + } + } + } + /** sets the output data */ + setOutputData(e, t) { + if (!(!this.outputs || !this.graph) && !(e == -1 || e >= this.outputs.length)) { + var i = this.outputs[e]; + if (i && (u.serialize_slot_data ? i._data = t : i._data = void 0, this.outputs[e].links)) + for (var n = 0; n < this.outputs[e].links.length; n++) { + var s = this.outputs[e].links[n], r = this.graph.links[s]; + r && (r.data = t); + } + } + } + /** sets the output data */ + setOutputDataType(e, t) { + if (this.outputs && !(e == -1 || e >= this.outputs.length)) { + var i = this.outputs[e]; + if (i && (i.type = t, this.outputs[e].links)) + for (let n = this.outputs[e].links.length - 1; n >= 0; n--) { + const s = this.outputs[e].links[n], r = this.graph.links[s]; + if (r) { + r.type = t; + const o = this.graph.getNodeById(r.target_id); + if (o) { + const a = o.getInputInfo(r.target_slot); + a && !u.isValidConnection(t, a.type) && o.disconnectInput(r.target_slot); + } + } + } + } + } + *iterateInputInfo() { + for (let e = 0; e < this.inputs.length; e++) + yield this.inputs[e]; + } + /** + * Retrieves the input data (data traveling through the connection) from one slot + * @param slot + * @param force_update if set to true it will force the connected node of this slot to output data into this link + * @return data or if it is not connected returns undefined + */ + getInputData(e, t) { + if (!(!this.inputs || !this.graph) && !(e >= this.inputs.length || this.inputs[e].link == null)) { + var i = this.inputs[e].link, n = this.graph.links[i]; + if (!n) + return u.debug && console.error(`Link not found in slot ${e}!`, this, this.inputs[e], i), null; + if (!t) + return n.data; + var s = this.graph.getNodeById(n.origin_id); + return s && (s.updateOutputData ? s.updateOutputData(n.origin_slot) : s.onExecute && s.onExecute(null, {})), n.data; + } + } + /** + * Retrieves the input data type (in case this supports multiple input types) + * @param slot + * @return datatype in string format + */ + getInputDataType(e) { + if (!this.inputs || e >= this.inputs.length || this.inputs[e].link == null) + return null; + var t = this.inputs[e].link, i = this.graph.links[t]; + if (!i) + return u.debug && console.error(`Link not found in slot ${e}!`, this, this.inputs[e], t), null; + var n = this.graph.getNodeById(i.origin_id); + if (!n) + return i.type; + var s = n.outputs[i.origin_slot]; + return s && s.type != -1 ? s.type : null; + } + /** + * Retrieves the input data from one slot using its name instead of slot number + * @param slot_name + * @param force_update if set to true it will force the connected node of this slot to output data into this link + * @return data or if it is not connected returns null + */ + getInputDataByName(e, t) { + var i = this.findInputSlotIndexByName(e); + return i == -1 ? null : this.getInputData(i, t); + } + /** tells you if there is a connection in one input slot */ + isInputConnected(e) { + return this.inputs ? e < this.inputs.length && this.inputs[e].link != null : !1; + } + /** tells you info about an input connection (which node, type, etc) */ + getInputInfo(e) { + return this.inputs && e < this.inputs.length ? this.inputs[e] : null; + } + /** + * Returns the link info in the connection of an input slot + * @param {number} slot + * @return {LLink} object or null + */ + getInputLink(e) { + if (!this.inputs || !this.graph) + return null; + if (e < this.inputs.length) { + var t = this.inputs[e]; + return this.graph.links[t.link]; + } + return null; + } + /** returns the node connected in the input slot */ + getInputNode(e) { + if (!this.inputs || !this.graph) + return null; + if (e < this.inputs.length) { + const i = this.inputs[e].link, n = this.graph.links[i]; + if (!n) + return u.debug && console.error(`Link not found in slot ${e}!`, this, this.inputs[e], i), null; + var t = this.graph.getNodeById(n.origin_id); + if (t) + return t; + } + return null; + } + /** returns the value of an input with this name, otherwise checks if there is a property with that name */ + getInputOrProperty(e) { + if (!this.inputs || !this.inputs.length || !this.graph) + return this.properties ? this.properties[e] : null; + for (var t = 0, i = this.inputs.length; t < i; ++t) { + var n = this.inputs[t]; + if (e == n.name && n.link != null) { + var s = this.graph.links[n.link]; + if (s) + return s.data; + } + } + return this.properties[e]; + } + /** sets the input data type */ + setInputDataType(e, t) { + if (!(!this.inputs || !this.graph) && !(e == -1 || e >= this.inputs.length)) { + var i = this.inputs[e]; + if (i && (i.type = t, i.link)) { + const n = i.link, s = this.graph.links[n]; + s.type = t; + const r = this.graph.getNodeById(s.origin_id); + if (r) { + const o = r.getOutputInfo(s.origin_slot); + o && !u.isValidConnection(o.type, t) && r.disconnectOutput(s.origin_slot); + } + } + } + } + /** + * Returns the output slot in another node that an input in this node is connected to. + * @param {number} slot + * @return {LLink} object or null + */ + getOutputSlotConnectedTo(e) { + if (!this.outputs || !this.graph) + return null; + if (e >= 0 && e < this.outputs.length) { + var t = this.inputs[e]; + if (t.link) { + const i = this.graph.links[t.link]; + return this.graph.getNodeById(i.origin_id).outputs[i.origin_slot]; + } + } + return null; + } + *iterateOutputInfo() { + for (let e = 0; e < this.outputs.length; e++) + yield this.outputs[e]; + } + /** tells you the last output data that went in that slot */ + getOutputData(e) { + if (!this.outputs || !this.graph || e >= this.outputs.length) + return null; + var t = this.outputs[e]; + return t._data; + } + /** + * Returns the link info in the connection of an output slot + * @param {number} slot + * @return {LLink} object or null + */ + getOutputLinks(e) { + if (!this.outputs || !this.graph) + return []; + if (e >= 0 && e < this.outputs.length) { + var t = this.outputs[e]; + if (t.links) { + var i = []; + for (const n of t.links) + i.push(this.graph.links[n]); + return i; + } + } + return []; + } + /** + * Returns the input slots in other nodes that an output in this node is connected to. + * @param {number} slot + * @return {LLink} object or null + */ + getInputSlotsConnectedTo(e) { + if (!this.outputs || !this.graph) + return []; + if (e >= 0 && e < this.outputs.length) { + var t = this.outputs[e]; + if (t.links) { + var i = []; + for (const n of t.links) { + const s = this.graph.links[n], r = this.graph.getNodeById(s.target_id); + i.push(r.inputs[s.target_slot]); + } + return i; + } + } + return []; + } + /** tells you info about an output connection (which node, type, etc) */ + getOutputInfo(e) { + return this.outputs && e < this.outputs.length ? this.outputs[e] : null; + } + /** tells you if there is a connection in one output slot */ + isOutputConnected(e) { + return !this.outputs || !this.graph ? !1 : e < this.outputs.length && this.outputs[e].links && this.outputs[e].links.length > 0; + } + /** tells you if there is any connection in the output slots */ + isAnyOutputConnected() { + if (!this.outputs || !this.graph) + return !1; + for (var e = 0; e < this.outputs.length; ++e) + if (this.outputs[e].links && this.outputs[e].links.length) + return !0; + return !1; + } + /** retrieves all the nodes connected to this output slot */ + getOutputNodes(e) { + if (!this.outputs || this.outputs.length == 0 || !this.graph || e >= this.outputs.length) + return null; + var t = this.outputs[e]; + if (!t.links || t.links.length == 0) + return null; + for (var i = [], n = 0; n < t.links.length; n++) { + var s = t.links[n], r = this.graph.links[s]; + if (r) { + var o = this.graph.getNodeById(r.target_id); + o && i.push(o); + } + } + return i; + } + *iterateAllLinks() { + if (this.graph) { + for (const e of this.iterateInputInfo()) + if (e.link) { + const t = this.graph.links[e.link]; + t && (yield t); + } + for (const e of this.iterateOutputInfo()) + if (e.links != null) + for (const t of e.links) { + const i = this.graph.links[t]; + i && (yield i); + } + } + } + addOnTriggerInput() { + var e = this.findInputSlotIndexByName("onTrigger"); + if (e == -1) { + //!trigS || + return this.addInput("onTrigger", I.EVENT, { optional: !0, nameLocked: !0 }), this.findInputSlotIndexByName("onTrigger"); + } + return e; + } + addOnExecutedOutput() { + var e = this.findOutputSlotIndexByName("onExecuted"); + if (e == -1) { + //!trigS || + return this.addOutput("onExecuted", I.ACTION, { optional: !0, nameLocked: !0 }), this.findOutputSlotIndexByName("onExecuted"); + } + return e; + } + onAfterExecuteNode(e, t) { + var i = this.findOutputSlotIndexByName("onExecuted"); + i != -1 && this.triggerSlot(i, e, null, t); + } + changeMode(e) { + switch (e) { + case Z.ON_EVENT: + break; + case Z.ON_TRIGGER: + this.addOnTriggerInput(), this.addOnExecutedOutput(); + break; + case Z.NEVER: + break; + case Z.ALWAYS: + break; + case Z.ON_REQUEST: + break; + default: + return !1; + } + return this.mode = e, !0; + } + doExecute(e, t = {}) { + this.onExecute && (t.action_call || (t.action_call = this.id + "_exec_" + Math.floor(Math.random() * 9999)), this.graph.nodes_executing[this.id] = !0, this.onExecute(e, t), this.graph.nodes_executing[this.id] = !1, this.exec_version = this.graph.iteration, t && t.action_call && (this.action_call = t.action_call, this.graph.nodes_executedAction[this.id] = t.action_call)), this.execute_triggered = 2, this.onAfterExecuteNode && this.onAfterExecuteNode(e, t); + } + /** + * Triggers an action, wrapped by logics to control execution flow + * @method actionDo + * @param {String} action name + * @param {*} param + */ + actionDo(e, t, i = {}) { + this.onAction && (i.action_call || (i.action_call = this.id + "_" + (e || "action") + "_" + Math.floor(Math.random() * 9999)), this.graph.nodes_actioning[this.id] = e || "actioning", this.onAction(e, t, i), this.graph.nodes_actioning[this.id] = !1, i && i.action_call && (this.action_call = i.action_call, this.graph.nodes_executedAction[this.id] = i.action_call)), this.action_triggered = 2, this.onAfterExecuteNode && this.onAfterExecuteNode(t, i); + } + /** Triggers an event in this node, this will trigger any output with the same name */ + trigger(e, t, i) { + if (!(!this.outputs || !this.outputs.length)) { + this.graph && (this.graph._last_trigger_time = u.getTime()); + for (var n = 0; n < this.outputs.length; ++n) { + var s = this.outputs[n]; + !s || s.type !== I.EVENT || e && s.name != e || this.triggerSlot(n, t, null, i); + } + } + } + /** + * Triggers an slot event in this node + * @param slot the index of the output slot + * @param param + * @param link_id in case you want to trigger and specific output link in a slot + */ + triggerSlot(e, t, i, n = {}) { + if (this.outputs) { + if (e == null) { + console.error("slot must be a number"); + return; + } + typeof e != "number" && console.warn("slot must be a number, use node.trigger('name') if you want to use a string"); + var s = this.outputs[e]; + if (s) { + var r = s.links; + if (!(!r || !r.length)) { + this.graph && (this.graph._last_trigger_time = u.getTime()); + for (var o = 0; o < r.length; ++o) { + var a = r[o]; + if (!(i != null && i != a)) { + var l = this.graph.links[r[o]]; + if (l) { + l._last_time = u.getTime(); + var h = this.graph.getNodeById(l.target_id); + if (h) { + if (h.inputs[l.target_slot], n.link = l, n.originNode = this, h.mode === Z.ON_TRIGGER) + n.action_call || (n.action_call = this.id + "_trigg_" + Math.floor(Math.random() * 9999)), h.onExecute && h.doExecute(t, n); + else if (h.onAction) { + n.action_call || (n.action_call = this.id + "_act_" + Math.floor(Math.random() * 9999)); + const p = h.inputs[l.target_slot]; + h.actionDo(p.name, t, n); + } + } + } + } + } + } + } + } + } + /** + * clears the trigger slot animation + * @param slot the index of the output slot + * @param link_id in case you want to trigger and specific output link in a slot + */ + clearTriggeredSlot(e, t) { + if (this.outputs) { + var i = this.outputs[e]; + if (i) { + var n = i.links; + if (!(!n || !n.length)) + for (var s = 0; s < n.length; ++s) { + var r = n[s]; + if (!(t != null && t != r)) { + var o = this.graph.links[n[s]]; + o && (o._last_time = 0); + } + } + } + } + } + /** + * changes node size and triggers callback + * @method setSize + * @param {vec2} size + */ + setSize(e) { + this.size = e, this.onResize && this.onResize(this.size); + } + /** + * add a new property to this node + * @param name + * @param default_value + * @param type string defining the output type ("vec3","number",...) + * @param extra_info this can be used to have special properties of the property (like values, etc) + */ + addProperty(e, t, i, n) { + var s = { name: e, type: i, default_value: t }; + if (n) + for (var r in n) + s[r] = n[r]; + return this.properties_info || (this.properties_info = []), this.properties_info.push(s), this.properties || (this.properties = {}), this.properties[e] = t, s; + } + /** + * add a new output slot to use in this node + * @param name + * @param type string defining the output type ("vec3","number",...) + * @param extra_info this can be used to have special properties of an output (label, special color, position, etc) + */ + addOutput(e, t = I.DEFAULT, i) { + var n = { name: e, type: t, links: [], properties: {} }; + if (i) + for (var s in i) + n[s] = i[s]; + return (n.shape == null || n.shape == k.DEFAULT) && (t == "array" ? n.shape = k.GRID_SHAPE : (t === I.EVENT || t === I.ACTION) && (n.shape = k.BOX_SHAPE)), (t === I.EVENT || t === I.ACTION) && (n.shape = k.BOX_SHAPE), this.outputs || (this.outputs = []), this.outputs.push(n), this.onOutputAdded && this.onOutputAdded(n), u.auto_load_slot_types && u.registerNodeAndSlotType(this, t, !0), this.setSize(this.computeSize()), this.setDirtyCanvas(!0, !0), n; + } + /** remove an existing output slot */ + removeOutput(e) { + const t = this.outputs[e]; + this.disconnectOutput(e), this.outputs.splice(e, 1); + for (var i = e; i < this.outputs.length; ++i) + if (!(!this.outputs[i] || !this.outputs[i].links)) + for (var n = this.outputs[i].links, s = 0; s < n.length; ++s) { + var r = this.graph.links[n[s]]; + r && (r.origin_slot -= 1); + } + this.setSize(this.computeSize()), this.onOutputRemoved && this.onOutputRemoved(e, t), this.setDirtyCanvas(!0, !0); + } + moveOutput(e, t) { + const i = this.outputs[e]; + if (i == null || t < 0 || t > this.outputs.length - 1) + return; + const n = this.outputs[t]; + if (i.links) + for (const s of i.links) { + const r = this.graph.links[s]; + r.origin_slot = t; + } + if (n.links) + for (const s of n.links) { + const r = this.graph.links[s]; + r.origin_slot = e; + } + this.outputs[t] = i, this.outputs[e] = n; + } + /** + * add a new input slot to use in this node + * @param name + * @param type string defining the input type ("vec3","number",...), it its a generic one use 0 + * @param extra_info this can be used to have special properties of an input (label, color, position, etc) + */ + addInput(e, t = I.DEFAULT, i) { + var n = { name: e, type: t, link: null, properties: {} }; + if (i) + for (var s in i) + n[s] = i[s]; + return (n.shape == null || n.shape == k.DEFAULT) && (t == "array" ? n.shape = k.GRID_SHAPE : (t === I.EVENT || t === I.ACTION) && (n.shape = k.BOX_SHAPE)), this.inputs || (this.inputs = []), this.inputs.push(n), this.setSize(this.computeSize()), this.onInputAdded && this.onInputAdded(n), u.registerNodeAndSlotType(this, t), this.setDirtyCanvas(!0, !0), n; + } + /** remove an existing input slot */ + removeInput(e) { + this.disconnectInput(e); + for (var t = this.inputs.splice(e, 1), i = e; i < this.inputs.length; ++i) + if (this.inputs[i]) { + var n = this.graph.links[this.inputs[i].link]; + n && (n.target_slot -= 1); + } + this.setSize(this.computeSize()), this.onInputRemoved && this.onInputRemoved(e, t[0]), this.setDirtyCanvas(!0, !0); + } + moveInput(e, t) { + const i = this.inputs[e]; + if (i == null || t < 0 || t > this.inputs.length - 1) + return; + const n = this.inputs[t]; + if (i.link != null) { + const s = this.graph.links[i.link]; + s.target_slot = t; + } + if (n.link != null) { + const s = this.graph.links[n.link]; + s.target_slot = e; + } + this.inputs[t] = i, this.inputs[e] = n; + } + /** + * add an special connection to this node (used for special kinds of graphs) + * @param name + * @param type string defining the input type ("vec3","number",...) + * @param pos position of the connection inside the node + * @param direction if is input or output + */ + addConnection(e, t, i, n) { + let s = { + name: e, + type: t, + pos: i, + direction: n, + links: null + }; + return this.connections.push(s), s; + } + /** computes the size of a node according to its inputs and output slots */ + computeSize(e = [0, 0]) { + const t = ye(this, "overrideSize"); + if (t) + return t.concat(); + var i = Math.max( + this.inputs ? this.inputs.length : 1, + this.outputs ? this.outputs.length : 1 + ), n = e; + i = Math.max(i, 1); + var s = u.NODE_TEXT_SIZE, r = d(this.title), o = 0, a = 0; + if (this.inputs) + for (var l = 0, h = this.inputs.length; l < h; ++l) { + var p = this.inputs[l], f = p.label || p.name || "", c = d(f); + o < c && (o = c); + } + if (this.outputs) + for (var l = 0, h = this.outputs.length; l < h; ++l) { + var v = this.outputs[l], f = v.label || v.name || "", c = d(f); + a < c && (a = c); + } + if (n[0] = Math.max(o + a + 10, r), n[0] = Math.max(n[0], u.NODE_WIDTH), this.widgets && this.widgets.length) + for (const _ of this.widgets) + n[0] = Math.max(n[0], _.width || u.NODE_WIDTH * 1.5); + n[1] = (this.constructor.slot_start_y || 0) + i * u.NODE_SLOT_HEIGHT; + var g = 0; + if (this.widgets && this.widgets.length) { + for (var l = 0, h = this.widgets.length; l < h; ++l) { + const b = this.widgets[l]; + b.hidden || (b.computeSize ? g += b.computeSize(n[0])[1] + 4 : g += u.NODE_WIDGET_HEIGHT + 4); + } + g += 8; + } + this.widgets_up ? n[1] = Math.max(n[1], g) : this.widgets_start_y != null ? n[1] = Math.max(n[1], g + this.widgets_start_y) : n[1] += g; + function d(_) { + return _ ? s * _.length * 0.6 : 0; + } + return this.constructor.min_height && n[1] < this.constructor.min_height && (n[1] = this.constructor.min_height), n[1] += 6, n; + } + /** + * returns all the info available about a property of this node. + * + * @method getPropertyInfo + * @param {String} property name of the property + * @return {Object} the object with all the available info + */ + getPropertyInfo(e) { + var t = null; + if (this.properties_info) { + for (var i = 0; i < this.properties_info.length; ++i) + if (this.properties_info[i].name == e) { + t = this.properties_info[i]; + break; + } + } + return this.constructor["@" + e] && (t = this.constructor["@" + e]), this.constructor.widgets_info && this.constructor.widgets_info[e] && (t = this.constructor.widgets_info[e]), !t && this.onGetPropertyInfo && (t = this.onGetPropertyInfo(e)), t || (t = {}), t.type || (t.type = typeof this.properties[e]), t.widget == "combo" && (t.type = "enum"), t; + } + /** + * https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#node-widgets + * @return created widget + */ + addWidget(e, t, i, n, s) { + this.widgets || (this.widgets = []), !s && n && n.constructor === Object && (s = n, n = null), s && s.constructor === String && (s = { property: s }), n && n.constructor === String && (s || (s = {}), s.property = n, n = null), n && n.constructor !== Function && (console.warn("addWidget: callback must be a function"), n = null); + var r = { + type: e.toLowerCase(), + name: t, + value: i, + callback: n, + options: s || {} + }; + if (r.options.y !== void 0 && (r.y = r.options.y), !n && !r.options.callback && !r.options.property && console.warn("LiteGraph addWidget(...) without a callback or property assigned"), e == "combo" && !r.options.values) + throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; + return this.widgets.push(r), this.setSize(this.computeSize()), r; + } + addCustomWidget(e) { + return this.widgets || (this.widgets = []), this.widgets.push(e), this.setSize(this.computeSize()), e; + } + setWidgetHidden(e, t) { + e.hidden = t, this.setSize(this.computeSize()); + } + /** + * returns the bounding of the object, used for rendering purposes + * @return [x, y, width, height] + */ + getBounding(e) { + return e = e || new Float32Array(4), e[0] = this.pos[0] - 4, e[1] = this.pos[1] - u.NODE_TITLE_HEIGHT, e[2] = this.size[0] + 4, e[3] = this.flags.collapsed ? u.NODE_TITLE_HEIGHT : this.size[1] + u.NODE_TITLE_HEIGHT, this.onBounding && this.onBounding(e), e; + } + /** checks if a point is inside the shape of a node */ + isPointInside(e, t, i = 0, n = !1) { + var s = this.graph && this.graph.isLive() ? 0 : u.NODE_TITLE_HEIGHT; + if (n && (s = 0), this.flags && this.flags.collapsed) { + if (u.isInsideRectangle( + e, + t, + this.pos[0] - i, + this.pos[1] - u.NODE_TITLE_HEIGHT - i, + (this._collapsed_width || u.NODE_COLLAPSED_WIDTH) + 2 * i, + u.NODE_TITLE_HEIGHT + 2 * i + )) + return !0; + } else if (this.pos[0] - 4 - i < e && this.pos[0] + this.size[0] + 4 + i > e && this.pos[1] - s - i < t && this.pos[1] + this.size[1] + i > t) + return !0; + return !1; + } + /** checks if a point is inside a node slot, and returns info about which slot */ + getSlotInPosition(e, t) { + var i = [0, 0]; + if (this.inputs) + for (var n = 0, s = this.inputs.length; n < s; ++n) { + var r = this.inputs[n]; + if (this.getConnectionPos(!0, n, i), u.isInsideRectangle( + e, + t, + i[0] - 10, + i[1] - 5, + 20, + 10 + )) + return { input: r, slot: n, link_pos: i }; + } + if (this.outputs) + for (var n = 0, s = this.outputs.length; n < s; ++n) { + var o = this.outputs[n]; + if (this.getConnectionPos(!1, n, i), u.isInsideRectangle( + e, + t, + i[0] - 10, + i[1] - 5, + 20, + 10 + )) + return { output: o, slot: n, link_pos: i }; + } + return null; + } + is(e) { + const t = e.__LITEGRAPH_TYPE__; + return t != null && this.type === t; + } + /** + * returns the input slot with a given name (used for dynamic slots), -1 if not found + * @param name the name of the slot + * @return the slot (-1 if not found) + */ + findInputSlotIndexByName(e, t = !1, i) { + if (!this.inputs) + return -1; + for (var n = 0, s = this.inputs.length; n < s; ++n) + if (!(t && this.inputs[n].link && this.inputs[n].link != null) && !(i && i.includes(this.inputs[n].type)) && (!e || e == this.inputs[n].name)) + return n; + return -1; + } + findInputSlotByName(e, t = !1, i) { + if (!this.inputs) + return null; + for (var n = 0, s = this.inputs.length; n < s; ++n) + if (!(t && this.inputs[n].link && this.inputs[n].link != null) && !(i && i.includes(this.inputs[n].type)) && (!e || e == this.inputs[n].name)) + return this.inputs[n]; + return null; + } + /** + * returns the output slot with a given name (used for dynamic slots), -1 if not found + * @param name the name of the slot + * @return the slot (-1 if not found) + */ + findOutputSlotIndexByName(e, t = !1, i) { + if (!this.outputs) + return -1; + for (var n = 0, s = this.outputs.length; n < s; ++n) + if (!(t && this.outputs[n].links && this.outputs[n].links != null) && !(i && i.includes(this.outputs[n].type)) && (!e || e == this.outputs[n].name)) + return n; + return -1; + } + findOutputSlotByName(e, t = !1, i) { + if (!this.outputs) + return null; + for (var n = 0, s = this.outputs.length; n < s; ++n) + if (!(t && this.outputs[n].links && this.outputs[n].links != null) && !(i && i.includes(this.outputs[n].type)) && (!e || e == this.outputs[n].name)) + return this.outputs[n]; + return null; + } + /** + * findSlotByType for INPUTS + */ + findInputSlotIndexByType(e, t = !1, i = !1) { + return this.findSlotByType(!0, e, !1, t, i); + } + /** + * findSlotByType for OUTPUTS + */ + findOutputSlotIndexByType(e, t = !1, i = !1) { + return this.findSlotByType(!1, e, !1, t, i); + } + /** + * findSlotByType for INPUTS + */ + findInputSlotByType(e, t = !1, i = !1) { + return this.findSlotByType(!0, e, !1, t, i); + } + /** + * findSlotByType for OUTPUTS + */ + findOutputSlotByType(e, t = !1, i = !1) { + return this.findSlotByType(!1, e, !1, t, i); + } + /** + * returns the output (or input) slot with a given type, -1 if not found + * @method findSlotByType + * @param {boolean} input uise inputs instead of outputs + * @param {string} type the type of the slot + * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway) + * @return {number_or_object} the slot (-1 if not found) + */ + findSlotByType(e, t, i, n = !1, s = !1) { + n = n || !1, s = s || !1; + var r = e ? this.inputs : this.outputs; + if (!r) + return i ? null : -1; + (t == "" || t == "*") && (t = 0); + for (var o = 0, a = r.length; o < a; ++o) { + var l = (t + "").toLowerCase().split(","), h = r[o].type == "0" || r[o].type == "*" ? "0" : r[o].type; + let p = (h + "").toLowerCase().split(","); + for (let f = 0; f < l.length; f++) + for (let c = 0; c < p.length; c++) + if (l[f] == "_event_" && (l[f] = I.EVENT), p[f] == "_event_" && (p[f] = I.EVENT), l[f] == "*" && (l[f] = I.DEFAULT), p[f] == "*" && (p[f] = I.DEFAULT), l[f] == p[c]) { + let v = r[o]; + if (n && v.links && v.links !== null || v.link && v.link !== null) + continue; + return i ? v : o; + } + } + if (n && !s) + for (var o = 0, a = r.length; o < a; ++o) { + var l = (t + "").toLowerCase().split(","), h = r[o].type == "0" || r[o].type == "*" ? "0" : r[o].type; + let g = (h + "").toLowerCase().split(","); + for (let d = 0; d < l.length; d++) + for (let _ = 0; _ < g.length; _++) + if (l[d] == "*" && (l[d] = I.DEFAULT), g[d] == "*" && (g[d] = I.DEFAULT), l[d] == g[_]) + return i ? r[o] : o; + } + return i ? null : -1; + } + /** + * connect this node output to the input of another node BY TYPE + * @method connectByType + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {LGraphNode} node the target node + * @param {string} target_type the input slot type of the target node + * @return {Object} the link_info is created, otherwise null + */ + connectByTypeInput(e, t, i, n = {}) { + var s = { + createEventInCase: !0, + firstFreeIfOutputGeneralInCase: !0, + generalTypeInCase: !0 + }, r = Object.assign(s, n); + t && t.constructor === Number && (t = this.graph.getNodeById(t)); + let o = i; + i === I.EVENT ? o = I.ACTION : i === I.ACTION && (o = I.EVENT); + let a = t.findInputSlotIndexByType(o, !0); + if (a >= 0 && a !== null) + return u.debug && console.debug("CONNbyTYPE type " + i + " for " + a), this.connect(e, t, a); + if (u.debug && console.log("type " + i + " not found or not free?"), r.createEventInCase && i == I.EVENT) + return u.debug && console.debug("connect WILL CREATE THE onTrigger " + i + " to " + t), this.connect(e, t, -1); + if (r.generalTypeInCase) { + let l = t.findInputSlotIndexByType(I.DEFAULT, !0, !0); + if (u.debug && console.debug("connect TO a general type (*, 0), if not found the specific type ", i, " to ", t, "RES_SLOT:", l), l >= 0) + return this.connect(e, t, l); + } + if (r.firstFreeIfOutputGeneralInCase && (i == 0 || i == "*" || i == "")) { + let l = t.findInputSlotIndexByName(null, !0, [I.EVENT]); + if (u.debug && console.debug("connect TO TheFirstFREE ", i, " to ", t, "RES_SLOT:", l), l >= 0) + return this.connect(e, t, l); + } + return u.debug && console.error("no way to connect type: ", i, " to targetNODE ", t), null; + } + /** + * connect this node input to the output of another node BY TYPE + * @method connectByType + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {LGraphNode} node the target node + * @param {string} target_type the output slot type of the target node + * @return {Object} the link_info is created, otherwise null + */ + connectByTypeOutput(e, t, i, n = {}) { + var s = { + createEventInCase: !0, + firstFreeIfInputGeneralInCase: !0, + generalTypeInCase: !0 + }, r = Object.assign(s, n); + t && t.constructor === Number && (t = this.graph.getNodeById(t)); + let o = i; + if (i === I.EVENT ? o = I.ACTION : i === I.ACTION && (o = I.EVENT), a = t.findOutputSlotIndexByType(o, !0), a >= 0 && a !== null) + return console.debug("CONNbyTYPE OUT! type " + i + " for " + a + " to " + o), t.connect(a, this, e); + if (r.generalTypeInCase) { + var a = t.findOutputSlotIndexByType(0, !0, !0); + if (a >= 0) + return t.connect(a, this, e); + } + if ((r.createEventInCase && i == I.EVENT || i == I.ACTION) && u.do_add_triggers_slots) { + var a = t.addOnExecutedOutput(); + return t.connect(a, this, e); + } + if (r.firstFreeIfInputGeneralInCase && (i == 0 || i == "*" || i == "")) { + let l = t.findOutputSlotIndexByName(null, !0, [I.EVENT, I.ACTION]); + if (l >= 0) + return t.connect(l, this, e); + } + return console.error("no way to connect byOUT type: ", i, " to sourceNODE ", t), console.error("type OUT! " + i + " not found or not free?"), null; + } + /** + * connect this node output to the input of another node + * @param slot (could be the number of the slot or the string with the name of the slot) + * @param targetNode the target node + * @param targetSlot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) + * @return {Object} the linkInfo is created, otherwise null + */ + connect(e, t, i) { + if (i = i || 0, !this.graph) + throw new Error("Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them."); + if (typeof e == "string") { + if (e = this.findOutputSlotIndexByName(e), e == -1) + return u.debug && console.error("Connect: Error, no slot of name " + e), null; + } else if (!this.outputs || e >= this.outputs.length) + return u.debug && console.error("Connect: Error, slot number not found"), null; + if (t && t.constructor === Number && (t = this.graph.getNodeById(t)), !t) + throw "target node is null"; + if (t == this) + return u.debug && console.error("Connect: Error, can't connect node to itself!"), null; + if (!t.graph) + throw new Error("Connect: Error, target node doesn't belong to any graph. Nodes must be added first to a graph before connecting them."); + if (typeof i == "string") { + if (i = t.findInputSlotIndexByName(i), i == -1) + return u.debug && console.error( + "Connect: Error, no slot of name " + i + ), null; + } else if (i === I.EVENT) + if (u.do_add_triggers_slots) + t.changeMode(Z.ON_TRIGGER), i = t.findInputSlotIndexByName("onTrigger"); + else + return u.debug && console.error("Connect: Error, can't connect event target slot"), null; + else if (!t.inputs || i >= t.inputs.length) + return u.debug && console.error("Connect: Error, slot number not found"), null; + var n = !1, s = t.inputs[i], r = null, o = this.outputs[e]; + if (!this.outputs[e]) + return u.debug && (console.warn("Connect: Invalid slot passed: " + e), console.warn(this.outputs)), null; + if (t.onBeforeConnectInput && (i = t.onBeforeConnectInput(i)), i === -1 || i === null || !u.isValidConnection(o.type, s.type)) + return this.setDirtyCanvas(!1, !0), n && this.graph.connectionChange(this, r), console.warn("Connect: Invalid connection: ", i, o.type, s.type), null; + if (u.debug && console.debug("valid connection", o.type, s.type), t.onConnectInput && t.onConnectInput(i, o.type, o, this, e) === !1) + return u.debug && console.debug("onConnectInput blocked", o.type, s.type), null; + if (this.onConnectOutput && this.onConnectOutput(e, s.type, s, t, i) === !1) + return u.debug && console.debug("onConnectOutput blocked", o.type, s.type), null; + if (t.inputs[i] && t.inputs[i].link != null && (this.graph.beforeChange(), t.disconnectInput(i, { doProcessChange: !1 }), n = !0), o.links !== null && o.links.length) + switch (o.type) { + case I.EVENT: + u.allow_multi_output_for_events || (this.graph.beforeChange(), this.disconnectOutput(e, null, { doProcessChange: !1 }), n = !0); + break; + } + let a; + return u.use_uuids ? a = oe() : a = ++this.graph.last_link_id, r = new he( + a, + s.type || o.type, + this.id, + e, + t.id, + i + ), this.graph.links[r.id] && console.error("Link already exists in graph!", r.id, r, this.graph.links[r.id]), this.graph.links[r.id] = r, o.links == null && (o.links = []), o.links.push(r.id), t.inputs[i].link = r.id, this.graph && this.graph._version++, this.onConnectionsChange && this.onConnectionsChange( + Y.OUTPUT, + e, + !0, + r, + o + ), t.onConnectionsChange && t.onConnectionsChange( + Y.INPUT, + i, + !0, + r, + s + ), this.graph && this.graph.onNodeConnectionChange && (this.graph.onNodeConnectionChange( + Y.INPUT, + t, + i, + this, + e + ), this.graph.onNodeConnectionChange( + Y.OUTPUT, + this, + e, + t, + i + )), this.setDirtyCanvas(!1, !0), this.graph.afterChange(), this.graph.connectionChange(this, r), r; + } + /** + * disconnect one output to an specific node + * @param slot (could be the number of the slot or the string with the name of the slot) + * @param targetNode the target node to which this slot is connected [Optional, if not targetNode is specified all nodes will be disconnected] + * @return if it was disconnected successfully + */ + disconnectOutput(e, t, i) { + if (typeof e == "string") { + if (e = this.findOutputSlotIndexByName(e), e == -1) + return u.debug && console.error("Connect: Error, no slot of name " + e), !1; + } else if (!this.outputs || e >= this.outputs.length) + return u.debug && console.error("Connect: Error, slot number not found"), !1; + var n = this.outputs[e]; + if (!n || !n.links || n.links.length == 0) + return !1; + if (t) { + if (t.constructor === Number && (t = this.graph.getNodeById(t)), !t) + throw "Target Node not found"; + for (var s = 0, r = n.links.length; s < r; s++) { + var o = n.links[s], a = this.graph.links[o]; + if (a.target_id == t.id) { + n.links.splice(s, 1); + var l = t.inputs[a.target_slot]; + l.link = null, delete this.graph.links[o], this.graph && this.graph._version++, t.onConnectionsChange && t.onConnectionsChange( + Y.INPUT, + a.target_slot, + !1, + a, + l + ), this.onConnectionsChange && this.onConnectionsChange( + Y.OUTPUT, + e, + !1, + a, + n + ), this.graph && this.graph.onNodeConnectionChange && this.graph.onNodeConnectionChange( + Y.OUTPUT, + this, + e + ), this.graph && this.graph.onNodeConnectionChange && (this.graph.onNodeConnectionChange( + Y.OUTPUT, + this, + e + ), this.graph.onNodeConnectionChange( + Y.INPUT, + t, + a.target_slot + )); + break; + } + } + } else { + for (var s = 0, r = n.links.length; s < r; s++) { + var o = n.links[s], a = this.graph.links[o]; + if (a) { + var t = this.graph.getNodeById(a.target_id), l = null; + this.graph && this.graph._version++, t && (l = t.inputs[a.target_slot], l.link = null, t.onConnectionsChange && t.onConnectionsChange( + Y.INPUT, + a.target_slot, + !1, + a, + l + ), this.graph && this.graph.onNodeConnectionChange && this.graph.onNodeConnectionChange( + Y.INPUT, + t, + a.target_slot + )), delete this.graph.links[o], this.onConnectionsChange && this.onConnectionsChange( + Y.OUTPUT, + e, + !1, + a, + n + ), this.graph && this.graph.onNodeConnectionChange && (this.graph.onNodeConnectionChange( + Y.OUTPUT, + this, + e + ), this.graph.onNodeConnectionChange( + Y.INPUT, + t, + a.target_slot + )); + } + } + n.links = null; + } + return this.setDirtyCanvas(!1, !0), this.graph.connectionChange(this), !0; + } + /** + * disconnect one input + * @param slot (could be the number of the slot or the string with the name of the slot) + * @return if it was disconnected successfully + */ + disconnectInput(e, t = {}) { + if (typeof e == "string") { + if (e = this.findInputSlotIndexByName(e), e == -1) + return u.debug && console.error("Connect: Error, no slot of name " + e), !1; + } else if (!this.inputs || e >= this.inputs.length) + return u.debug && console.error("Connect: Error, slot number not found"), !1; + var i = this.inputs[e]; + if (!i) + return !1; + var n = this.inputs[e].link; + if (n != null) { + this.inputs[e].link = null; + var s = this.graph.links[n]; + if (s) { + var r = this.graph.getNodeById(s.origin_id); + if (!r) + return !1; + var o = r.outputs[s.origin_slot]; + if (!o || !o.links || o.links.length == 0) + return !1; + for (var a = 0, l = o.links.length; a < l; a++) + if (o.links[a] == n) { + o.links.splice(a, 1); + break; + } + delete this.graph.links[n], this.graph && this.graph._version++, this.onConnectionsChange && this.onConnectionsChange( + Y.INPUT, + e, + !1, + s, + i + ), r.onConnectionsChange && r.onConnectionsChange( + Y.OUTPUT, + a, + !1, + s, + o + ), this.graph && this.graph.onNodeConnectionChange && (this.graph.onNodeConnectionChange( + Y.OUTPUT, + r, + a + ), this.graph.onNodeConnectionChange(Y.INPUT, this, e)); + } + } + return this.setDirtyCanvas(!1, !0), this.graph && this.graph.connectionChange(this), !0; + } + /** + * returns the center of a connection point in canvas coords + * @param is_input true if if a input slot, false if it is an output + * @param slot (could be the number of the slot or the string with the name of the slot) + * @param out a place to store the output, to free garbage + * @return the position + **/ + getConnectionPos(e, t, i = [0, 0], n = !1) { + var s = 0; + e && this.inputs && (s = this.inputs.length), !e && this.outputs && (s = this.outputs.length); + var r = u.NODE_SLOT_HEIGHT * 0.5; + if (this.flags.collapsed && !n) { + var o = this._collapsed_width || u.NODE_COLLAPSED_WIDTH; + return this.horizontal ? (i[0] = this.pos[0] + o * 0.5, e ? i[1] = this.pos[1] - u.NODE_TITLE_HEIGHT : i[1] = this.pos[1]) : (e ? i[0] = this.pos[0] : i[0] = this.pos[0] + o, i[1] = this.pos[1] - u.NODE_TITLE_HEIGHT * 0.5), i; + } + return e && t == -1 ? (i[0] = this.pos[0] + u.NODE_TITLE_HEIGHT * 0.5, i[1] = this.pos[1] + u.NODE_TITLE_HEIGHT * 0.5, i) : e && s > t && this.inputs[t].pos ? (i[0] = this.pos[0] + this.inputs[t].pos[0], i[1] = this.pos[1] + this.inputs[t].pos[1], i) : !e && s > t && this.outputs[t].pos ? (i[0] = this.pos[0] + this.outputs[t].pos[0], i[1] = this.pos[1] + this.outputs[t].pos[1], i) : this.horizontal ? (i[0] = this.pos[0] + (t + 0.5) * (this.size[0] / s), e ? i[1] = this.pos[1] - u.NODE_TITLE_HEIGHT : i[1] = this.pos[1] + this.size[1], i) : (e ? i[0] = this.pos[0] + r : i[0] = this.pos[0] + this.size[0] + 1 - r, i[1] = this.pos[1] + (t + 0.7) * u.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0), i); + } + /** Force align to grid */ + alignToGrid() { + this.pos[0] = u.CANVAS_GRID_SIZE * Math.round(this.pos[0] / u.CANVAS_GRID_SIZE), this.pos[1] = u.CANVAS_GRID_SIZE * Math.round(this.pos[1] / u.CANVAS_GRID_SIZE); + } + /** Console output */ + trace(e) { + this.console || (this.console = []), this.console.push(e), this.console.length > Ne.MAX_CONSOLE && this.console.shift(), this.graph.onNodeTrace && this.graph.onNodeTrace(this, e); + } + /** Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ + setDirtyCanvas(e, t = !1) { + this.graph && this.graph.sendActionToCanvas("setDirty", [e, t]); + } + loadImage(e) { + var t = new Image(); + t.src = u.node_images_path + e; + var i = this; + return t.onload = function() { + i.setDirtyCanvas(!0); + }, t; + } + /** Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ + captureInput(e) { + if (!(!this.graph || !this.graph.list_of_graphcanvas)) + for (var t = this.graph.list_of_graphcanvas, i = 0; i < t.length; ++i) { + var n = t[i]; + !e && n.node_capturing_input != this || (n.node_capturing_input = e ? this : null); + } + } + isShowingTitle(e) { + return this.titleMode == se.TRANSPARENT_TITLE || this.titleMode == se.NO_TITLE ? !1 : (this.titleMode == se.AUTOHIDE_TITLE && e, !0); + } + /** Collapse the node to make it smaller on the canvas */ + collapse(e = !1) { + this.graph._version++, !(this.collapsable === !1 && !e) && (this.flags.collapsed ? this.flags.collapsed = !1 : this.flags.collapsed = !0, this.setDirtyCanvas(!0, !0)); + } + /** Forces the node to do not move or realign on Z */ + pin(e) { + this.graph._version++, e === void 0 ? this.flags.pinned = !this.flags.pinned : this.flags.pinned = e; + } + localToScreen(e, t, i) { + return [ + (e + this.pos[0]) * i.ds.scale + i.ds.offset[0], + (t + this.pos[1]) * i.ds.scale + i.ds.offset[1] + ]; + } + getOptionalSlots() { + return ye(this, "optionalSlots"); + } +}; +let ae = Ne; +ae.MAX_CONSOLE = 100; +function we() { + let e = []; + return e = e.concat(Ae), e = e.concat([I.ACTION]), e = e.concat(u.slot_types_in.map((t) => t.toUpperCase())), e; +} +function Ye() { + return we().map(J); +} +class $ extends ae { + constructor(t) { + super(t), this.properties = { + name: "", + type: "number", + value: 0, + subgraphID: null + }, this.nameInGraph = "", this.clonable = !1, this.size = [180, 90]; + let i = this; + this.nameWidget = this.addWidget( + "text", + "Name", + this.properties.name, + this.setName.bind(this) + ), u.graph_inputs_outputs_use_combo_widget ? this.typeWidget = this.addWidget( + "combo", + "Type", + J(this.properties.type), + this.setType.bind(this), + { values: Ye } + ) : this.typeWidget = this.addWidget( + "text", + "Type", + J(this.properties.type), + this.setType.bind(this) + ), this.valueWidget = this.addWidget( + "number", + "Value", + this.properties.value, + function(n) { + i.setProperty("value", n); + } + ), this.widgets_up = !0; + } + setName(t) { + if (t == null || t === this.properties.name) + return; + const i = this.getParentSubgraph(); + i && (t = i.getValidGraphInputName(t), this.setProperty("name", t)); + } + setType(t) { + t || (t = "*"); + let i = t; + t === "-1" || t === "Action" ? i = I.ACTION : t === "-2" || t === "Event" ? i = I.EVENT : t === "0" && (i = "*"), this.setProperty("type", i); + } + onConfigure() { + this.updateType(); + } + getParentSubgraph() { + var t, i; + return (i = (t = this.graph._subgraph_node) == null ? void 0 : t.graph) == null ? void 0 : i.getNodeById(this.properties.subgraphID); + } + /** ensures the type in the node output and the type in the associated graph input are the same */ + updateType() { + var t = this.properties.type; + this.typeWidget.value = J(t); + const i = this.outputs[0]; + i.type != t && (u.isValidConnection(i.type, t) || this.disconnectOutput(0), i.type = t), t == "array" ? i.shape = k.GRID_SHAPE : t === I.EVENT || t === I.ACTION ? i.shape = k.BOX_SHAPE : i.shape = k.DEFAULT, t == "number" ? (this.valueWidget.type = "number", this.valueWidget.value = 0) : t == "boolean" ? (this.valueWidget.type = "toggle", this.valueWidget.value = !0) : t == "string" ? (this.valueWidget.type = "text", this.valueWidget.value = "") : (this.valueWidget.type = null, this.valueWidget.value = null), this.properties.value = this.valueWidget.value, this.graph && this.nameInGraph && Se(t) ? (this.graph.changeInputType(this.nameInGraph, t), i.type !== t && this.setOutputDataType(0, t)) : console.error("[GraphInput] Can't change output to type", t, this.graph, this.nameInGraph); + } + /** this is executed AFTER the property has changed */ + onPropertyChanged(t, i) { + if (t == "name") { + if (i == "" || i == this.nameInGraph || i == "enabled") + return !1; + this.graph && (this.nameInGraph ? this.graph.renameInput(this.nameInGraph, i) : this.graph.addInput(i, "" + this.properties.type, null)), this.nameWidget.value = i, this.nameInGraph = i; + } else + t == "type" && this.updateType(); + } + getTitle() { + return this.flags.collapsed ? this.properties.name : this.title; + } + onAction(t, i) { + this.properties.type == I.EVENT && this.triggerSlot(0, i); + } + onExecute() { + var t = this.properties.name, i = this.graph.inputs[t]; + if (!i) { + this.setOutputData(0, this.properties.value); + return; + } + this.setOutputData(0, i.value !== void 0 ? i.value : this.properties.value); + } + onRemoved() { + this.nameInGraph && this.graph.removeInput(this.nameInGraph); + } +} +$.slotLayout = { + inputs: [], + outputs: [ + { name: "", type: "number" } + ] +}; +u.registerNodeType({ + class: $, + title: "Input", + desc: "Input of the graph", + type: "graph/input", + hide_in_node_lists: !0 +}); +function Le() { + let e = []; + return e = e.concat(Ae), e = e.concat([I.EVENT]), e = e.concat(u.slot_types_out), e; +} +function We() { + return Le().map(J); +} +class Q extends ae { + constructor(t) { + super(t), this.properties = { + name: "", + type: "number", + subgraphID: null + }, this.nameInGraph = "", this.clonable = !1, this.size = [180, 60], this.nameWidget = this.addWidget( + "text", + "Name", + this.properties.name, + this.setName.bind(this) + ), u.graph_inputs_outputs_use_combo_widget ? this.typeWidget = this.addWidget( + "combo", + "Type", + J(this.properties.type), + this.setType.bind(this), + { values: We } + ) : this.typeWidget = this.addWidget( + "text", + "Type", + J(this.properties.type), + this.setType.bind(this) + ), this.widgets_up = !0; + } + setName(t) { + if (t == null || t === this.properties.name) + return; + const i = this.getParentSubgraph(); + i && (t = i.getValidGraphOutputName(t), this.setProperty("name", t)); + } + setType(t) { + t || (t = "*"); + let i = t; + t === "-1" || t === "Action" ? i = I.ACTION : t === "-2" || t === "Event" ? i = I.EVENT : t === "0" && (i = "*"), this.setProperty("type", i); + } + onConfigure() { + this.updateType(); + } + getParentSubgraph() { + var t, i; + return (i = (t = this.graph._subgraph_node) == null ? void 0 : t.graph) == null ? void 0 : i.getNodeById(this.properties.subgraphID); + } + updateType() { + var t = this.properties.type; + const i = this.inputs[0]; + this.typeWidget && (this.typeWidget.value = J(t)), t == "array" ? i.shape = k.GRID_SHAPE : t === I.EVENT || t === I.ACTION ? i.shape = k.BOX_SHAPE : i.shape = k.DEFAULT, i.type != t && ((t == "action" || t == "event") && (t = I.EVENT), u.isValidConnection(i.type, t) || this.disconnectInput(0), i.type = t), this.graph && this.nameInGraph && Se(t) ? (this.graph.changeOutputType(this.nameInGraph, t), i.type !== t && this.setInputDataType(0, t)) : console.error("Can't change GraphOutput to type", t, this.graph, this.nameInGraph); + } + /** this is executed AFTER the property has changed */ + onPropertyChanged(t, i) { + if (t == "name") { + if (i == "" || i == this.nameInGraph || i == "enabled") + return !1; + this.graph ? this.nameInGraph ? this.graph.renameOutput(this.nameInGraph, i) : this.graph.addOutput(i, "" + this.properties.type, null) : console.error("[GraphOutput] missing graph!", t, i), this.nameWidget.value = i, this.nameInGraph = i; + } else + t == "type" && this.updateType(); + } + getTitle() { + return this.flags.collapsed ? this.properties.name : this.title; + } + onAction(t, i, n) { + const s = this.getParentSubgraph(); + if (!s) + return; + const r = s.findOutputSlotIndexByName(this.properties.name); + r == null || s.outputs[r] == null || s.triggerSlot(r, i); + } + onExecute() { + const t = this.getInputData(0); + this.graph.setOutputData(this.properties.name, t); + } + onRemoved() { + this.nameInGraph && this.graph.removeOutput(this.nameInGraph); + } +} +Q.slotLayout = { + inputs: [ + { name: "", type: "" } + ], + outputs: [] +}; +u.registerNodeType({ + class: Q, + title: "Output", + desc: "Output of the graph", + type: "graph/output", + hide_in_node_lists: !0 +}); +var xe = /* @__PURE__ */ ((e) => (e[e.STATUS_STOPPED = 1] = "STATUS_STOPPED", e[e.STATUS_RUNNING = 2] = "STATUS_RUNNING", e))(xe || {}); +const De = class { + constructor(e) { + this.supported_types = null, this.vars = {}, this.extra = {}, this.inputs = {}, this.outputs = {}, this.links = {}, this.list_of_graphcanvas = [], this._nodes = [], this._groups = [], this._nodes_by_id = {}, this._nodes_executable = null, this._nodes_in_order = [], this._version = -1, this._last_trigger_time = 0, this._is_subgraph = !1, this._subgraph_node = null, this.nodes_executing = [], this.nodes_actioning = [], this.nodes_executedAction = [], this.execution_timer_id = -1, this.execution_time = 0, this.errors_in_execution = !1, u.debug && console.log("Graph created"), this.list_of_graphcanvas = null, this.clear(), e && this.configure(e); + } + getSupportedTypes() { + return this.supported_types || De.DEFAULT_SUPPORTED_TYPES; + } + /* + * Gets the root graph above any subgraphs. + */ + getRootGraph() { + const e = Array.from(this.iterateParentGraphs()), t = e[e.length - 1]; + return t._is_subgraph ? null : t; + } + *iterateParentGraphs() { + var t; + let e = this; + for (; e; ) + yield e, e = (t = e._subgraph_node) == null ? void 0 : t.graph; + } + /** Removes all nodes from this graph */ + clear() { + if (this.stop(), this.status = 1, this.last_node_id = 0, this.last_link_id = 0, this._version = -1, this._nodes) + for (var e = 0; e < this._nodes.length; ++e) { + var t = this._nodes[e]; + t.onRemoved && t.onRemoved(); + } + this._nodes = [], this._nodes_by_id = {}, this._nodes_in_order = [], this._nodes_executable = null, this._groups = [], this.links = {}, this.iteration = 0, this.config = {}, this.vars = {}, this.extra = {}, this.globaltime = 0, this.runningtime = 0, this.fixedtime = 0, this.fixedtime_lapse = 0.01, this.elapsed_time = 0.01, this.last_update_time = 0, this.starttime = 0, this.catch_errors = !0, this.nodes_executing = [], this.nodes_actioning = [], this.nodes_executedAction = [], this.inputs = {}, this.outputs = {}, this.change(), this.sendActionToCanvas("clear"); + } + /** Attach Canvas to this graph */ + attachCanvas(e) { + if (!(e instanceof N)) + throw "attachCanvas expects a LGraphCanvas instance"; + e.graph && e.graph != this && e.graph.detachCanvas(e), e.graph = this, this.list_of_graphcanvas || (this.list_of_graphcanvas = []), this.list_of_graphcanvas.push(e); + } + /** Detach Canvas to this graph */ + detachCanvas(e) { + if (this.list_of_graphcanvas) { + var t = this.list_of_graphcanvas.indexOf(e); + t != -1 && (e.graph = null, this.list_of_graphcanvas.splice(t, 1)); + } + } + /** + * Starts running this graph every interval milliseconds. + * @param interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate + */ + start(e) { + if (this.status != 2) { + this.status = 2, this.onPlayEvent && this.onPlayEvent(), this.sendEventToAllNodes("onStart"), this.starttime = u.getTime(), this.last_update_time = this.starttime, e = e || 0; + var t = this; + if (e == 0 && typeof window < "u" && window.requestAnimationFrame) { + let i = function() { + t.execution_timer_id == -1 && (window.requestAnimationFrame(i), t.onBeforeStep && t.onBeforeStep(), t.runStep(1, !t.catch_errors), t.onAfterStep && t.onAfterStep()); + }; + this.execution_timer_id = -1, i(); + } else + this.execution_timer_id = setInterval(function() { + t.onBeforeStep && t.onBeforeStep(), t.runStep(1, !t.catch_errors), t.onAfterStep && t.onAfterStep(); + }, e); + } + } + /** Stops the execution loop of the graph */ + stop() { + this.status != 1 && (this.status = 1, this.onStopEvent && this.onStopEvent(), this.execution_timer_id != null && (this.execution_timer_id != -1 && clearInterval(this.execution_timer_id), this.execution_timer_id = null), this.sendEventToAllNodes("onStop")); + } + /** + * Run N steps (cycles) of the graph + * @param num number of steps to run, default is 1 + * @param do_not_catch_errors if you want to try/catch errors + */ + runStep(e = 1, t = !1, i) { + var n = u.getTime(); + this.globaltime = 1e-3 * (n - this.starttime); + let s = this._nodes_executable ? this._nodes_executable : this._nodes; + if (s) { + if (i = i || s.length, t) { + for (var r = 0; r < e; r++) { + for (var o = 0; o < i; ++o) { + var a = s[o]; + a.mode == Z.ALWAYS && a.onExecute && a.doExecute(); + } + this.fixedtime += this.fixedtime_lapse, this.onExecuteStep && this.onExecuteStep(); + } + this.onAfterExecute && this.onAfterExecute(); + } else + try { + for (var r = 0; r < e; r++) { + for (var o = 0; o < i; ++o) { + var a = s[o]; + a.mode == Z.ALWAYS && a.onExecute && a.onExecute(null, {}); + } + this.fixedtime += this.fixedtime_lapse, this.onExecuteStep && this.onExecuteStep(); + } + this.onAfterExecute && this.onAfterExecute(), this.errors_in_execution = !1; + } catch (p) { + if (this.errors_in_execution = !0, u.throw_errors) + throw p; + u.debug && console.log("Error during execution: " + p), this.stop(); + } + var l = u.getTime(), h = l - n; + h == 0 && (h = 1), this.execution_time = 1e-3 * h, this.globaltime += 1e-3 * h, this.iteration += 1, this.elapsed_time = (l - this.last_update_time) * 1e-3, this.last_update_time = l, this.nodes_executing = [], this.nodes_actioning = [], this.nodes_executedAction = []; + } + } + /** + * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than + * nodes with only inputs. + */ + updateExecutionOrder() { + this._nodes_in_order = this.computeExecutionOrder(!1), this._nodes_executable = []; + for (var e = 0; e < this._nodes_in_order.length; ++e) + if (this._nodes_in_order[e].onExecute) { + let t = this._nodes_in_order[e]; + this._nodes_executable.push(t); + } + } + *computeExecutionOrderRecursive(e = !1, t) { + for (const i of this.computeExecutionOrder(e, t)) + if (yield i, i.is(ne)) + for (const n of i.subgraph.computeExecutionOrderRecursive(e, t)) + yield n; + } + /** This is more internal, it computes the executable nodes in order and returns it */ + computeExecutionOrder(e = !1, t) { + for (var i = [], n = [], s = {}, r = {}, o = {}, a = 0, _ = this._nodes.length; a < _; ++a) { + var l = this._nodes[a]; + if (!(e && !l.onExecute)) { + s[l.id] = l; + var h = 0; + if (l.inputs) + for (var p = 0, f = l.inputs.length; p < f; p++) + l.inputs[p] && l.inputs[p].link != null && (h += 1); + h == 0 ? (n.push(l), t && (l._level = 1)) : (t && (l._level = 0), o[l.id] = h); + } + } + for (; n.length != 0; ) { + let y = n.shift(); + if (i.push(y), delete s[y.id], !!y.outputs) + for (var a = 0; a < y.outputs.length; a++) { + var c = y.outputs[a]; + if (!(c == null || c.links == null || c.links.length == 0)) + for (var p = 0; p < c.links.length; p++) { + var v = c.links[p], g = this.links[v]; + if (g && !r[g.id]) { + var d = this.getNodeById(g.target_id); + if (d == null) { + r[g.id] = !0; + continue; + } + t && (!d._level || d._level <= y._level) && (d._level = y._level + 1), r[g.id] = !0, o[d.id] -= 1, o[d.id] == 0 && n.push(d); + } + } + } + } + for (let y of Object.keys(s).sort()) + i.push(s[y]); + i.length != this._nodes.length && u.debug && console.warn("something went wrong, nodes missing"); + for (var _ = i.length, a = 0; a < _; ++a) + i[a].order = a; + i = i.sort(function(y, b) { + var m = y.constructor.priority || y.priority || 0, E = b.constructor.priority || b.priority || 0; + return m == E ? y.order - b.order : m - E; + }); + for (var a = 0; a < _; ++a) + i[a].order = a; + return i; + } + /** + * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. + * It doesn't include the node itself + * @return an array with all the LGraphNodes that affect this node, in order of execution + */ + getAncestors(e) { + for (var t = [], i = [e], n = {}; i.length; ) { + var s = i.shift(); + if (s.inputs) { + !n[s.id] && s != e && (n[s.id] = !0, t.push(s)); + for (var r = 0; r < s.inputs.length; ++r) { + var o = s.getInputNode(r); + o && t.indexOf(o) == -1 && i.push(o); + } + } + } + return t.sort(function(a, l) { + return a.order - l.order; + }), t; + } + /** + * Positions every node in a more readable manner + */ + arrange(e = 100, t = ue.HORIZONTAL_LAYOUT) { + const i = this.computeExecutionOrder(!1, !0), n = []; + for (let r = 0; r < i.length; ++r) { + const o = i[r], a = o._level || 1; + n[a] || (n[a] = []), n[a].push(o); + } + let s = e; + for (let r = 0; r < n.length; ++r) { + const o = n[r]; + if (!o) + continue; + let a = 100, l = e + u.NODE_TITLE_HEIGHT; + for (let h = 0; h < o.length; ++h) { + const p = o[h]; + p.pos[0] = t == ue.VERTICAL_LAYOUT ? l : s, p.pos[1] = t == ue.VERTICAL_LAYOUT ? s : l; + const f = t == ue.VERTICAL_LAYOUT ? 1 : 0; + p.size[f] > a && (a = p.size[f]); + const c = t == ue.VERTICAL_LAYOUT ? 0 : 1; + l += p.size[c] + e + u.NODE_TITLE_HEIGHT; + } + s += a + e; + } + this.setDirtyCanvas(!0, !0); + } + /** + * Returns the amount of time the graph has been running in milliseconds + * @return number of milliseconds the graph has been running + */ + getTime() { + return this.globaltime; + } + /** + * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant + * @return number of milliseconds the graph has been running + */ + getFixedTime() { + return this.fixedtime; + } + /** + * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct + * if the nodes are using graphical actions + * @return number of milliseconds it took the last cycle + */ + getElapsedTime() { + return this.elapsed_time; + } + /** + * Iterates all nodes in this graph *excluding* subgraphs. + */ + *iterateNodesInOrder() { + const e = this._nodes_in_order ? this._nodes_in_order : this._nodes || []; + for (const t of e) + yield t; + } + /** + * Iterates all nodes in this graph and subgraphs. + */ + *iterateNodesInOrderRecursive() { + const e = this._nodes_in_order ? this._nodes_in_order : this._nodes || []; + for (const t of e) + if (yield t, t.subgraph != null) + for (const i of t.subgraph.iterateNodesInOrderRecursive()) + yield i; + } + /** + * Iterates all nodes in this graph *excluding* subgraphs. + */ + *iterateNodesOfClass(e) { + const t = e.__LITEGRAPH_TYPE__; + if (t != null) + for (const i of this.iterateNodesInOrder()) + i.type === t && (yield i); + } + /** + * Iterates all nodes in this graph *excluding* subgraphs. + */ + *iterateNodesOfClassRecursive(e) { + const t = e.__LITEGRAPH_TYPE__; + if (t != null) + for (const i of this.iterateNodesInOrderRecursive()) + i.type === t && (yield i); + } + /** + * Iterates all nodes in this graph *excluding* subgraphs. + */ + *iterateNodesOfTypeRecursive(e) { + for (const t of this.iterateNodesInOrderRecursive()) + t.type === e && (yield t); + } + /** + * Sends an event to all the nodes, useful to trigger stuff + * @param eventName the name of the event (function to be called) + * @param params parameters in array format + */ + sendEventToAllNodes(e, t = [], i = Z.ALWAYS) { + var n = this._nodes_in_order ? this._nodes_in_order : this._nodes; + if (n) + for (const s of this.iterateNodesInOrder()) { + if (s.type === "basic/subgraph" && e != "onExecute") { + s.mode == i && s.sendEventToAllNodes(e, t, i); + continue; + } + !s[e] || s.mode != i || (t === void 0 ? s[e]() : t && t.constructor === Array ? s[e].apply(s, t) : s[e](t)); + } + } + sendActionToCanvas(e, t = []) { + if (this.list_of_graphcanvas) + for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { + var n = this.list_of_graphcanvas[i]; + n[e] && n[e].apply(n, t); + } + } + addGroup(e) { + return this._groups.push(e), this.setDirtyCanvas(!0), this.change(), e.graph = this, this._version++, e; + } + /** + * Adds a new node instance to this graph + * @param node the instance of the node + */ + add(e, t = {}) { + if (e.id != -1 && this._nodes_by_id[e.id] != null && (console.warn( + "LiteGraph: there is already a node with this ID, changing it", + e.id + ), u.use_uuids ? e.id = oe() : e.id = ++this.last_node_id), t.pos && (isNaN(t.pos[0]) || isNaN(t.pos[1]))) + throw "LiteGraph: Node position contained NaN(s)!"; + if (this._nodes.length >= u.MAX_NUMBER_OF_NODES) + throw "LiteGraph: max number of nodes in a graph reached"; + return u.use_uuids ? e.id || (e.id = oe()) : e.id == null || e.id == -1 ? e.id = ++this.last_node_id : this.last_node_id < e.id && (this.last_node_id = e.id), e.graph = this, this._version++, this._nodes.push(e), this._nodes_by_id[e.id] = e, t.pos && (e.pos = t.pos), e.onAdded && e.onAdded(this), this.config.align_to_grid && e.alignToGrid(), t.skipComputeOrder || this.updateExecutionOrder(), this.onNodeAdded && this.onNodeAdded(e, t), this.setDirtyCanvas(!0), this.change(), e; + } + /** Removes a node from the graph */ + remove(e, t = {}) { + if (e instanceof me) { + var i = this._groups.indexOf(e); + i != -1 && this._groups.splice(i, 1), e.graph = null, this._version++, this.setDirtyCanvas(!0, !0), this.change(); + return; + } + if (this._nodes_by_id[e.id] != null && !e.ignore_remove) { + if (this.beforeChange(), e.inputs) + for (var n = 0; n < e.inputs.length; n++) { + var s = e.inputs[n]; + s.link != null && e.disconnectInput(n); + } + if (e.outputs) + for (var n = 0; n < e.outputs.length; n++) { + let l = e.outputs[n]; + l.links != null && l.links.length && e.disconnectOutput(n); + } + if (e.onRemoved && e.onRemoved(t), e.graph = null, this._version++, this.list_of_graphcanvas) + for (var n = 0; n < this.list_of_graphcanvas.length; ++n) { + var r = this.list_of_graphcanvas[n]; + r.selected_nodes[e.id] && delete r.selected_nodes[e.id], r.node_dragged == e && (r.node_dragged = null); + } + var o = this._nodes.indexOf(e); + o != -1 && this._nodes.splice(o, 1), delete this._nodes_by_id[e.id], this.onNodeRemoved && this.onNodeRemoved(e, t), this.sendActionToCanvas("checkPanels"), this.setDirtyCanvas(!0, !0), this.afterChange(), this.change(), this.updateExecutionOrder(); + } + } + /** Returns a node by its id. */ + getNodeById(e) { + return e == null ? null : this._nodes_by_id[e]; + } + /** Returns a node by its id. */ + getNodeByIdRecursive(e) { + const t = this.getNodeById(e); + if (t != null) + return t; + for (const i of this.iterateNodesOfClass(ne)) { + const n = i.subgraph.getNodeByIdRecursive(e); + if (n) + return n; + } + return null; + } + /** + * Returns a list of nodes that matches a class + * @param classObject the class itself (not an string) + * @return a list with all the nodes of this type + */ + findNodesByClass(e, t = []) { + t.length = 0; + for (const i of this.iterateNodesOfClass(e)) + t.push(i); + return t; + } + /** + * Returns a list of nodes that matches a type + * @param type the name of the node type + * @return a list with all the nodes of this type + */ + findNodesByType(i, t = []) { + var i = i.toLowerCase(); + t.length = 0; + for (var n = 0, s = this._nodes.length; n < s; ++n) + this._nodes[n].type.toLowerCase() == i && t.push(this._nodes[n]); + return t; + } + /** + * Returns a list of nodes that matches a class + * @param classObject the class itself (not an string) + * @return a list with all the nodes of this type + */ + findNodesByClassRecursive(e, t = []) { + t.length = 0; + for (const i of this.iterateNodesOfClassRecursive(e)) + t.push(i); + return t; + } + /** + * Returns a list of nodes that matches a type + * @param type the name of the node type + * @return a list with all the nodes of this type + */ + findNodesByTypeRecursive(i, t = []) { + var i = i.toLowerCase(); + t.length = 0; + for (const n of this.iterateNodesOfTypeRecursive(i)) + t.push(n); + return t; + } + /** + * Returns the first node that matches a name in its title + * @param title the name of the node to search + * @return the node or null + */ + findNodeByTitle(e) { + for (var t = 0, i = this._nodes.length; t < i; ++t) + if (this._nodes[t].title == e) + return this._nodes[t]; + return null; + } + /** + * Returns a list of nodes that matches a name + * @param title the name of the node to search + * @return a list with all the nodes with this name + */ + findNodesByTitle(e) { + for (var t = [], i = 0, n = this._nodes.length; i < n; ++i) + this._nodes[i].title == e && t.push(this._nodes[i]); + return t; + } + /** + * Returns the top-most node in this position of the canvas + * @param x the x coordinate in canvas space + * @param y the y coordinate in canvas space + * @param nodesList a list with all the nodes to search from, by default is all the nodes in the graph + * @return the node at this position or null + */ + getNodeOnPos(e, t, i, n) { + i = i || this._nodes; + for (var s = null, r = i.length - 1; r >= 0; r--) { + var o = i[r], a = o.titleMode == se.NO_TITLE; + if (o.isPointInside(e, t, n, a)) + return o; + } + return s; + } + /** + * Returns the top-most group in that position + * @param x the x coordinate in canvas space + * @param y the y coordinate in canvas space + * @return the group or null + */ + getGroupOnPos(e, t) { + for (var i = this._groups.length - 1; i >= 0; i--) { + var n = this._groups[i]; + if (n.isPointInside(e, t, 2, !0)) + return n; + } + return null; + } + /** + * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution + * this replaces the ones using the old version with the new version + * @method checkNodeTypes + */ + checkNodeTypes() { + for (var e = !1, t = 0; t < this._nodes.length; t++) { + var i = this._nodes[t], n = u.registered_node_types[i.type]; + if (i.constructor != n.class) { + console.log("node being replaced by newer version: " + i.type); + var s = u.createNode(i.type); + e = !0, this._nodes[t] = s, s.configure(i.serialize()), s.graph = this, this._nodes_by_id[s.id] = s, i.inputs && (s.inputs = i.inputs.concat()), i.outputs && (s.outputs = i.outputs.concat()); + } + } + return this.updateExecutionOrder(), e; + } + // ********** GLOBALS ***************** + onAction(e, t, i = {}) { + for (const n of this.iterateNodesOfClass($)) + if (n.properties.name == e) { + n.actionDo(e, t, i); + break; + } + } + trigger(e, t) { + this.onTrigger && this.onTrigger(e, t); + } + triggerSlot(e, t) { + this.onTrigger && this.onTrigger(e, t); + } + /** Tell this graph it has a global graph input of this type */ + addInput(e, t, i) { + var n = this.inputs[e]; + n || (this.beforeChange(), this.inputs[e] = { name: e, type: t, value: i }, this._version++, this.afterChange(), this.onInputAdded && this.onInputAdded(e, t, i), this.onInputsOutputsChange && this.onInputsOutputsChange()); + } + /** Assign a data to the global graph input */ + setInputData(e, t) { + var i = this.inputs[e]; + i && (i.value = t); + } + /** Returns the current value of a global graph input */ + getInputData(e) { + var t = this.inputs[e]; + return t ? t.value : null; + } + /** Changes the name of a global graph input */ + renameInput(e, t) { + if (t != e) + return this.inputs[e] ? this.inputs[t] ? (console.error("there is already one input with that name"), !1) : (this.inputs[t] = this.inputs[e], delete this.inputs[e], this._version++, this.onInputRenamed && this.onInputRenamed(e, t), this.onInputsOutputsChange && this.onInputsOutputsChange(), !0) : !1; + } + /** Changes the type of a global graph input */ + changeInputType(e, t) { + if (!this.inputs[e]) + return !1; + if (this.inputs[e].type && String(this.inputs[e].type).toLowerCase() == String(t).toLowerCase()) + return; + const i = this.inputs[e].type; + return this.inputs[e].type = t, this._version++, this.onInputTypeChanged && this.onInputTypeChanged(e, i, t), !0; + } + /** Removes a global graph input */ + removeInput(e) { + return this.inputs[e] ? (delete this.inputs[e], this._version++, this.onInputRemoved && this.onInputRemoved(e), this.onInputsOutputsChange && this.onInputsOutputsChange(), !0) : !1; + } + /** Creates a global graph output */ + addOutput(e, t, i) { + this.outputs[e] = { name: e, type: t, value: i }, this._version++, this.onOutputAdded && this.onOutputAdded(e, t, i), this.onInputsOutputsChange && this.onInputsOutputsChange(); + } + /** Assign a data to the global output */ + setOutputData(e, t) { + var i = this.outputs[e]; + i && (i.value = t); + } + /** Returns the current value of a global graph output */ + getOutputData(e) { + var t = this.outputs[e]; + return t ? t.value : null; + } + /** Renames a global graph output */ + renameOutput(e, t) { + return this.outputs[e] ? this.outputs[t] ? (console.error("there is already one output with that name"), !1) : (this.outputs[t] = this.outputs[e], delete this.outputs[e], this._version++, this.onOutputRenamed && this.onOutputRenamed(e, t), this.onInputsOutputsChange && this.onInputsOutputsChange(), !0) : !1; + } + /** Changes the type of a global graph output */ + changeOutputType(e, t) { + if (!this.outputs[e]) + return !1; + if (this.outputs[e].type && String(this.outputs[e].type).toLowerCase() == String(t).toLowerCase()) + return; + const i = this.outputs[e].type; + return this.outputs[e].type = t, this._version++, this.onOutputTypeChanged && this.onOutputTypeChanged(e, i, t), !0; + } + /** Removes a global graph output */ + removeOutput(e) { + return this.outputs[e] ? (delete this.outputs[e], this._version++, this.onOutputRemoved && this.onOutputRemoved(e), this.onInputsOutputsChange && this.onInputsOutputsChange(), !0) : !1; + } + /* TODO implement + triggerInput(name: string, value: any): void { + var nodes = this.findNodesByTitle(name); + for (var i = 0; i < nodes.length; ++i) { + nodes[i].onTrigger(value); + } + } + + setCallback(name: string, func: (...args: any[]) => any): void { + var nodes = this.findNodesByTitle(name); + for (var i = 0; i < nodes.length; ++i) { + nodes[i].setTrigger(func); + } + } + */ + /** used for undo, called before any change is made to the graph */ + beforeChange(e) { + this.onBeforeChange && this.onBeforeChange(this, e), this.sendActionToCanvas("onBeforeChange", [this]); + } + /** used to resend actions, called after any change is made to the graph */ + afterChange(e) { + this.onAfterChange && this.onAfterChange(this, e), this.sendActionToCanvas("onAfterChange", [this]); + } + connectionChange(e, t) { + this.updateExecutionOrder(), this.onConnectionChange && this.onConnectionChange(e), this._version++, this.sendActionToCanvas("onConnectionChange"); + } + /** returns if the graph is in live mode */ + isLive() { + if (!this.list_of_graphcanvas) + return !1; + for (var e = 0; e < this.list_of_graphcanvas.length; ++e) { + var t = this.list_of_graphcanvas[e]; + if (t.live_mode) + return !0; + } + return !1; + } + /** clears the triggered slot animation in all links (stop visual animation) */ + clearTriggeredSlots() { + for (var e in this.links) { + var t = this.links[e]; + t && t._last_time && (t._last_time = 0); + } + } + /* Called when something visually changed (not the graph!) */ + change() { + u.debug && console.log("Graph changed"), this.sendActionToCanvas("setDirty", [!0, !0]), this.onChange && this.onChange(this); + } + setDirtyCanvas(e = !1, t = !1) { + this.sendActionToCanvas("setDirty", [e, t]); + } + /** Destroys a link */ + removeLink(e) { + var t = this.links[e]; + if (t) { + var i = this.getNodeById(t.target_id); + i && i.disconnectInput(t.target_slot); + } + } + /** Creates a Object containing all the info about this graph, it can be serialized */ + serialize() { + for (var e = [], t = 0, i = this._nodes.length; t < i; ++t) + e.push(this._nodes[t].serialize()); + var n = []; + for (const h in this.links) { + var s = this.links[h]; + if (!s.serialize) { + console.error( + "weird LLink bug, link info is not a LLink but a regular object", + s + ); + var r = he.configure(s); + for (var o in s) + r[o] = s[o]; + this.links[h] = r, s = r; + } + n.push(s.serialize()); + } + for (var a = [], t = 0; t < this._groups.length; ++t) + a.push(this._groups[t].serialize()); + var l = { + last_node_id: this.last_node_id, + last_link_id: this.last_link_id, + nodes: e, + links: n, + groups: a, + config: this.config, + extra: this.extra, + version: u.VERSION + }; + return this.onSerialize && this.onSerialize(l), l; + } + /** + * Configure a graph from a JSON string + * @param data configure a graph from a JSON string + * @returns if there was any error parsing + */ + configure(e, t) { + if (e) { + t || this.clear(); + var i = e.nodes; + if (e.links && e.links.constructor === Array) { + for (var n = [], s = 0; s < e.links.length; ++s) { + var r = e.links[s]; + if (!r) { + console.warn("serialized graph link data contains errors, skipping."); + continue; + } + var o = he.configure(r); + n[o.id] = o; + } + e.links = n; + } + for (const c in e) + c == "nodes" || c == "groups" || (this[c] = e[c]); + var a = !1; + if (this._nodes = [], i) { + for (var s = 0, l = i.length; s < l; ++s) { + var h = i[s], p = u.createNode(h.type, h.title); + p || (console.error( + "Node not found or has errors: " + h.type + ), p = new ae(), p.last_serialization = h, p.has_errors = !0, a = !0), p.id = h.id, this.add(p, { addedBy: "configure", skipComputeOrder: !0 }); + } + for (var s = 0, l = i.length; s < l; ++s) { + var h = i[s], p = this.getNodeById(h.id); + p && p.configure(h); + } + } + if (this._groups.length = 0, e.groups) + for (var s = 0; s < e.groups.length; ++s) { + var f = new me(); + f.configure(e.groups[s]), this.addGroup(f); + } + return this.updateExecutionOrder(), this.extra = e.extra || {}, this.onConfigure && this.onConfigure(e), this._version++, this.setDirtyCanvas(!0, !0), a; + } + } + load(e, t) { + var i = this; + if (e.constructor === File || e.constructor === Blob) { + var n = new FileReader(); + n.addEventListener("load", function(r) { + var o = JSON.parse(n.result); + i.configure(o), t && t(o); + }), n.readAsText(e); + return; + } + var s = new XMLHttpRequest(); + s.open("GET", e, !0), s.send(null), s.onload = function(r) { + if (s.status !== 200) { + console.error("Error loading graph:", s.status, s.response); + return; + } + var o = JSON.parse(s.response); + i.configure(o), t && t(o); + }, s.onerror = function(r) { + console.error("Error loading graph:", r); + }; + } +}; +let Pe = De; +Pe.DEFAULT_SUPPORTED_TYPES = ["number", "string", "boolean"]; +function Re(e) { + const t = { nodeIDs: {}, linkIDs: {} }; + for (const i of e.nodes) { + const n = i.id, s = oe(); + if (i.id = s, t.nodeIDs[n] || t.nodeIDs[s]) + throw new Error( + `New/old node UUID wasn't unique in changed map! ${n} ${s}` + ); + t.nodeIDs[n] = s, t.nodeIDs[s] = n; + } + for (const i of e.links) { + const n = i[0], s = oe(); + if (i[0] = s, t.linkIDs[n] || t.linkIDs[s]) + throw new Error( + `New/old link UUID wasn't unique in changed map! ${n} ${s}` + ); + t.linkIDs[n] = s, t.linkIDs[s] = n; + const r = i[1], o = i[3]; + if (!t.nodeIDs[r]) + throw new Error(`Old node UUID not found in mapping! ${r}`); + if (i[1] = t.nodeIDs[r], !t.nodeIDs[o]) + throw new Error(`Old node UUID not found in mapping! ${o}`); + i[3] = t.nodeIDs[o]; + } + for (const i of e.nodes) { + for (const n of i.inputs) + n.link && (n.link = t.linkIDs[n.link]); + for (const n of i.outputs) + n.links && (n.links = n.links.map((s) => t.linkIDs[s])); + } + for (const i of e.nodes) + if (i.type === "graph/subgraph") { + const n = Re( + i.subgraph + ); + t.nodeIDs = { ...t.nodeIDs, ...n.nodeIDs }, t.linkIDs = { ...t.linkIDs, ...n.linkIDs }; + } + return t; +} +function Ve(e, t) { + for (const i of e.iterateNodesInOrderRecursive()) + i.onReassignID && i.onReassignID(t); +} +const Me = class extends ae { + constructor(e, t) { + super(e), this.properties = { + enabled: !0 + }, this.size = [140, 80], this.enabled = !0, this.subgraph = (t || Me.default_lgraph_factory)(), this.subgraph._subgraph_node = this, this.subgraph._is_subgraph = !0; + const i = (n, s) => { + const r = s.bind(this); + return function(...o) { + n == null || n.apply(this, o), r(...o); + }; + }; + this.subgraph.onTrigger = i( + this.subgraph.onTrigger, + this.onSubgraphTrigger + ), this.subgraph.onNodeAdded = i( + this.subgraph.onNodeAdded, + this.onSubgraphNodeAdded + ), this.subgraph.onNodeRemoved = i( + this.subgraph.onNodeRemoved, + this.onSubgraphNodeRemoved + ), this.subgraph.onInputAdded = i( + this.subgraph.onInputAdded, + this.onSubgraphNewInput + ), this.subgraph.onInputRenamed = i( + this.subgraph.onInputRenamed, + this.onSubgraphRenamedInput + ), this.subgraph.onInputTypeChanged = i( + this.subgraph.onInputTypeChanged, + this.onSubgraphTypeChangeInput + ), this.subgraph.onInputRemoved = i( + this.subgraph.onInputRemoved, + this.onSubgraphRemovedInput + ), this.subgraph.onOutputAdded = i( + this.subgraph.onOutputAdded, + this.onSubgraphNewOutput + ), this.subgraph.onOutputRenamed = i( + this.subgraph.onOutputRenamed, + this.onSubgraphRenamedOutput + ), this.subgraph.onOutputTypeChanged = i( + this.subgraph.onOutputTypeChanged, + this.onSubgraphTypeChangeOutput + ), this.subgraph.onOutputRemoved = i( + this.subgraph.onOutputRemoved, + this.onSubgraphRemovedOutput + ); + } + // getRootGraph(): LGraph | null { + // const graphs = Array.from(this.iterateParentGraphs()); + // const graph = graphs[graphs.length - 1] + // // console.warn(graph._is_subgraph) + // if (graph._is_subgraph) + // return null; + // return graph; + // } + *iterateParentGraphs() { + var t; + let e = this.graph; + for (; e; ) + yield e, e = (t = e._subgraph_node) == null ? void 0 : t.graph; + } + onDblClick(e, t, i) { + var n = this; + setTimeout(function() { + i.openSubgraph(n.subgraph); + }, 10); + } + onAction(e, t, i) { + const { originNode: n, link: s } = i; + if (!n || !s) + return; + const r = s.target_slot; + this.getInnerGraphInputByIndex(r).triggerSlot(0, t); + } + onExecute() { + if (this.enabled = this.getInputOrProperty("enabled"), !!this.enabled) { + if (this.inputs) + for (var e = 0; e < this.inputs.length; e++) { + var t = this.inputs[e], i = this.getInputData(e); + this.subgraph.setInputData(t.name, i); + } + if (this.subgraph.runStep(), this.outputs) + for (var e = 0; e < this.outputs.length; e++) { + var n = this.outputs[e], i = this.subgraph.getOutputData(n.name); + this.setOutputData(e, i); + } + } + } + sendEventToAllNodes(e, t, i) { + this.enabled && this.subgraph.sendEventToAllNodes(e, t, i); + } + onDrawBackground(e, t, i, n) { + } + // override onMouseDown(e, localpos, graphcanvas) + // { + // var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + // if(localpos[1] > y) + // { + // graphcanvas.showSubgraphPropertiesDialog(this); + // } + // } + // override onMouseDown(e: MouseEventExt, localpos: Vector2, graphcanvas: LGraphCanvas): boolean | undefined { + // var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + // console.log(0) + // if (localpos[1] > y) { + // if (localpos[0] < this.size[0] / 2) { + // console.log(1) + // graphcanvas.showSubgraphPropertiesDialog(this); + // } else { + // console.log(2) + // graphcanvas.showSubgraphPropertiesDialogRight(this); + // } + // } + // return false; + // } + computeSize() { + var e = this.inputs ? this.inputs.length : 0, t = this.outputs ? this.outputs.length : 0; + return [ + 200, + Math.max(e, t) * u.NODE_SLOT_HEIGHT + u.NODE_SLOT_HEIGHT * 0.5 + ]; + } + //**** INPUTS *********************************** + onSubgraphTrigger(e, t) { + } + onSubgraphNodeAdded(e, t) { + var i, n; + (i = this.graph) != null && i.onNodeAdded && (t.subgraphs || (t.subgraphs = []), t.subgraphs.push(this), (n = this.graph) == null || n.onNodeAdded(e, t)); + } + onSubgraphNodeRemoved(e, t) { + var i, n; + (i = this.graph) != null && i.onNodeRemoved && (t.subgraphs || (t.subgraphs = []), t.subgraphs.push(this), (n = this.graph) == null || n.onNodeRemoved(e, t)); + } + onSubgraphNewInput(e, t) { + var i = this.findInputSlotIndexByName(e); + i == -1 && this.addInput(e, t); + } + onSubgraphRenamedInput(e, t) { + var i = this.findInputSlotIndexByName(e); + if (i != -1) { + var n = this.getInputInfo(i); + n.name = t; + } + } + onSubgraphTypeChangeInput(e, t, i) { + var n = this.findInputSlotIndexByName(e); + if (n != -1) { + var s = this.getInputInfo(n); + s.type = i; + } + } + onSubgraphRemovedInput(e) { + var t = this.findInputSlotIndexByName(e); + t != -1 && this.removeInput(t); + } + //**** OUTPUTS *********************************** + onSubgraphNewOutput(e, t) { + var i = this.findOutputSlotIndexByName(e); + i == -1 && this.addOutput(e, t); + } + onSubgraphRenamedOutput(e, t) { + var i = this.findOutputSlotIndexByName(e); + if (i != -1) { + var n = this.getOutputInfo(i); + n.name = t; + } + } + onSubgraphTypeChangeOutput(e, t, i) { + var n = this.findOutputSlotIndexByName(e); + if (n != -1) { + var s = this.getOutputInfo(n); + s.type = i; + } + } + onSubgraphRemovedOutput(e) { + var t = this.findOutputSlotIndexByName(e); + t != -1 && this.removeOutput(t); + } + // ***************************************************** + getExtraMenuOptions(e, t) { + var i = this; + return [ + { + content: "Open", + callback: function() { + e.openSubgraph(i.subgraph); + } + } + ]; + } + onResize(e) { + console.error("TEST subgraph resize"); + } + serialize() { + var e = ae.prototype.serialize.call(this); + return e.subgraph = this.subgraph.serialize(), e; + } + //no need to define node.configure, the default method detects node.subgraph and passes the object to node.subgraph.configure() + onConfigure(e) { + super.onConfigure && super.onConfigure(e), this.subgraph._is_subgraph = !0, this.subgraph._subgraph_node = this; + for (const t of this.subgraph.iterateNodesInOrder()) + (t.is($) || t.is(Q)) && (t.properties.subgraphID = this.id); + } + onReassignID() { + for (const e of this.subgraph.iterateNodesInOrder()) + (e.is($) || e.is(Q)) && (e.properties.subgraphID = this.id); + } + clone(e = { forNode: {} }) { + var s, r, o, a; + var t = u.createNode(this.type), i = this.serialize(); + let n = null; + if (u.use_uuids) { + const l = u.cloneObject(i.subgraph); + n = Re(l), i.subgraph = l; + } + return delete i.id, delete i.inputs, delete i.outputs, t.configure(i), u.use_uuids && Ve(t.subgraph, n), (s = e.forNode)[r = this.id] || (s[r] = {}), e.forNode[this.id].subgraphNewIDMapping = n, (o = e.forNode)[a = t.id] || (o[a] = {}), e.forNode[t.id].subgraphNewIDMapping = n, t; + } + buildFromNodes(e) { + var _, y; + if (e = e.filter((b) => !b.is($) && !b.is(Q)), e.length === 0) + return; + const t = {}, i = {}, n = {}, s = e.reduce((b, m) => (b[m.id] = m, b), {}); + let r = Number.MAX_SAFE_INTEGER, o = 0, a = Number.MAX_SAFE_INTEGER, l = 0; + for (const b of Object.values(e)) + r = Math.min(b.pos[0], r), o = Math.max(b.pos[0] + b.size[0], o), a = Math.min(b.pos[1], a), l = Math.max(b.pos[1] + b.size[1], l); + const h = {}; + for (const b of e) { + h[b.id] = b; + for (let m = 0; m < b.inputs.length; m++) { + const E = b.getInputLink(m); + if (E) { + const T = b.getConnectionPos(!0, m), O = b.getInputInfo(m), A = b.getInputNode(m); + A && (h[A.id] = A), s[E.origin_id] != null ? n[E.id] = [E, T] : t[E.id] = [E, T, O.name]; + } + } + for (let m = 0; m < b.outputs.length; m++) { + const E = b.getOutputLinks(m); + for (const T of E) { + const O = b.getConnectionPos(!1, m), A = b.getOutputInfo(m), M = b.graph.getNodeById(T.target_id); + M && (h[M.id] = M), s[T.target_id] != null ? n[T.id] = [T, O] : i[T.id] = [T, O, A.name]; + } + } + } + const p = Object.values(t), f = Object.values(i); + p.sort((b, m) => b[1][1] - m[1][1]), f.sort((b, m) => b[1][1] - m[1][1]), u.debug && (console.debug("NODES", Object.keys(e)), console.debug("IN", Object.keys(t)), console.debug("OUT", Object.keys(i)), console.debug("INNER", Object.keys(n))); + const c = {}, v = {}; + for (const b of e) { + const m = [b.pos[0] - r, b.pos[1] - a], E = b.id; + b.graph.remove(b, { removedBy: "moveIntoSubgraph" }), this.subgraph.add(b, { + addedBy: "moveIntoSubgraph", + prevNodeID: E + }), b.pos = m, h[E] = b, h[b.id] = b; + } + let g = 0, d = 0; + for (const [b, m, E] of p) { + let T = null; + if (c[b.origin_id] && (T = c[b.origin_id][b.origin_slot]), !T && (T = this.addGraphInput(E, b.type, [ + -200, + g + ]), g += T.innerNode.size[1] + u.NODE_SLOT_HEIGHT, !T)) { + console.error( + "Failed creating subgraph output pair!", + b + ); + continue; + } + const O = h[b.origin_id], A = h[b.target_id]; + O.connect(b.origin_slot, this, T.outerInputIndex), T.innerNode.connect(0, A, b.target_slot), c[_ = b.origin_id] || (c[_] = {}), c[b.origin_id][b.origin_slot] = T; + } + for (const [b, m, E] of f) { + let T = null; + if (v[b.target_id] && (T = v[b.target_id][b.target_slot]), !T && (T = this.addGraphOutput(E, b.type, [ + o - r + 200, + d + ]), d += T.innerNode.size[1] + u.NODE_SLOT_HEIGHT, !T)) { + console.error( + "Failed creating subgraph output pair!", + b + ); + continue; + } + const O = h[b.origin_id], A = h[b.target_id]; + O.connect(b.origin_slot, T.innerNode, 0), this.connect(T.outerOutputIndex, A, b.target_slot), v[y = b.target_id] || (v[y] = {}), v[b.target_id][b.origin_slot] = T; + } + for (const [b, m] of Object.values(n)) { + const E = h[b.origin_id], T = h[b.target_id]; + E.connect( + b.origin_slot, + T, + b.target_slot + ); + } + } + addGraphInput(e, t, i) { + e = this.getValidGraphInputName(e); + const n = u.createNode($); + if (n == null) + return null; + let s = t; + t === I.EVENT ? s = I.ACTION : t === I.ACTION && (t = I.EVENT), console.warn("[Subgraph] addGraphInput", e, t, s, i), n.setProperty("name", e), n.setProperty("type", t), n.properties.subgraphID = this.id, this.subgraph.add(n); + const r = n.computeSize(); + i && (n.pos = [ + i[0] - r[0] * 0.5, + i[1] - r[1] * 0.5 + ]), this.subgraph.addInput(e, s, null); + const o = this.inputs.length - 1, a = this.inputs[o]; + return { innerNode: n, outerInput: a, outerInputIndex: o }; + } + addGraphOutput(e, t, i) { + e = this.getValidGraphOutputName(e); + const n = u.createNode(Q); + if (n == null) + return null; + let s = t; + t === I.EVENT ? t = I.ACTION : t === I.ACTION && (s = I.EVENT), console.warn("[Subgraph] addGraphOutput", e, t, s, i), n.setProperty("name", e), n.setProperty("type", t), n.properties.subgraphID = this.id, this.subgraph.add(n); + const r = n.computeSize(); + i && (n.pos = [i[0], i[1] - r[1] * 0.5]), this.subgraph.addOutput(e, s, null); + const o = this.outputs.length - 1, a = this.outputs[o]; + return { innerNode: n, outerOutput: a, outerOutputIndex: o }; + } + removeGraphInput(e) { + if (this.findInputSlotIndexByName(e) == null) { + console.error("[Subgraph] No input in slot!", e); + return; + } + const i = this.subgraph.findNodesByClass($).filter((n) => n.properties.name === e); + if (i.length > 0) + for (const n of i) + this.subgraph.remove(n); + else { + console.warn( + "[Subgraph] No GraphInputs found on input removal", + e + ); + const n = this.findInputSlotIndexByName(e); + n !== -1 && this.removeInput(n); + } + } + removeGraphOutput(e) { + if (this.findOutputSlotIndexByName(e) == null) { + console.error("[Subgraph] No output in slot!", e); + return; + } + const i = this.subgraph.findNodesByClass(Q).filter((n) => n.properties.name === e); + if (i.length > 0) + for (const n of i) + this.subgraph.remove(n); + else { + console.warn( + "[Subgraph] No GraphOutputs found on output removal", + e + ); + const n = this.findOutputSlotIndexByName(e); + n !== -1 && this.removeOutput(n); + } + } + getValidGraphInputName(e) { + e || (e = "newInput"); + let t = e, i = this.getInnerGraphInput(t), n = 1; + for (; i != null; ) + t = `${e}_${n++}`, i = this.getInnerGraphInput(t); + return t; + } + getValidGraphOutputName(e) { + e || (e = "newOutput"); + let t = e, i = this.getInnerGraphOutput(t), n = 1; + for (; i != null; ) + t = `${e}_${n++}`, i = this.getInnerGraphOutput(t); + return t; + } + getInnerGraphOutput(e) { + return this.subgraph._nodes.find((i) => i.is(Q) && i.properties.name === e) || null; + } + getInnerGraphInput(e) { + return this.subgraph._nodes.find((i) => i.is($) && i.properties.name === e) || null; + } + getInnerGraphOutputByIndex(e) { + const t = this.getOutputInfo(e); + return t ? this.getInnerGraphOutput(t.name) : null; + } + getInnerGraphInputByIndex(e) { + const t = this.getInputInfo(e); + return t ? this.getInnerGraphInput(t.name) : null; + } + moveNodesToParentGraph(e) { + if (e = e.filter((g) => !g.is($) && !g.is(Q)), e.length === 0) + return; + const t = this, i = t.graph; + let n = Number.MAX_SAFE_INTEGER, s = 0, r = Number.MAX_SAFE_INTEGER, o = 0; + for (const g of Object.values(e)) + n = Math.min(g.pos[0], n), s = Math.max(g.pos[0] + g.size[0], s), r = Math.min(g.pos[1], r), o = Math.max(g.pos[1] + g.size[1], o); + const a = s - n, l = o - r, h = t.pos[0] + t.size[0] / 2 - a / 2, p = t.pos[1] + t.size[1] / 2 - l / 2, f = {}, c = {}; + for (const [g, d] of e.entries()) + c[d.id] = d; + for (const g of e) + for (const d of g.iterateAllLinks()) { + const _ = d.target_id === g.id, y = g.getConnectionPos( + _, + _ ? d.target_slot : d.origin_slot + ); + c[d.origin_id] != null && c[d.target_id] != null && (f[d.id] = [d, y]); + } + const v = {}; + for (const [g, d] of e.entries()) { + const _ = [ + d.pos[0] - n + h, + d.pos[1] - r + p + ], y = d.id; + d.graph.remove(d, { removedBy: "moveOutOfSubgraph" }), i.add(d, { addedBy: "moveOutOfSubgraph", prevNodeID: y }), d.pos = _, v[y] = d; + } + for (const [g, d] of Object.values(f)) { + const _ = c[g.origin_id], y = c[g.target_id]; + _.connect(g.origin_slot, y, g.target_slot); + } + return v; + } + convertNodesToSubgraphInputs(e) { + var a; + if (e = e.filter((l) => !l.is($) && !l.is(Q)), e.length === 0) + return; + const t = ve(e, (l) => l.id), i = [], n = {}, s = this.subgraph; + for (const l of e) + for (const h of l.iterateAllLinks()) { + if (t[h.origin_id] == null) + throw new Error( + "Can't convert to input with an origin link outward" + ); + if (t[h.target_id] == null) { + i.push(h); + const p = [0, 0]; + l.getConnectionPos(!1, h.target_slot, p), n[l.id] = [ + [l.pos[0], l.pos[1]], + p + ]; + } + } + const r = this.moveNodesToParentGraph(e), o = {}; + for (const l of i) { + const h = s.getNodeById(l.target_id), p = h.getInputInfo(l.target_slot); + o[a = l.origin_id] || (o[a] = {}); + let f = o[l.origin_id][l.origin_slot]; + if (f == null) { + const v = this.getValidGraphInputName(p.name); + f = this.addGraphInput(v, p.type), o[l.origin_id][l.origin_slot] = f; + const [g, d] = n[l.origin_id], _ = f.innerNode.pos, y = f.innerNode.computeSize(), b = f.innerNode.getConnectionPos(!0, 0), m = [ + f.innerNode.pos[0] - b[0], + f.innerNode.pos[1] - b[1] + ], E = [ + d[0] + m[0] - y[0], + d[1] + m[1] + ]; + console.warn( + "newPos", + _, + "size", + f.innerNode.size, + "connPos", + d, + "newConPos", + b, + "offset", + m + ), f.innerNode.pos = E; + } + r[l.origin_id].connect(l.origin_slot, this, f.outerInputIndex), f.innerNode.connect(0, h, l.target_slot); + } + } + convertNodesToSubgraphOutputs(e) { + var a; + if (e = e.filter((l) => !l.is($) && !l.is(Q)), e.length === 0) + return; + const t = ve(e, (l) => l.id), i = [], n = {}, s = this.subgraph; + for (const l of e) + for (const h of l.iterateAllLinks()) + if (t[h.origin_id] == null) { + i.push(h); + const p = [0, 0]; + l.getConnectionPos(!0, h.origin_slot, p), n[l.id] = [ + [l.pos[0], l.pos[1]], + p + ]; + } else if (t[h.target_id] == null) + throw new Error( + "Can't convert to input with an origin link outward" + ); + const r = this.moveNodesToParentGraph(e), o = {}; + for (const l of i) { + const h = s.getNodeById(l.origin_id), p = h.getOutputInfo(l.origin_slot); + o[a = l.target_id] || (o[a] = {}); + let f = o[l.target_id][l.target_slot]; + if (f == null) { + const v = this.getValidGraphOutputName(p.name); + f = this.addGraphOutput(v, p.type), o[l.target_id][l.target_slot] = f; + const [g, d] = n[l.target_id], _ = f.innerNode.getConnectionPos(!0, 0), y = [ + f.innerNode.pos[0] - _[0], + f.innerNode.pos[1] - _[1] + ], b = [ + d[0] + y[0], + d[1] + y[1] + ]; + f.innerNode.pos = b; + } + const c = r[l.target_id]; + h.connect(l.origin_slot, f.innerNode, 0), this.connect(f.outerOutputIndex, c, l.target_slot); + } + } +}; +let ne = Me; +ne.default_lgraph_factory = () => new Pe(); +ne.slotLayout = { + inputs: [], + outputs: [] +}; +ne.propertyLayout = [ + { name: "enabled", defaultValue: !0 } +]; +ne.optionalSlots = { + outputs: [{ name: "enabled", type: "boolean" }] +}; +u.registerNodeType({ + class: ne, + title: "Subgraph", + desc: "Graph inside a node", + title_color: "#334", + type: "graph/subgraph" +}); +class C { + static onMenuCollapseAll() { + } + static onMenuNodeEdit() { + } + // refactor: there are different dialogs, some uses createDialog some dont + prompt(t = "", i, n, s, r = !1, o = null) { + var a = this, l = document.createElement("div"); + if (l.is_modified = !1, l.className = "graphdialog rounded", r) { + let T = 5; + typeof i != "string" && (i = JSON.stringify(i, null, 2)); + const O = (i.match(/\n/g) || "").length + 1; + T = Te(O, 5, 10), l.innerHTML = ` + + + +`; + } else + l.innerHTML = ` + + +`; + l.close = function() { + a.prompt_box = null, l.parentNode && l.parentNode.removeChild(l); + }; + var h = N.active_canvas, p = h.canvas; + p.parentNode.appendChild(l), this.ds.scale > 1 && (l.style.transform = "scale(" + this.ds.scale + ")"); + var f = null, c = 0; + u.pointerListenerAdd(l, "leave", function(T) { + c || u.dialog_close_on_mouse_leave && !l.is_modified && u.dialog_close_on_mouse_leave && T.buttons === 0 && (f = setTimeout(l.close, u.dialog_close_on_mouse_leave_delay)); + }), u.pointerListenerAdd(l, "enter", function(T) { + u.dialog_close_on_mouse_leave && f && clearTimeout(f); + }); + var v = l.querySelectorAll("select"); + v && v.forEach(function(T) { + T.addEventListener("click", function(O) { + c++; + }), T.addEventListener("blur", function(O) { + c = 0; + }), T.addEventListener("change", function(O) { + c = -1; + }); + }), a.prompt_box && a.prompt_box.close(), a.prompt_box = l; + var g = l.querySelector(".name"); + g.innerText = t; + let d = l.querySelector(".value"); + d.value = i; + var _ = d; + if (_.addEventListener("keydown", function(T) { + if (l.is_modified = !0, T.keyCode == 27) + l.close(); + else if (T.keyCode == 13 && T.target instanceof Element && T.target.localName != "textarea") + n && n(this.value), l.close(); + else + return; + T.preventDefault(), T.stopPropagation(); + }), o) + for (const [T, O] of Object.entries(o)) + _.style[T] = O; + var y = l.querySelector("button"); + y.addEventListener("click", function(T) { + n && n(_.value), a.setDirty(!0), l.close(); + }); + var b = p.getBoundingClientRect(), m = -20, E = -20; + return b && (m -= b.left, E -= b.top), s ? (l.style.left = s.clientX + "px", l.style.top = s.clientY + "px") : (l.style.left = p.width * 0.5 + m + "px", l.style.top = p.height * 0.5 + E + "px"), console.warn(l.style.left, l.style.top), console.warn(s), setTimeout(function() { + _.focus(); + }, 10), Ee(l), l; + } + showSearchBox(t, i = {}) { + var n = { + slotFrom: null, + node_from: null, + node_to: null, + do_type_filter: u.search_filter_enabled, + type_filter_in: null, + type_filter_out: null, + show_general_if_none_on_typefilter: !0, + show_general_after_typefiltered: !0, + hide_on_mouse_leave: u.search_hide_on_mouse_leave, + show_all_if_empty: !0, + show_all_on_open: u.search_show_all_on_open + }; + i = Object.assign(n, i); + var s = this, r = N.active_canvas, o = r.canvas, a = o.ownerDocument || document; + let l = t; + var h = document.createElement("div"); + h.className = "litegraph litesearchbox graphdialog rounded", h.innerHTML = "Search ", i.do_type_filter && (h.innerHTML += "", h.innerHTML += ""), h.innerHTML += "
", a.fullscreenElement ? a.fullscreenElement.appendChild(h) : (a.body.appendChild(h), a.body.style.overflow = "hidden"); + let p = null, f = null; + if (i.do_type_filter && (p = h.querySelector(".slot_in_type_filter"), f = h.querySelector(".slot_out_type_filter")), h.close = function() { + s.search_box = null, this.blur(), o.focus(), a.body.style.overflow = "", setTimeout(function() { + s.canvas.focus(); + }, 20), h.parentNode && h.parentNode.removeChild(h); + }, this.ds.scale > 1 && (h.style.transform = "scale(" + this.ds.scale + ")"), i.hide_on_mouse_leave) { + var c = 0, v = null; + u.pointerListenerAdd(h, "enter", function(D) { + v && (clearTimeout(v), v = null); + }), u.pointerListenerAdd(h, "leave", function(D) { + c || (v = setTimeout(function() { + h.close(); + }, 500)); + }), i.do_type_filter && (p.addEventListener("click", function(D) { + c++; + }), p.addEventListener("blur", function(D) { + c = 0; + }), p.addEventListener("change", function(D) { + c = -1; + }), f.addEventListener("click", function(D) { + c++; + }), f.addEventListener("blur", function(D) { + c = 0; + }), f.addEventListener("change", function(D) { + c = -1; + })); + } + s.search_box && s.search_box.close(), s.search_box = h; + var g = h.querySelector(".helper"), d = null, _ = null, y = null; + const b = (D) => { + if (D) + if (s.onSearchBoxSelection) + s.onSearchBoxSelection(D, l, r); + else { + var P = u.searchbox_extras[D.toLowerCase()]; + P && (D = P.type), r.graph.beforeChange(); + var F = u.createNode(D); + if (F && (F.pos = r.convertEventToCanvasOffset( + l + ), r.graph.add(F)), P && P.data) { + if (P.data.properties) + for (var V in P.data.properties) + F.addProperty("" + V, P.data.properties[V]); + if (P.data.inputs) { + F.inputs = []; + for (var V in P.data.inputs) + F.addInput( + P.data.inputs[V][0], + P.data.inputs[V][1] + ); + } + if (P.data.outputs) { + F.outputs = []; + for (var V in P.data.outputs) + F.addOutput( + P.data.outputs[V][0], + P.data.outputs[V][1] + ); + } + P.data.title && (F.title = P.data.title), P.data.json && F.configure(P.data.json); + } + if (i.node_from) { + var R = null; + switch (typeof i.slotFrom) { + case "string": + R = i.node_from.findOutputSlotIndexByName(i.slotFrom); + break; + case "object": + i.slotFrom.name ? R = i.node_from.findOutputSlotIndexByName(i.slotFrom.name) : R = -1, R == -1 && typeof i.slotFrom.slot_index < "u" && (R = i.slotFrom.slot_index); + break; + case "number": + R = i.slotFrom; + break; + default: + R = 0; + } + R = R, typeof i.node_from.outputs[R] !== void 0 && R !== null && R > -1 && i.node_from.connectByTypeInput(R, F, i.node_from.outputs[R].type); + } + if (i.node_to) { + var R = null; + switch (typeof i.slotFrom) { + case "string": + R = i.node_to.findInputSlotIndexByName(i.slotFrom); + break; + case "number": + R = i.slotFrom; + break; + default: + R = 0; + } + typeof i.node_to.inputs[R] !== void 0 && R !== null && R > -1 && i.node_to.connectByTypeOutput(R, F, i.node_to.inputs[R].type); + } + r.graph.afterChange(); + } + h.close(); + }, m = (D) => { + var P = y; + y && y.classList.remove("selected"), y ? (y = D ? y.nextSibling : y.previousSibling, y || (y = P)) : y = D ? g.childNodes[0] : g.childNodes[g.childNodes.length], y && (y.classList.add("selected"), y.scrollIntoView({ block: "end", behavior: "smooth" })); + }, E = (D, P, F, V, R, le = {}) => { + const ee = Object.assign({ + skipFilter: !1, + inTypeOverride: null, + outTypeOverride: null + }, le), fe = u.registered_node_types[D]; + if (fe.hide_in_node_lists || P && fe.filter != P || (!i.show_all_if_empty || F) && D.toLowerCase().indexOf(F) === -1) + return !1; + if (i.do_type_filter && !ee.skipFilter) { + const W = D; + let H = V == null ? void 0 : V.value; + if (ee.inTypeOverride != null && (H = ee.inTypeOverride), V && H && u.registered_slot_in_types[H] && u.registered_slot_in_types[H].nodes) { + var te = u.registered_slot_in_types[H].nodes.includes(W); + if (te === !1) + return !1; + } + if (H = R == null ? void 0 : R.value, ee.outTypeOverride != null && (H = ee.outTypeOverride), R && H && u.registered_slot_out_types[H] && u.registered_slot_out_types[H].nodes) { + var te = u.registered_slot_out_types[H].nodes.includes(W); + if (te === !1) + return !1; + } + } + return !0; + }, T = () => { + _ = null; + var D = O.value; + if (d = null, g.innerHTML = "", !D && !i.show_all_if_empty) + return; + if (s.onSearchBox) { + var P = s.onSearchBox(g, D, r); + if (P) + for (var F = 0; F < P.length; ++F) + te(P[F]); + } else { + var V = 0; + D = D.toLowerCase(); + var R = r.filter || r.graph.filter; + let W, H; + i.do_type_filter && s.search_box ? (W = s.search_box.querySelector(".slot_in_type_filter"), H = s.search_box.querySelector(".slot_out_type_filter")) : (W = null, H = null); + for (const x in u.searchbox_extras) { + var le = u.searchbox_extras[x]; + if (!((!i.show_all_if_empty || D) && le.desc.toLowerCase().indexOf(D) === -1)) { + var be = u.registered_node_types[le.type]; + if (!(be && be.filter != R) && E(le.type, R, D, W, H) && (te(le.desc, "searchbox_extra"), N.search_limit !== -1 && V++ > N.search_limit)) + break; + } + } + var ee = null; + if (Array.prototype.filter) + var fe = Object.keys(u.registered_node_types), ee = fe.filter((q) => E(q, R, D, W, H)); + else { + ee = []; + for (const x in u.registered_node_types) + E(x, R, D, W, H) && ee.push(x); + } + for (var F = 0; F < ee.length && (te(ee[F]), !(N.search_limit !== -1 && V++ > N.search_limit)); F++) + ; + if (i.show_general_after_typefiltered && (W != null && W.value || H != null && H.value)) { + let x = []; + for (const q in u.registered_node_types) + E(q, R, D, W, H, { inTypeOverride: W && W.value ? "*" : null, outTypeOverride: H && H.value ? "*" : null }) && x.push(q); + for (let q = 0; q < x.length && (te(x[q], "generic_type"), !(N.search_limit !== -1 && V++ > N.search_limit)); q++) + ; + } + if ((W != null && W.value || H != null && H.value) && (g == null ? void 0 : g.childNodes.length) == 0 && i.show_general_if_none_on_typefilter) { + let x = []; + for (const q in u.registered_node_types) + E(q, R, D, W, H, { skipFilter: !0 }) && x.push(q); + for (let q = 0; q < x.length && (te(x[q], "not_in_filter"), !(N.search_limit !== -1 && V++ > N.search_limit)); q++) + ; + } + } + function te(W, H) { + var x = document.createElement("div"); + d || (d = W), x.innerText = W, x.dataset.type = escape(W), x.className = "litegraph lite-search-item", H && (x.className += " " + H), x.addEventListener("click", function(q) { + b(unescape(this.dataset.type)); + }), g.appendChild(x); + } + }; + var O = h.querySelector("input"); + if (O && (O.addEventListener("blur", function(D) { + this.focus(); + }), O.addEventListener("keydown", function(D) { + if (D.keyCode == 38) + m(!1); + else if (D.keyCode == 40) + m(!0); + else if (D.keyCode == 27) + h.close(); + else if (D.keyCode == 13) + y ? b(y.innerHTML) : d ? b(d) : h.close(); + else { + _ && clearInterval(_), _ = setTimeout(T, u.search_box_refresh_interval_ms); + return; + } + return D.preventDefault(), D.stopPropagation(), D.stopImmediatePropagation(), !0; + })), i.do_type_filter) { + if (p) { + var A = u.slot_types_in, M = A.length; + (i.type_filter_in == I.EVENT || i.type_filter_in == I.ACTION) && (i.type_filter_in = "_event_"); + for (var L = 0; L < M; L++) { + var B = document.createElement("option"); + B.value = A[L], B.innerHTML = A[L], p.appendChild(B), i.type_filter_in !== null && (i.type_filter_in + "").toLowerCase() == (A[L] + "").toLowerCase() && (B.selected = !0); + } + p.addEventListener("change", T); + } + if (f) { + var A = u.slot_types_out, M = A.length; + (i.type_filter_out == I.EVENT || i.type_filter_out == I.ACTION) && (i.type_filter_out = "_event_"); + for (var L = 0; L < M; L++) { + var B = document.createElement("option"); + B.value = A[L], B.innerHTML = A[L], f.appendChild(B), i.type_filter_out !== null && (i.type_filter_out + "").toLowerCase() == (A[L] + "").toLowerCase() && (B.selected = !0); + } + f.addEventListener("change", T); + } + } + var G = o.getBoundingClientRect(), z = (l ? l.clientX : G.left + G.width * 0.5) - 80, pe = (l ? l.clientY : G.top + G.height * 0.5) - 20; + return h.style.left = z + "px", h.style.top = pe + "px", l.layerY > G.height - 200 && (g.style.maxHeight = G.height - l.layerY - 20 + "px"), O.focus(), i.show_all_on_open && T(), h; + } + showShowNodePanel(t) { + this.closePanels(); + var i = this.getCanvasWindow(), n = this, s = this.createPanel(t.title || "", { + closable: !0, + window: i, + onOpen: function() { + }, + onClose: function() { + n.node_panel = null; + } + }); + n.node_panel = s, s.id = "node-panel", s.node = t, s.classList.add("settings"); + function r() { + s.content.innerHTML = "", s.addHTML("" + t.type + "" + (t.constructor.desc || "") + ""), s.addHTML("

Properties

"); + var o = function(f, c) { + switch (n.graph.beforeChange(t), f) { + case "Title": + t.title = c; + break; + case "Mode": + var v = Object.values(re).indexOf(c); + v >= Z.ALWAYS && re[v] ? t.changeMode(v) : console.warn("unexpected mode: " + c); + break; + case "Color": + N.node_colors[c] ? (t.color = N.node_colors[c].color, t.bgcolor = N.node_colors[c].bgcolor) : console.warn("unexpected color: " + c); + break; + default: + t.setProperty(f, c); + break; + } + n.graph.afterChange(), n.dirty_canvas = !0; + }; + s.addWidget("string", "Title", t.title, {}, o), s.addWidget("combo", "Mode", re[t.mode], { values: re }, o); + var a = ""; + t.color !== void 0 && (a = Object.keys(N.node_colors).filter(function(f) { + return N.node_colors[f].color == t.color; + })[0]), s.addWidget("combo", "Color", a, { values: Object.keys(N.node_colors) }, o); + for (var l in t.properties) { + var h = t.properties[l], p = t.getPropertyInfo(l); + p.type, !(t.onAddPropertyToPanel && t.onAddPropertyToPanel(l, s)) && s.addWidget(p.widget || p.type, l, h, p, o); + } + s.addSeparator(), t.onShowCustomPanelInfo && t.onShowCustomPanelInfo(s), s.footer.innerHTML = "", s.addButton("Delete", function() { + t.block_delete || (t.graph.remove(t), s.close()); + }).classList.add("delete"); + } + s.inner_showCodePad = function(o) { + s.classList.remove("settings"), s.classList.add("centered"), s.alt_content.innerHTML = ""; + var a = s.alt_content.querySelector("textarea"), l = function() { + s.toggleAltContent(!1), s.toggleFooterVisibility(!0), a.parentNode.removeChild(a), s.classList.add("settings"), s.classList.remove("centered"), r(); + }; + a.value = t.properties[o], a.addEventListener("keydown", function(f) { + f.code == "Enter" && f.ctrlKey && (t.setProperty(o, a.value), l()); + }), s.toggleAltContent(!0), s.toggleFooterVisibility(!1), a.style.height = "calc(100% - 40px)"; + var h = s.addButton("Assign", function() { + t.setProperty(o, a.value), l(); + }); + s.alt_content.appendChild(h); + var p = s.addButton("Close", l); + p.style.float = "right", s.alt_content.appendChild(p); + }, r(), this.canvas.parentNode.appendChild(s); + } + showSubgraphPropertiesDialog(t) { + console.log("showing subgraph properties dialog"); + var i = this.canvas.parentNode.querySelector(".subgraph_dialog"); + i && i.close(); + var n = this.createPanel("Subgraph Inputs", { closable: !0, width: 500 }); + n.node = t, n.classList.add("subgraph_dialog"); + const s = t; + var r = s.subgraph; + if (!r) { + console.warn("subnode without subgraph!"); + return; + } + function o() { + if (n.clear(), t.inputs) + for (var d = 0; d < t.inputs.length; ++d) { + var _ = t.inputs[d]; + if (_.not_subgraph_input) + continue; + var y = ` + + + + + +`, b = n.addHTML(y, "subgraph_property"); + b.dataset.name = _.name, b.dataset.slot = "" + d, b.querySelector(".name").innerText = _.name, b.querySelector(".type").innerText = J(_.type), b.querySelector(".delete").addEventListener("click", function(T) { + const O = this.parentNode.dataset.name; + s.removeGraphInput(O), o(); + }); + const m = b.querySelector(".move_up"); + m.disabled = d <= 0, m.addEventListener("click", function(T) { + const O = +this.parentNode.dataset.slot; + O < 0 || (s.moveInput(O, O - 1), o()); + }); + const E = b.querySelector(".move_down"); + E.disabled = d >= t.inputs.length - 1, E.addEventListener("click", function(T) { + const O = +this.parentNode.dataset.slot; + O > t.inputs.length - 1 || (s.moveInput(O, O + 1), o()); + }); + } + } + var a = ` ++ +Name + +Type + +`, l = n.addHTML(a, "subgraph_property extra", !0); + const h = l.querySelector(".name"), p = l.querySelector(".type"), f = l.querySelector("button"); + for (const d of we()) { + var c = document.createElement("option"); + c.value = d, c.innerHTML = J(d), p.appendChild(c), d === "*" && (c.selected = !0); + } + const v = () => { + const d = h.value; + let _ = p.value; + _ === "-1" ? _ = I.ACTION : _ === "-2" && (_ = I.EVENT), !(!d || t.findInputSlotIndexByName(d) != -1) && (this.addGraphInputNode(t, d, _), h.value = "", p.value = "", o(), h.focus()); + }, g = (d) => { + d.keyCode == 13 ? (v(), d.preventDefault()) : d.keyCode == 27 && (n.close(), d.preventDefault()); + }; + return f.addEventListener("click", v), h.addEventListener("keydown", g), p.addEventListener("keydown", g), o(), this.canvas.parentNode.appendChild(n), h.focus(), n; + } + showSubgraphPropertiesDialogRight(t) { + var i = this.canvas.parentNode.querySelector(".subgraph_dialog"); + i && i.close(); + var n = this.createPanel("Subgraph Outputs", { closable: !0, width: 500 }); + n.node = t, n.classList.add("subgraph_dialog"); + const s = t; + if (!s.subgraph) { + console.warn("subnode without subgraph!"); + return; + } + function o() { + if (n.clear(), t.outputs) + for (var d = 0; d < t.outputs.length; ++d) { + var _ = t.outputs[d]; + if (_.not_subgraph_output) + continue; + var y = ` + + + + + +`, b = n.addHTML(y, "subgraph_property"); + b.dataset.name = _.name, b.dataset.slot = "" + d, b.querySelector(".name").innerText = _.name, b.querySelector(".type").innerText = J(_.type), b.querySelector("button").addEventListener("click", function(T) { + const O = this.parentNode.dataset.name; + s.removeGraphOutput(O), o(); + }); + const m = b.querySelector(".move_up"); + m.disabled = d <= 0, m.addEventListener("click", function(T) { + const O = +this.parentNode.dataset.slot; + O < 0 || (s.moveOutput(O, O - 1), o()); + }); + const E = b.querySelector(".move_down"); + E.disabled = d >= t.outputs.length - 1, E.addEventListener("click", function(T) { + const O = +this.parentNode.dataset.slot; + O > t.outputs.length - 1 || (s.moveOutput(O, O + 1), o()); + }); + } + } + var a = ` ++ +Name + +Type + +`, l = n.addHTML(a, "subgraph_property extra", !0); + const h = l.querySelector(".name"), p = l.querySelector(".type"), f = l.querySelector("button"); + for (const d of Le()) { + var c = document.createElement("option"); + c.value = d, c.innerHTML = J(d), p.appendChild(c), d === "*" && (c.selected = !0); + } + const v = () => { + const d = h.value; + let _ = p.value; + _ === "-1" ? _ = I.ACTION : _ === "-2" && (_ = I.EVENT), !(!d || t.findOutputSlotIndexByName(d) != -1) && (this.addGraphOutputNode(t, d, _), h.value = "", p.value = "", o(), h.focus()); + }, g = (d) => { + d.keyCode == 13 ? (v(), d.preventDefault()) : d.keyCode == 27 && (n.close(), d.preventDefault()); + }; + return f.addEventListener("click", v), h.addEventListener("keydown", g), p.addEventListener("keydown", g), o(), this.canvas.parentNode.appendChild(n), h.focus(), n; + } + showConnectionMenu(t = {}) { + var i = t.nodeFrom && t.slotFrom, n = !i && t.nodeTo && t.slotTo; + if (!i && !n) + return console.warn("No data passed to showConnectionMenu"), !1; + var s = i ? t.nodeFrom : t.nodeTo; + const r = i ? t.slotFrom : t.slotTo; + let o; + var a = null; + switch (typeof r) { + case "string": + a = i ? s.findOutputSlotIndexByName(r) : s.findInputSlotIndexByName(r), o = i ? s.outputs[r] : s.inputs[r]; + break; + case "object": + o = r, a = i ? s.findOutputSlotIndexByName(o.name) : s.findInputSlotIndexByName(o.name); + break; + case "number": + a = r, o = i ? s.outputs[a] : s.inputs[a]; + break; + default: + return console.error("Can't get slot information", r), !1; + } + var l = [{ content: "Add Node" }, j.SEPARATOR]; + s.graph._is_subgraph && (i ? l.push({ content: "Add Subgraph Output" }) : l.push({ content: "Add Subgraph Input" }), l.push(j.SEPARATOR)), this.allow_searchbox && (l.push({ content: "Search" }), l.push(j.SEPARATOR)); + var h = o.type == I.EVENT ? "_event_" : o.type, p = i ? u.slot_types_default_out : u.slot_types_default_in; + const f = p[h]; + if (console.warn("FROMSL", p, f), p && p[h]) + if (Array.isArray(f)) + for (var c of f) { + const b = typeof c == "string" ? c : (c == null ? void 0 : c.title) || (c == null ? void 0 : c.node); + l.push({ content: b, value: c }); + } + else + throw new Error(`Invalid default slot specifier, must be an array: ${f}`); + const v = (b) => { + const m = s.graph._subgraph_node, E = [b.canvasX, b.canvasY]; + m.addGraphInput(o.name, o.type, E).innerNode.connect(0, s, a); + }, g = (b) => { + const m = s.graph._subgraph_node, E = [b.canvasX, b.canvasY], T = m.addGraphOutput(o.name, o.type, E); + s.connect(a, T.innerNode, 0); + }, d = (b) => { + const m = Object.assign(t, { + position: [t.e.canvasX, t.e.canvasY] + }); + var E = this.createDefaultNodeForSlot(b, m); + E ? console.log("node created", b) : console.error("node not in defaults", b); + }, _ = (b, m, E) => { + switch (b.content) { + case "Add Node": + N.onMenuAdd(b, m, E, y, function(T) { + i ? t.nodeFrom.connectByTypeInput(a, T, h) : t.nodeTo.connectByTypeOutput(a, T, h); + }); + break; + case "Add Subgraph Input": + v(this.adjustMouseEvent(E)); + break; + case "Add Subgraph Output": + g(this.adjustMouseEvent(E)); + break; + case "Search": + i ? this.showSearchBox(E, { node_from: t.nodeFrom, slotFrom: o, type_filter_in: h }) : this.showSearchBox(E, { node_to: t.nodeTo, slotFrom: o, type_filter_out: h }); + break; + default: + d(b.value); + break; + } + }; + var y = new X(l, { + event: t.e, + title: (o && o.name != "" ? o.name + (h ? " | " : "") : "") + (o && h ? h : ""), + callback: _ + }); + return !1; + } + getLinkMenuOptions(t) { + const i = this.graph.getNodeById(t.origin_id), n = this.graph.getNodeById(t.target_id); + let s = null; + i && i.outputs && i.outputs[t.origin_slot] && (s = i.outputs[t.origin_slot].type); + let r = null; + n && n.outputs && n.outputs[t.target_slot] && (r = n.inputs[t.target_slot].type); + const o = (p) => { + console.debug("node autoconnect"), !(!p.inputs || !p.inputs.length || !p.outputs || !p.outputs.length) && i.connectByTypeInput(t.origin_slot, p, s) && (p.connectByTypeInput(t.target_slot, n, r), p.pos[0] -= p.size[0] * 0.5); + }, a = (p, f, c, v, g) => { + N.onMenuAdd(p, f, c, v, o); + }, l = () => { + this.graph.removeLink(t.id); + }; + let h = [ + { + content: "Add Node", + has_submenu: !0, + callback: a + }, + j.SEPARATOR, + { + content: "Delete", + has_submenu: !0, + callback: l + }, + j.SEPARATOR + ]; + return this.graph.onGetLinkMenuOptions && (h = this.graph.onGetLinkMenuOptions(h, t)), i.getExtraLinkOptions && (h = i.getExtraLinkOptions(this, t, Y.OUTPUT, h)), n.getExtraLinkOptions && (h = n.getExtraLinkOptions(this, t, Y.INPUT, h)), h; + } + showLinkMenu(t, i) { + const n = this.getLinkMenuOptions(t); + return new X(n, { + event: i, + title: t.data != null ? t.data.constructor.name : null, + extra: t + }), !1; + } + /* + * Shows a popup for editing one of the LGraphNode.properties. + */ + showEditPropertyValue(t, i, n = {}) { + if (!t || t.properties[i] === void 0 || u.ignore_all_widget_events) + return; + var s = t.getPropertyInfo(i), r = s.type, o = ""; + if (r == "string" || r == "number" || r == "array" || r == "object") + if (s.multiline) { + let d = t.properties[i], _ = 5; + if (r !== "string") { + d = JSON.stringify(d, null, 2); + const y = (d.match(/\n/g) || "").length + 1; + _ = Te(y, 5, 10); + } + o = ""; + } else + o = ""; + else if ((r == "enum" || r == "combo") && s.values) { + o = ""; + } else if (r == "boolean" || r == "toggle") + o = ""; + else { + console.warn("unknown type: " + r); + return; + } + var h = this.createDialog( + "" + (s.label ? s.label : i) + "" + o + "", + n + ), p = null; + if ((r == "enum" || r == "combo") && s.values) + p = h.querySelector("select"), p.addEventListener("change", function(d) { + h.modified(), v(d.target.value); + }); + else if (r == "boolean" || r == "toggle") + p = h.querySelector("input"), p && p.addEventListener("click", function(d) { + h.modified(), v(!!p.checked); + }); + else if (s.multiline ? p = h.querySelector("textarea") : p = h.querySelector("input"), p) { + p.addEventListener("blur", function(_) { + this.focus(); + }); + let d = t.properties[i] !== void 0 ? t.properties[i] : ""; + if (r !== "string") { + let _ = null; + s.multiline && (_ = 2), d = JSON.stringify(d, null, _); + } + if (p.value = d, p.addEventListener("keydown", function(_) { + let y = !1; + _.keyCode == 27 ? (h.close(), y = !0) : _.keyCode == 13 && !s.multiline ? (c(), y = !0) : _.keyCode != 13 && h.modified(), y && (_.preventDefault(), _.stopPropagation()); + }), s.inputStyle) + for (const [_, y] of Object.entries(s.inputStyle)) + p.style[_] = y; + } + p && p.focus(); + const f = () => { + n.onclose && n.onclose(), h.close(), t.setDirtyCanvas(!0, !0); + }, c = () => { + r != "boolean" && r != "toggle" ? v(p.value) : f(); + }, v = (d) => { + s && s.values && s.values.constructor === Object && s.values[d] != null && (d = s.values[d]), typeof t.properties[i] == "number" && (d = Number(d)), (r == "array" || r == "object") && (d = JSON.parse(d)), t.setProperty(i, d), f(); + }; + var g = h.querySelector("button"); + return g.addEventListener("click", c), Ee(h), h; + } + // TODO refactor, theer are different dialog, some uses createDialog, some dont + createDialog(t, i = { checkForInput: !1, closeOnLeave: !0, closeOnLeave_checkModified: !0 }) { + var n = document.createElement("div"); + n.className = "graphdialog", n.innerHTML = t, n.is_modified = !1; + var s = this.canvas.getBoundingClientRect(), r = -20, o = -20; + if (s && (r -= s.left, o -= s.top), i.position ? (r = i.position[0], o = i.position[1]) : i.event ? (r = i.event.clientX, o = i.event.clientY) : (r += this.canvas.width * 0.5, o += this.canvas.height * 0.5), n.style.left = r + "px", n.style.top = o + "px", this.canvas.parentNode.appendChild(n), i.checkForInput) { + var a = n.querySelectorAll("input"), l = !1; + a && a.forEach(function(c) { + c.addEventListener("keydown", function(v) { + if (n.modified(), v.keyCode == 27) + n.close(); + else if (v.keyCode != 13) + return; + v.preventDefault(), v.stopPropagation(); + }), l || c.focus(); + }); + } + n.modified = function() { + n.is_modified = !0; + }, n.close = function() { + n.parentNode && n.parentNode.removeChild(n); + }; + var h = null, p = 0; + n.addEventListener("mouseleave", function(c) { + p || (i.closeOnLeave || u.dialog_close_on_mouse_leave) && !n.is_modified && u.dialog_close_on_mouse_leave && c.buttons === 0 && (h = setTimeout(n.close, u.dialog_close_on_mouse_leave_delay)); + }), n.addEventListener("mouseenter", function(c) { + (i.closeOnLeave || u.dialog_close_on_mouse_leave) && h && clearTimeout(h); + }); + var f = n.querySelectorAll("select"); + return f && f.forEach(function(c) { + c.addEventListener("click", function(v) { + p++; + }), c.addEventListener("blur", function(v) { + p = 0; + }), c.addEventListener("change", function(v) { + p = -1; + }); + }), n; + } + getCanvasMenuOptions() { + var t = null; + if (this.getMenuOptions ? t = this.getMenuOptions(this) : (t = [ + { + content: "Add Node", + has_submenu: !0, + callback: N.onMenuAdd + }, + { content: "Add Group", callback: N.onGroupAdd } + //{ content: "Arrange", callback: that.graph.arrange }, + //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } + ], this._graph_stack && this._graph_stack.length > 0 && t.push(j.SEPARATOR, { + content: "Close subgraph", + callback: this.closeSubgraph.bind(this) + })), this.getExtraMenuOptions) { + var i = this.getExtraMenuOptions(this, t); + i && (t = t.concat(i)); + } + return t; + } + getNodeMenuOptions(t) { + let i = []; + t.getMenuOptions ? i = t.getMenuOptions(this) : (i = [ + { + content: "Inputs", + has_submenu: !0, + disabled: !0, + callback: N.showMenuNodeOptionalInputs + }, + { + content: "Outputs", + has_submenu: !0, + disabled: !0, + callback: N.showMenuNodeOptionalOutputs + }, + j.SEPARATOR, + { + content: "Properties", + has_submenu: !0, + disabled: u.ignore_all_widget_events, + callback: N.onShowMenuNodeProperties + }, + j.SEPARATOR, + { + content: "Title", + value: { name: "title", type: "string" }, + callback: N.onShowPropertyEditor + }, + { + content: "Mode", + has_submenu: !0, + callback: N.onMenuNodeMode + } + ], t.resizable !== !1 && i.push({ + content: "Resize", + callback: N.onMenuResizeNode + }), i.push( + { + content: "Collapse", + callback: N.onMenuNodeCollapse + }, + { content: "Pin", callback: N.onMenuNodePin }, + { + content: "Colors", + has_submenu: !0, + callback: N.onMenuNodeColors + }, + { + content: "Shapes", + has_submenu: !0, + callback: N.onMenuNodeShapes + }, + j.SEPARATOR + )); + const n = t.getOptionalSlots(); + if (n && (n.inputs && n.inputs.length > 0 && typeof i[0] == "object" && (i[0].disabled = !1), n.outputs && n.outputs.length && typeof i[1] == "object" && (i[1].disabled = !1)), t.getExtraMenuOptions) { + var s = t.getExtraMenuOptions(this, i); + s && (s.push(j.SEPARATOR), i = s.concat(i)); + } + t.clonable !== !1 && i.push({ + content: "Clone", + callback: N.onMenuNodeClone + }), i.push({ + content: "To Subgraph", + callback: N.onMenuNodeToSubgraph + }); + let r = Object.values(this.selected_nodes || {}); + if (r.length || (r = [t]), r = r.filter((o) => !o.is($) && !o.is(Q)), i.push({ + content: "To Parent Graph", + disabled: !t.graph._is_subgraph || r.length === 0, + callback: N.onMenuNodeToParentGraph + }), t.graph._is_subgraph) { + const o = (p) => { + let f = 0; + const c = ve(p, (v) => v.id); + for (const v of p) + for (const g of v.iterateAllLinks()) { + if (c[g.origin_id] == null) + return 0; + c[g.target_id] == null && (f += 1); + } + return f; + }, a = (p) => { + let f = 0; + const c = ve(p, (v) => v.id); + for (const v of p) + for (const g of v.iterateAllLinks()) + if (c[g.origin_id] == null) + f += 1; + else if (c[g.target_id] == null) + return 0; + return f; + }, l = o(r); + i.push({ + content: "To Subgraph Input" + (l > 1 ? "s" : ""), + disabled: l === 0, + callback: N.onMenuNodeToSubgraphInputs + }); + const h = a(r); + i.push({ + content: "To Subgraph Output" + (h > 1 ? "s" : ""), + disabled: h === 0, + callback: N.onMenuNodeToSubgraphOutputs + }); + } + return i.push(j.SEPARATOR, { + content: "Remove", + disabled: !(t.removable !== !1 && !t.block_delete), + callback: N.onMenuNodeRemove + }), t.graph && t.graph.onGetNodeMenuOptions && (i = t.graph.onGetNodeMenuOptions(i, t)), i; + } + getGroupMenuOptions(t) { + var i = [ + { + content: "Title", + value: { name: "title", type: "string" }, + callback: N.onShowPropertyEditor + }, + { + content: "Color", + has_submenu: !0, + callback: N.onMenuNodeColors + }, + { + content: "Font size", + value: { name: "fontSize", type: "number" }, + callback: N.onShowPropertyEditor + }, + j.SEPARATOR, + { content: "Remove", callback: N.onMenuNodeRemove } + ]; + return i; + } + /** Called when mouse right click */ + processContextMenu(t, i) { + var n = N.active_canvas, s = n.getCanvasWindow(); + let r = i, o = null, a = null, l = null; + t != null && (l = t.item, t.type === "node" && (o = t.item), t.type === "link" && (a = t.item)); + let h = null; + var p = { + event: r, + extra: l + }; + o != null && (p.title = o.type); + let f = null; + o != null && (f = o.getSlotInPosition(r.canvasX, r.canvasY), N.active_node = o); + const c = (y) => { + const b = y.slot; + o.graph.beforeChange(), b.input ? o.removeInput(b.slot) : b.output && o.removeOutput(b.slot), o.graph.afterChange(); + }, v = (y) => { + var b = y.slot; + o.graph.beforeChange(), b.output ? o.disconnectOutput(b.slot) : b.input && o.disconnectInput(b.slot), o.graph.afterChange(); + }, g = (y) => { + var b = y.slot, m = b.input ? o.getInputInfo(b.slot) : o.getOutputInfo(b.slot), E = this.createDialog( + "Name", + p + ), T = E.querySelector("input"); + T && m && (T.value = m.label || ""); + var O = () => { + o.graph.beforeChange(), T.value && (m && (m.label = T.value), this.setDirty(!0)), E.close(), o.graph.afterChange(); + }; + E.querySelector("button").addEventListener("click", O), T.addEventListener("keydown", function(A) { + if (E.is_modified = !0, A.keyCode == 27) + E.close(); + else if (A.keyCode == 13) + O(); + else if (A.keyCode != 13 && A.target instanceof Element && A.target.localName != "textarea") + return; + A.preventDefault(), A.stopPropagation(); + }), T.focus(); + }; + if (f) { + if (h = [], o.getSlotMenuOptions) + h = o.getSlotMenuOptions(f); + else { + f && f.output && f.output.links && f.output.links.length && h.push({ content: "Disconnect Links", slot: f, callback: v }); + var d = f.input || f.output; + d.removable && h.push( + d.locked ? "Cannot remove" : { content: "Remove Slot", slot: f, callback: c } + ), d.nameLocked || h.push({ content: "Rename Slot", slot: f, callback: g }); + } + const y = (f.input ? f.input.type : f.output.type) || "*"; + p.title = J(y); + } else if (o) + h = this.getNodeMenuOptions(o); + else if (a) + h = this.getLinkMenuOptions(a); + else { + h = this.getCanvasMenuOptions(); + var _ = this.graph.getGroupOnPos( + r.canvasX, + r.canvasY + ); + _ && h.push(j.SEPARATOR, { + content: "Edit Group", + has_submenu: !0, + submenu: { + title: "Group", + extra: _, + options: this.getGroupMenuOptions(_) + } + }); + } + h && new X(h, p, s); + } + createPanel(t, i = {}) { + var n = i.window || window, s = document.createElement("div"); + if (s.className = "litegraph dialog", s.innerHTML = ` +
+
+ +`, s.header = s.querySelector(".dialog-header"), i.width && (s.style.width = i.width + (i.width.constructor === Number ? "px" : "")), i.height && (s.style.height = i.height + (i.height.constructor === Number ? "px" : "")), i.closable) { + var r = document.createElement("span"); + r.innerHTML = "✕", r.classList.add("close"), r.addEventListener("click", function() { + s.close(); + }), s.header.appendChild(r); + } + return i.onOpen && (s.onOpen = i.onOpen), i.onClose && (s.onClose = i.onClose), s.title_element = s.querySelector(".dialog-title"), s.title_element.innerText = t, s.content = s.querySelector(".dialog-content"), s.alt_content = s.querySelector(".dialog-alt-content"), s.footer = s.querySelector(".dialog-footer"), s.close = function() { + s.onClose && typeof s.onClose == "function" && s.onClose(), s.parentNode && s.parentNode.removeChild(s), this.parentNode && this.parentNode.removeChild(this); + }, s.toggleAltContent = function(o = !1) { + if (typeof o < "u") + var a = o ? "block" : "none", l = o ? "none" : "block"; + else + var a = s.alt_content.style.display != "block" ? "block" : "none", l = s.alt_content.style.display != "block" ? "none" : "block"; + s.alt_content.style.display = a, s.content.style.display = l; + }, s.toggleFooterVisibility = function(o = !1) { + if (typeof o < "u") + var a = o ? "block" : "none"; + else + var a = s.footer.style.display != "block" ? "block" : "none"; + s.footer.style.display = a; + }, s.clear = function() { + this.content.innerHTML = ""; + }, s.addHTML = function(o, a, l) { + var h = document.createElement("div"); + return a && (h.className = a), h.innerHTML = o, l ? s.footer.appendChild(h) : s.content.appendChild(h), h; + }, s.addButton = function(o, a, l) { + var h = document.createElement("button"); + return h.innerText = o, h.options = l, h.classList.add("btn"), h.addEventListener("click", a), s.footer.appendChild(h), h; + }, s.addSeparator = function() { + var o = document.createElement("div"); + return o.className = "separator", s.content.appendChild(o), o; + }, s.addWidget = function(o, a, l, h = {}, p) { + var f = String(l); + o = o.toLowerCase(), o == "number" && (f = l.toFixed(3)); + var c = document.createElement("div"); + c.className = "property", c.innerHTML = ""; + let v = c.querySelector(".property_name"); + v.innerText = h.label || a; + var g = c.querySelector(".property_value"); + if (g.innerText = f, c.dataset.property = a, c.dataset.type = h.type || o, c.options = h, c.value = l, o == "code") + c.addEventListener("click", function(_) { + s.inner_showCodePad(this.dataset.property); + }); + else if (o == "boolean") + c.classList.add("boolean"), l && c.classList.add("bool-on"), c.addEventListener("click", function() { + var _ = this.dataset.property; + this.value = !this.value, this.classList.toggle("bool-on"); + const y = this.querySelector(".property_value"); + y.innerText = this.value ? "true" : "false", d(_, this.value); + }); + else if (o == "string" || o == "number") + g.setAttribute("contenteditable", "true"), g.addEventListener("keydown", function(_) { + _.code == "Enter" && (o != "string" || !_.shiftKey) && (_.preventDefault(), this.blur()); + }), g.addEventListener("blur", function() { + let _ = this.innerText; + const y = this.parentNode; + var b = y.dataset.property, m = y.dataset.type; + m == "number" && (_ = Number(_)), d(b, _); + }); + else if ((o == "enum" || o == "combo") && "values" in h) { + var f = N.getPropertyPrintableValue(l, h.values); + g.innerText = f, g.addEventListener("click", function(y) { + let b = h.values || []; + typeof b == "function" && (console.error("Values by callback not supported in panel.addWidget!", b), b = []); + var E = this.parentNode.dataset.property, T = this; + let O = Array.from(b).map((M) => ({ content: M })); + new X(O, { + event: y, + className: "dark", + callback: A + }, n); + function A(M, L, B) { + return T.innerText = M.content, d(E, M.content), !1; + } + }); + } + s.content.appendChild(c); + function d(_, y) { + h.callback && h.callback(_, y, h), p && p(_, y, h); + } + return c; + }, s.onOpen && typeof s.onOpen == "function" && s.onOpen(), s; + } + checkPanels() { + if (this.canvas) + for (var t = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"), i = 0; i < t.length; ++i) { + var n = t[i]; + if (n.node && (n.node.graph || n.close(), n.node.graph != this.graph)) { + if (n.node.is(ne) && this.graph._is_subgraph && this.graph === n.node.subgraph) + continue; + n.close(); + } + } + } + closePanels() { + var t = document.querySelector("#node-panel"); + t && t.close(); + var t = document.querySelector("#option-panel"); + t && t.close(); + } +} +C.onShowPropertyEditor = function(e, t, i, n, s) { + var r = e.value, o = r.name, a = s[o], l = document.createElement("div"); + l.is_modified = !1, l.className = "graphdialog", l.innerHTML = "", l.close = function() { + l.parentNode && l.parentNode.removeChild(l); + }; + var h = l.querySelector(".name"); + h.innerText = o; + var p = l.querySelector(".value"); + if (p && (p.value = a, p.addEventListener("blur", function(E) { + this.focus(); + }), p.addEventListener("keydown", function(E) { + if (l.is_modified = !0, E.keyCode == 27) + l.close(); + else if (E.keyCode == 13) + _(); + else if (E.keyCode != 13 && E.target instanceof Element && E.target.localName != "textarea") + return; + E.preventDefault(), E.stopPropagation(); + }), r.inputStyle)) + for (const [E, T] of Object.entries(r.inputStyle)) + p.style[E] = T; + var f = N.active_canvas, c = f.canvas, v = c.getBoundingClientRect(), g = -20, d = -20; + v && (g -= v.left, d -= v.top), i ? (l.style.left = i.clientX + g + "px", l.style.top = i.clientY + d + "px") : (l.style.left = c.width * 0.5 + g + "px", l.style.top = c.height * 0.5 + d + "px"); + const _ = () => { + p && y(p.value); + }, y = (E) => { + r.type == "number" ? E = Number(E) : r.type == "boolean" && (E = !!E); + const T = s[o]; + s[o] = E, s.onJSPropertyChanged && s.onJSPropertyChanged(o, E, T) === !1 && (s[o] = T), l.parentNode && l.parentNode.removeChild(l), s.setDirtyCanvas(!0, !0); + }; + var b = l.querySelector("button"); + b.addEventListener("click", _), c.parentNode.appendChild(l), p && p.focus(); + var m = null; + l.addEventListener("mouseleave", function(E) { + u.dialog_close_on_mouse_leave && !l.is_modified && u.dialog_close_on_mouse_leave && E.buttons === 0 && (m = setTimeout(l.close, u.dialog_close_on_mouse_leave_delay)); + }), l.addEventListener("mouseenter", function(E) { + u.dialog_close_on_mouse_leave && m && clearTimeout(m); + }), Ee(l); +}; +C.onGroupAdd = function(e, t, i, n) { + var s = N.active_canvas; + s.getCanvasWindow(); + var r = new me(); + r.pos = s.convertEventToCanvasOffset(i), s.graph.addGroup(r); +}; +C.onMenuAdd = function(e, t, i, n, s) { + var r = N.active_canvas, o = r.getCanvasWindow(), a = r.graph; + if (!a) + return; + function l(h, p) { + var f = u.getNodeTypesCategories(r.filter || a.filter).filter(function(g) { + return g.startsWith(h); + }), c = []; + f.map(function(g) { + if (g) { + var d = new RegExp("^(" + h + ")"), _ = g.replace(d, "").split("/")[0], y = h === "" ? _ + "/" : h + _ + "/", b = _; + b.indexOf("::") != -1 && (b = b.split("::")[1]); + var m = c.findIndex(function(E) { + return E.value === y; + }); + m === -1 && c.push( + { + value: y, + content: b, + has_submenu: !0, + callback: function(E, T, O, A) { + l(E.value, A); + } + } + ); + } + }); + var v = u.getNodeTypesInCategory(h.slice(0, -1), r.filter || a.filter); + v.map(function(g) { + if (!g.hide_in_node_lists) { + var d = { + value: g.class, + content: g.title, + has_submenu: !1, + callback: function(_, y, b, m) { + var E = m.getFirstEvent(); + r.graph.beforeChange(); + var T = u.createNode(_.value); + T && (T.pos = r.convertEventToCanvasOffset(E), r.graph.add(T)), s && s(T), r.graph.afterChange(); + } + }; + c.push(d); + } + }), new X(c, { event: i, parentMenu: p }, o); + } + return l("", n), !1; +}; +C.showMenuNodeOptionalInputs = function(e, t, i, n, s) { + if (!s) + return; + var r = this, o = N.active_canvas, a = o.getCanvasWindow(); + let l = s.getOptionalSlots().inputs, h = []; + if (l) + for (let v = 0; v < l.length; v++) { + let g = l[v]; + if (!g) { + h.push(j.SEPARATOR); + continue; + } + let { name: d, type: _, options: y } = g; + y || (y = {}), y.label && (d = y.label), y.removable = !0; + var p = { content: d, value: g }; + _ == I.ACTION && (p.className = "event"), h.push(p); + } + if (s.onMenuNodeInputs) { + var f = s.onMenuNodeInputs(h); + f && (h = f); + } + if (!h.length) { + console.log("no input entries"); + return; + } + new X( + h, + { + event: i, + callback: c, + parentMenu: n, + node: s + }, + a + ); + function c(v, g, d, _) { + if (s && (v.callback && v.callback.call(r, s, v, d, _), v.value)) { + let y = v.value; + s.graph.beforeChange(), s.addInput(y.name, y.type, y.options), s.onNodeOptionalInputAdd && s.onNodeOptionalInputAdd(v.value), s.setDirtyCanvas(!0, !0), s.graph.afterChange(); + } + } + return !1; +}; +C.showMenuNodeOptionalOutputs = function(e, t, i, n, s) { + if (!s) + return; + var r = this, o = N.active_canvas, a = o.getCanvasWindow(), l = s.getOptionalSlots().outputs, h = []; + if (l) + for (var p = 0; p < l.length; p++) { + var f = l[p]; + if (!f) { + h.push(j.SEPARATOR); + continue; + } + let { name: d, type: _, options: y } = f; + if (!(s.flags && s.flags.skip_repeated_outputs && s.findOutputSlotIndexByName(f[0]) != -1)) { + y || (y = {}), y.label && (d = y.label), y.removable = !0; + var c = { content: d, value: [d, _, y] }; + _ == I.EVENT && (c.className = "event"), h.push(c); + } + } + if (this.onMenuNodeOutputs && (h = this.onMenuNodeOutputs(h)), u.do_add_triggers_slots && s.findOutputSlotIndexByName("onExecuted") == -1 && h.push({ content: "On Executed", value: ["onExecuted", I.EVENT, { nameLocked: !0 }], className: "event" }), s.onMenuNodeOutputs) { + var v = s.onMenuNodeOutputs(h); + v && (h = v); + } + if (!h.length) + return; + let g = function(d, _, y, b) { + if (s && (d.callback && d.callback.call(r, s, d, y, b), !!d.value)) { + var m = d.value[1]; + if (m && (m.constructor === Object || m.constructor === Array)) { + var E = []; + for (var T in m) + E.push({ content: T, value: m[T] }); + return new X(E, { + event: y, + callback: g, + parentMenu: n, + node: s + }), !1; + } else { + const O = d.value; + s.graph.beforeChange(), s.addOutput(O.name, O.type, O.options), s.onNodeOptionalOutputAdd && s.onNodeOptionalOutputAdd(d.value), s.setDirtyCanvas(!0, !0), s.graph.afterChange(); + } + } + }; + return new X( + h, + { + event: i, + callback: g, + parentMenu: n, + node: s + }, + a + ), !1; +}; +C.onMenuResizeNode = function(e, t, i, n, s) { + if (s) { + var r = function(l) { + l.size = l.computeSize(), l.onResize && l.onResize(l.size); + }, o = N.active_canvas; + if (!o.selected_nodes || Object.keys(o.selected_nodes).length <= 1) + r(s); + else + for (var a in o.selected_nodes) + r(o.selected_nodes[a]); + s.setDirtyCanvas(!0, !0); + } +}; +C.onShowMenuNodeProperties = function(e, t, i, n, s) { + if (!s || !s.properties) + return; + var r = N.active_canvas, o = r.getCanvasWindow(), a = []; + for (var l in s.properties) { + var h = s.properties[l] !== void 0 ? s.properties[l] : " "; + typeof h == "object" && (h = JSON.stringify(h)); + var p = s.getPropertyInfo(l); + (p.type == "enum" || p.type == "combo") && (h = N.getPropertyPrintableValue(h, p.values)), h = N.decodeHTML(h), a.push({ + content: "" + (p.label ? p.label : l) + "" + h + "", + value: l + }); + } + if (!a.length) + return; + new X( + a, + { + event: i, + callback: f, + parentMenu: n, + allow_html: !0, + node: s + }, + o + ); + function f(c, v, g, d) { + if (s) { + var _ = this.getBoundingClientRect(); + r.showEditPropertyValue(s, c.value, { + position: [_.left, _.top] + }); + } + } + return !1; +}; +C.onResizeNode = function(e, t, i, n, s) { + s && (s.size = s.computeSize(), s.setDirtyCanvas(!0, !0)); +}; +C.onMenuNodeCollapse = function(e, t, i, n, s) { + s.graph.beforeChange( + /*?*/ + ); + var r = function(l) { + l.collapse(); + }, o = N.active_canvas; + if (!o.selected_nodes || Object.keys(o.selected_nodes).length <= 1) + r(s); + else + for (var a in o.selected_nodes) + r(o.selected_nodes[a]); + s.graph.afterChange( + /*?*/ + ); +}; +C.onMenuNodePin = function(e, t, i, n, s) { + s.pin(); +}; +C.onMenuNodeMode = function(e, t, i, n, s) { + let r = Array.from(re).map((a) => ({ content: a })); + new X( + r, + { event: i, callback: o, parentMenu: n, node: s } + ); + function o(a) { + if (s) { + var l = Object.values(re).indexOf(a.content), h = function(c) { + l >= Z.ALWAYS && re[l] ? c.changeMode(l) : (console.warn("unexpected mode: " + a), c.changeMode(Z.ALWAYS)); + }, p = N.active_canvas; + if (!p.selected_nodes || Object.keys(p.selected_nodes).length <= 1) + h(s); + else + for (var f in p.selected_nodes) + h(p.selected_nodes[f]); + } + } + return !1; +}; +C.onMenuNodeColors = function(e, t, i, n, s) { + if (!s) + throw "no node for color"; + var r = []; + r.push({ + value: null, + content: "No color" + }); + for (let l in N.node_colors) { + var o = N.node_colors[l]; + let h = { + value: l, + content: "" + l + "" + }; + r.push(h); + } + new X(r, { + event: i, + callback: a, + parentMenu: n, + node: s, + allow_html: !0 + }); + function a(l) { + if (s) { + var h = l.value ? N.node_colors[l.value] : null, p = function(v) { + h ? v instanceof me ? v.color = h.groupcolor : (v.color = h.color, v.bgcolor = h.bgcolor) : (delete v.color, v instanceof ae && delete v.bgcolor); + }, f = N.active_canvas; + if (!f.selected_nodes || Object.keys(f.selected_nodes).length <= 1) + p(s); + else + for (var c in f.selected_nodes) + p(f.selected_nodes[c]); + s.setDirtyCanvas(!0, !0); + } + } + return !1; +}; +C.onMenuNodeShapes = function(e, t, i, n, s) { + if (!s) + throw "no node passed"; + const r = Array.from(Ie).map((a) => ({ content: a })); + new X(r, { + event: i, + callback: o, + parentMenu: n, + node: s + }); + function o(a) { + if (s) { + s.graph.beforeChange( + /*?*/ + ); + var l = function(f) { + f.shape = Ie.indexOf(a.content); + }, h = N.active_canvas; + if (!h.selected_nodes || Object.keys(h.selected_nodes).length <= 1) + l(s); + else + for (var p in h.selected_nodes) + l(h.selected_nodes[p]); + s.graph.afterChange( + /*?*/ + ), s.setDirtyCanvas(!0); + } + } + return !1; +}; +C.onMenuNodeRemove = function(e, t, i, n, s) { + if (!s) + throw "no node passed"; + var r = s.graph; + r.beforeChange(); + var o = function(h) { + h.removable !== !1 && r.remove(h); + }, a = N.active_canvas; + if (!a.selected_nodes || Object.keys(a.selected_nodes).length <= 1) + o(s); + else + for (var l in a.selected_nodes) + o(a.selected_nodes[l]); + r.afterChange(), s.setDirtyCanvas(!0, !0); +}; +C.onMenuNodeToSubgraph = function(e, t, i, n, s) { + var r = s.graph, o = N.active_canvas; + if (o) { + var a = Object.values(o.selected_nodes || {}); + a.length || (a = [s]); + var l = u.createNode("graph/subgraph", null, { constructorArgs: [null] }); + l.pos = s.pos.concat(), r.add(l), l.buildFromNodes(a), o.deselectAllNodes(), s.setDirtyCanvas(!0, !0); + } +}; +C.onMenuNodeToSubgraphInputs = function(e, t, i, n, s) { + var r = N.active_canvas; + if (!r) + return; + const o = s.graph._subgraph_node; + if (!s.graph._is_subgraph || !o) { + console.error("[To Subgraph Inputs] Current graph is not a subgraph!", s.graph); + return; + } + let a = Object.values(r.selected_nodes || {}); + a.length || (a = [s]), o.convertNodesToSubgraphInputs(a), r.deselectAllNodes(), s.setDirtyCanvas(!0, !0); +}; +C.onMenuNodeToSubgraphOutputs = function(e, t, i, n, s) { + var r = N.active_canvas; + if (!r) + return; + const o = s.graph._subgraph_node; + if (!s.graph._is_subgraph || !o) { + console.error("[To Subgraph Outputs] Current graph is not a subgraph!", s.graph); + return; + } + let a = Object.values(r.selected_nodes || {}); + a.length || (a = [s]), o.convertNodesToSubgraphOutputs(a), r.deselectAllNodes(), s.setDirtyCanvas(!0, !0); +}; +C.onMenuNodeToParentGraph = function(e, t, i, n, s) { + var r = N.active_canvas; + if (!r) + return; + const o = s.graph._subgraph_node; + if (!s.graph._is_subgraph || !o) { + console.error("[To Parent Graph] Current graph is not a subgraph!", s.graph); + return; + } + let a = Object.values(r.selected_nodes || {}); + a.length || (a = [s]), o.moveNodesToParentGraph(a), r.deselectAllNodes(), s.setDirtyCanvas(!0, !0); +}; +C.onMenuNodeClone = function(e, t, i, n, s) { + var r = N.active_canvas; + (!r.selected_nodes || Object.keys(r.selected_nodes).length <= 1) && r.selectNode(s), r.cloneSelection(); +}; +const ce = class { + constructor(e, t, i = {}) { + this.link_type_colors = {}, this.node_panel = null, this.options_panel = null, this.render_time = 0, this.allow_dragcanvas = !0, this.allow_dragnodes = !0, this.allow_interaction = !0, this.allow_reconnect_links = !0, this.allow_searchbox = !0, this.always_render_background = !1, this.background_image = ce.DEFAULT_BACKGROUND_IMAGE, this.block_click = !1, this.clear_background = !0, this.clear_background_color = "#222", this.connecting_pos = null, this.connecting_slot = null, this.connecting_input = null, this.connecting_output = null, this.connections_width = 3, this.current_node = null, this.drag_mode = !1, this.dragging_rectangle = null, this.ds = new Be(), this.editor_alpha = 1, this.filter = null, this.highquality_render = !0, this.skip_events = !1, this.last_mouse_position = [0, 0], this.last_click_position = [0, 0], this.last_click_position_offset = [0, 0], this.last_mouse_dragging = !1, this.links_render_mode = de.SPLINE_LINK, this.live_mode = !1, this.mouse = [0, 0], this.offset_mouse = [0, 0], this.graph_mouse = [0, 0], this.node_widget = null, this.maxZoom = null, this.minZoom = null, this.multi_select = !1, this.over_link_center = null, this.pause_rendering = !1, this.read_only = !1, this.render_canvas_border = !0, this.render_collapsed_slots = !0, this.render_connection_arrows = !1, this.render_connections_border = !0, this.render_connections_shadows = !1, this.render_connections = !0, this.render_curved_connections = !1, this.render_execution_order = !1, this.render_link_tooltip = !0, this.render_only_selected = !0, this.render_shadows = !0, this.render_title_colored = !0, this.render_subgraph_panels = !0, this.render_subgraph_stack_header = !0, this.round_radius = 8, this.set_canvas_dirty_on_mouse_event = !0, this.show_info = !0, this.use_gradients = !1, this.visible_links = [], this.zoom_modify_alpha = !0, this.pointer_is_down = !1, this.pointer_is_double = !1, this._highlight_input = null, this._highlight_input_slot = null, this._highlight_output = null, this._graph_stack = [], this._bg_img = null, this._pattern = null, this._pattern_img = null, this.search_box = null, this.prompt_box = null, this._events_binded = !1, this.resizing_node = null, typeof e == "string" && (e = document.querySelector(e)), this.skip_events = i.skip_events || !1, this.title_text_font = "" + u.NODE_TEXT_SIZE + "px Arial", this.inner_text_font = "normal " + u.NODE_SUBTEXT_SIZE + "px Arial", this.node_title_color = u.NODE_TITLE_COLOR, this.default_link_color = u.LINK_COLOR, this.link_type_colors = u.cloneObject(ce.DEFAULT_LINK_TYPE_COLORS), this.canvas_mouse = this.graph_mouse, this.visible_area = this.ds.visible_area, this.viewport = i.viewport || null, t && t.attachCanvas(this), this.setCanvas(e, i.skip_events), this.clear(), i.skip_render || this.startRendering(), this.autoresize = i.autoresize; + } + static getFileExtension(e) { + var t = e.indexOf("?"); + t != -1 && (e = e.substr(0, t)); + var i = e.lastIndexOf("."); + return i == -1 ? "" : e.substr(i + 1).toLowerCase(); + } + static decodeHTML(e) { + var t = document.createElement("div"); + return t.innerText = e, t.innerHTML; + } + static getPropertyPrintableValue(e, t) { + if (!t || t.constructor === Array) + return String(e); + if (t.constructor === Object) { + var i = ""; + for (var n in t) + if (t[n] == e) { + i = n; + break; + } + return String(e) + " (" + i + ")"; + } + } + get scale() { + return this.ds.scale; + } + set scale(e) { + this.ds.scale = e; + } + /** clears all the data inside */ + clear() { + this.frame = 0, this.last_draw_time = 0, this.render_time = 0, this.fps = 0, this.dragging_rectangle = 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_node = null, this.highlighted_links = {}, this.dragging_canvas = !1, this.dirty_canvas = !0, this.dirty_bgcanvas = !0, this.dirty_area = null, this.node_in_panel = null, this.node_widget = null, this.last_mouse = [0, 0], this.last_mouseclick = 0, this.pointer_is_down = !1, this.pointer_is_double = !1, this.onClear && this.onClear(); + } + /** assigns a graph, you can reassign graphs to the same canvas */ + setGraph(e, t = !1) { + if (this.graph != e) { + if (t || this.clear(), !e && this.graph) { + this.graph.detachCanvas(this); + return; + } + e.attachCanvas(this), this._graph_stack && (this._graph_stack = null), this.setDirty(!0, !0); + } + } + /** opens a graph contained inside a node in the current graph */ + openSubgraph(e) { + if (!e) + throw "graph cannot be null"; + if (this.graph == e) + throw "graph cannot be the same"; + if (this.clear(), this.graph) { + this._graph_stack || (this._graph_stack = []); + const i = [this.ds.offset[0], this.ds.offset[1]]; + this._graph_stack.push({ graph: this.graph, offset: i, scale: this.ds.scale }); + } + u.debug && (console.warn("SubGraph opened", e), console.warn("Graph inputs", e.inputs), console.warn("Graph outputs", e.outputs)), e.attachCanvas(this); + const t = [0, 0]; + if (e._nodes.length > 0) { + let i = Number.MAX_SAFE_INTEGER, n = 0, s = Number.MAX_SAFE_INTEGER, r = 0; + for (const o of e.iterateNodesInOrder()) + i = Math.min(o.pos[0], i), n = Math.max(o.pos[0] + o.size[0], n), s = Math.min(o.pos[1], s), r = Math.max(o.pos[1] + o.size[1], r); + t[0] = -(i + (n - i) / 2) + this.canvas.width / 2, t[1] = -(s + (r - s) / 2) + this.canvas.height / 2; + } + this.ds.offset = t, this.ds.scale = 1, this.checkPanels(), this.setDirty(!0, !0); + } + closeAllSubgraphs() { + for (; this._graph_stack && this._graph_stack.length > 0; ) + this.closeSubgraph(); + } + /** closes a subgraph contained inside a node */ + closeSubgraph() { + if (!(!this._graph_stack || this._graph_stack.length == 0)) { + var e = this.graph._subgraph_node, { graph: t, offset: i, scale: n } = this._graph_stack.pop(); + this.selected_nodes = {}, this.highlighted_links = {}, t.attachCanvas(this), this.setDirty(!0, !0), e && (this.centerOnNode(e), this.selectNodes([e])), this.ds.offset = i, this.ds.scale = n; + } + } + /** assigns a canvas */ + setCanvas(e, t = !1) { + if (e && typeof e == "string" && (e = document.getElementById(e), !e)) + throw "Error creating LiteGraph canvas: Canvas not found"; + if (e = e, e !== this.canvas && (!e && this.canvas && (t || this.unbindEvents()), this.canvas = e, this.ds.element = e, !!e)) { + if (e.className += " lgraphcanvas", e.data = this, e.tabIndex = 1, this.bgcanvas = null, this.bgcanvas || (this.bgcanvas = document.createElement("canvas"), this.bgcanvas.width = this.canvas.width, this.bgcanvas.height = this.canvas.height), e.getContext == null) + throw e.localName != "canvas" ? "Element supplied for LGraphCanvas must be a element, you passed a " + e.localName : "This browser doesn't support Canvas"; + t || this.bindEvents(), this.adjustCanvasForHiDPI(); + } + } + //used in some events to capture them + _doNothing(e) { + return e.preventDefault(), !1; + } + _doReturnTrue(e) { + return e.preventDefault(), !0; + } + /** binds mouse, keyboard, touch and drag events to the canvas */ + bindEvents() { + if (this._events_binded) { + console.warn("LGraphCanvas: events already binded"); + return; + } + var e = this.canvas, t = this.getCanvasWindow(), i = t.document; + this._mousedown_callback = this.processMouseDown.bind(this), this._mousewheel_callback = this.processMouseWheel.bind(this), this._mousemove_callback = this.processMouseMove.bind(this), this._mouseup_callback = this.processMouseUp.bind(this), u.pointerListenerAdd(e, "down", this._mousedown_callback, !0), e.addEventListener("mousewheel", this._mousewheel_callback, !1), u.pointerListenerAdd(e, "up", this._mouseup_callback, !0), u.pointerListenerAdd(e, "move", this._mousemove_callback), e.addEventListener("contextmenu", this._doNothing), e.addEventListener( + "DOMMouseScroll", + this._mousewheel_callback, + !1 + ), this._key_callback = this.processKey.bind(this), e.addEventListener("keydown", this._key_callback, !0), i.addEventListener("keyup", this._key_callback, !0), this._ondrop_callback = this.processDrop.bind(this), e.addEventListener("dragover", this._doNothing, !1), e.addEventListener("dragend", this._doNothing, !1), e.addEventListener("drop", this._ondrop_callback, !1), e.addEventListener("dragenter", this._doReturnTrue, !1), this._events_binded = !0; + } + /** unbinds mouse events from the canvas */ + unbindEvents() { + if (!this._events_binded) { + console.warn("LGraphCanvas: no events binded"); + return; + } + u.debug && console.log("pointerevents: unbindEvents"); + var e = this.getCanvasWindow(), t = e.document; + u.pointerListenerRemove(this.canvas, "move", this._mousedown_callback), u.pointerListenerRemove(this.canvas, "up", this._mousedown_callback), u.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), t.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 = !1; + } + /** + * 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() { + } + /** + * marks as dirty the canvas, this way it will be rendered again + * @param fg if the foreground canvas is dirty (the one containing the nodes) + * @param bg if the background canvas is dirty (the one containing the wires) + */ + setDirty(e = !1, t = !1) { + e && (this.dirty_canvas = !0), t && (this.dirty_bgcanvas = !0); + } + /** + * Used to attach the canvas in a popup + * @return the window where the canvas is attached (the DOM root node) + */ + getCanvasWindow() { + if (!this.canvas) + return window; + var e = this.canvas.ownerDocument; + return e.defaultView; + } + adjustCanvasForHiDPI(e) { + if (e || (e = window.devicePixelRatio), e == 1 || !this.canvas.parentNode) + return; + const t = this.canvas.parentNode.getBoundingClientRect(), { width: i, height: n } = t; + this.canvas.width = i * e, this.canvas.height = n * e, this.canvas.style.width = i + "px", this.canvas.style.height = n + "px", this.canvas.getContext("2d").scale(e, e); + } + /** starts rendering the content of the canvas when needed */ + startRendering() { + if (this.is_rendering) + return; + this.is_rendering = !0, e.call(this); + function e() { + this.pause_rendering || this.draw(); + var t = this.getCanvasWindow(); + this.is_rendering && t.requestAnimationFrame(e.bind(this)); + } + } + /** stops rendering the content of the canvas (to save resources) */ + stopRendering() { + this.is_rendering = !1; + } + //used to block future mouse events (because of im gui) + blockClick() { + this.block_click = !0, this.last_mouseclick = 0; + } + createDefaultNodeForSlot(e, t = {}) { + var i = this, n = t.nodeFrom && t.slotFrom !== null, s = !n && t.nodeTo && t.slotTo !== null; + if (t = { ...{ + position: [0, 0], + posAdd: [0, 0], + posSizeFix: [0, 0] + }, ...t }, !n && !s) + return console.warn("No data passed to createDefaultNodeForSlot " + t.nodeFrom + " " + t.slotFrom + " " + t.nodeTo + " " + t.slotTo), !1; + if (!e) + return console.warn("No type to createDefaultNodeForSlot"), !1; + var o = n ? t.nodeFrom : t.nodeTo, a = n ? t.slotFrom : t.slotTo, l = null; + switch (typeof a) { + case "string": + l = n ? o.findOutputSlotIndexByName(a) : o.findInputSlotIndexByName(a), a = n ? o.outputs[a] : o.inputs[a]; + break; + case "object": + l = n ? o.findOutputSlotIndexByName(a.name) : o.findInputSlotIndexByName(a.name); + break; + case "number": + l = a, a = n ? o.outputs[a] : o.inputs[a]; + break; + case "undefined": + default: + return console.warn("Cant get slot information " + a), !1; + } + a = a, (!a || !l) && console.warn("createDefaultNodeForSlot bad slotX " + a + " " + l); + var h = a.type == I.EVENT ? "_event_" : a.type, p = n ? u.slot_types_default_out : u.slot_types_default_in; + const f = p[h]; + if (p && f) { + a.link !== null || a.links && a.links.length > 0; + let _ = null; + if (Array.isArray(f)) { + for (var c in f) + if (e == p[h][c] || e == "AUTO") { + _ = p[h][c], u.debug && console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: " + e); + break; + } + } else + throw new Error(`Invalid default slot specifier, must be an array: ${f}`); + if (_) { + var v = null; + typeof _ == "object" && _.node && (v = _, _ = _.node); + var g = u.createNode(_); + if (g) { + if (v) { + if (v.properties) + for (var d in v.properties) + g.addProperty(d, v.properties[d]); + if (v.inputs) { + g.inputs = []; + for (var d in v.inputs) + g.addOutput( + v.inputs[d][0], + v.inputs[d][1] + ); + } + if (v.outputs) { + g.outputs = []; + for (var d in v.outputs) + g.addOutput( + v.outputs[d][0], + v.outputs[d][1] + ); + } + v.title && (g.title = v.title), v.json && g.configure(v.json); + } + console.warn("PLACING", g.type, t); + const y = t.position[0] + t.posAdd[0] + (t.posSizeFix[0] ? t.posSizeFix[0] * g.size[0] : 0), b = t.position[1] + t.posAdd[1] + (t.posSizeFix[1] ? t.posSizeFix[1] * g.size[1] : 0), m = [y, b]; + return i.graph.add(g, { pos: m }), n ? t.nodeFrom.connectByTypeInput(l, g, h) : t.nodeTo.connectByTypeOutput(l, g, h), n && s && console.debug("connecting in between"), !0; + } else + console.log("failed creating " + _); + } + } + return !1; + } + /** returns true if a position (in graph space) is on top of a node little corner box */ + isOverNodeBox(e, t, i) { + var n = u.NODE_TITLE_HEIGHT; + return !!u.isInsideRectangle( + t, + i, + e.pos[0] + 2, + e.pos[1] + 2 - n, + n - 4, + n - 4 + ); + } + /** returns slot index if a position (in graph space) is on top of a node input slot */ + isOverNodeInput(e, t, i, n) { + if (e.inputs) + for (var s = 0, r = e.inputs.length; s < r; ++s) { + var o = e.getConnectionPos(!0, s), a = !1; + if (e.horizontal ? a = u.isInsideRectangle( + t, + i, + o[0] - 5, + o[1] - 10, + 10, + 20 + ) : a = u.isInsideRectangle( + t, + i, + o[0] - 10, + o[1] - 5, + 40, + 10 + ), a) + return n && (n[0] = o[0], n[1] = o[1]), s; + } + return -1; + } + /** + * returns the INDEX if a position (in graph space) is on top of a node output slot + * @method isOverNodeOuput + **/ + isOverNodeOutput(e, t, i, n) { + if (e.outputs) + for (var s = 0, r = e.outputs.length; s < r; ++s) { + e.outputs[s]; + var o = e.getConnectionPos(!1, s), a = !1; + if (e.horizontal ? a = u.isInsideRectangle( + t, + i, + o[0] - 5, + o[1] - 10, + 10, + 20 + ) : a = u.isInsideRectangle( + t, + i, + o[0] - 10, + o[1] - 5, + 40, + 10 + ), a) + return n && (n[0] = o[0], n[1] = o[1]), s; + } + return -1; + } + findLinkCenterAtPos(e, t) { + for (let i = 0; i < this.visible_links.length; ++i) { + const n = this.visible_links[i]; + if (this.graph && this.graph.links[n.id] == null) + continue; + const s = n._pos; + if (!(!s || e < s[0] - 4 || e > s[0] + 4 || t < s[1] - 4 || t > s[1] + 4)) + return n; + } + return null; + } + /** process a key event */ + processKey(e) { + if (!this.graph) + return; + var t = !1; + if (u.debug && console.log("processKey", e), e.target instanceof Element && e.target.localName == "input") + return; + const i = this.allow_interaction && !this.read_only; + if (e.type == "keydown") { + if (e.keyCode == 32 && !(e.metaKey || e.ctrlKey || e.shiftKey) && (this.dragging_canvas = !0, t = !0), e.keyCode == 27 && !(e.metaKey || e.ctrlKey || e.shiftKey) && (this.node_panel && this.node_panel.close(), this.options_panel && this.options_panel.close(), t = !0), i && (e.keyCode == 65 && e.ctrlKey && (this.selectNodes(), t = !0), e.code == "KeyX" && (e.metaKey || e.ctrlKey) && !e.shiftKey && this.selected_nodes && (this.cutToClipboard(), t = !0), e.code == "KeyC" && (e.metaKey || e.ctrlKey) && !e.shiftKey && this.selected_nodes && (this.copyToClipboard(), t = !0), e.code == "KeyV" && (e.metaKey || e.ctrlKey) && !e.shiftKey && this.pasteFromClipboard(), e.code == "KeyD" && (e.metaKey || e.ctrlKey) && !e.shiftKey && (this.cloneSelection(), t = !0), (e.keyCode == 46 || e.keyCode == 8) && e.target instanceof Element && e.target.localName != "input" && e.target.localName != "textarea" && (this.deleteSelectedNodes(), t = !0), this.selected_nodes)) + for (var n in this.selected_nodes) + this.selected_nodes[n].onKeyDown && this.selected_nodes[n].onKeyDown(e); + } else if (e.type == "keyup" && (e.keyCode == 32 && (this.dragging_canvas = !1), i && this.selected_nodes)) + for (var n in this.selected_nodes) + this.selected_nodes[n].onKeyUp && this.selected_nodes[n].onKeyUp(e); + if (this.graph.change(), t) + return e.preventDefault(), e.stopImmediatePropagation(), !1; + } + cutToClipboard() { + this.copyToClipboard(), this.deleteSelectedNodes(); + } + copyToClipboard() { + var e = { + nodes: [], + nodeCloneData: {}, + links: [] + }, t = 0, i = []; + for (var n in this.selected_nodes) { + var s = this.selected_nodes[n]; + s._relative_id = t, i.push(s), t += 1; + } + for (let h = 0; h < i.length; ++h) { + let p = i[h]; + if (!p.clonable) + continue; + const f = { forNode: {} }; + let c = p.clone(f); + if (!c) { + console.warn("node type not found: " + p.type); + continue; + } + if (e.nodes.push(c.serialize()), e.nodeCloneData[c.id] = { + prevNodeID: p.id, + cloneData: f + }, p.inputs && p.inputs.length) + for (var r = 0; r < p.inputs.length; ++r) { + var o = p.inputs[r]; + if (!(!o || o.link == null)) { + var a = this.graph.links[o.link]; + if (a) { + var l = this.graph.getNodeById( + a.origin_id + ); + !l || !this.selected_nodes[l.id] || !this.selected_nodes[l.id].clonable || e.links.push([ + l._relative_id, + a.origin_slot, + //j, + p._relative_id, + a.target_slot + ]); + } + } + } + } + localStorage.setItem( + "litegrapheditor_clipboard", + JSON.stringify(e) + ); + } + pasteFromClipboard() { + var e = localStorage.getItem("litegrapheditor_clipboard"); + if (e) { + this.graph.beforeChange(); + for (var t = JSON.parse(e), i = null, n = null, s = 0; s < t.nodes.length; ++s) + i ? (i[0] > t.nodes[s].pos[0] && (i[0] = t.nodes[s].pos[0], n[0] = s), i[1] > t.nodes[s].pos[1] && (i[1] = t.nodes[s].pos[1], n[1] = s)) : (i = [t.nodes[s].pos[0], t.nodes[s].pos[1]], n = [s, s]); + for (var r = [], s = 0; s < t.nodes.length; ++s) { + var o = t.nodes[s], a = u.createNode(o.type); + if (a) { + a.configure(o), a.pos[0] += this.graph_mouse[0] - i[0], a.pos[1] += this.graph_mouse[1] - i[1]; + const { cloneData: c, prevNodeID: v } = t.nodeCloneData[a.id]; + this.graph.add(a, { doProcessChange: !1, addedBy: "paste", prevNodeID: v, cloneData: c }), r.push(a); + } + } + for (var s = 0; s < t.links.length; ++s) { + var l = t.links[s], h = r[l[0]], p = r[l[2]]; + h && p ? h.connect(l[1], p, l[3]) : console.warn("Warning, nodes missing on pasting"); + } + this.selectNodes(r), this.graph.afterChange(); + } + } + cloneSelection() { + if (!this.selected_nodes || Object.keys(this.selected_nodes).length === 0) + return; + this.graph.beforeChange(); + const e = {}, t = [], i = {}; + for (const r of Object.values(this.selected_nodes)) + for (const o of r.iterateAllLinks()) + this.selected_nodes[o.origin_id] && this.selected_nodes[o.target_id] && t.push(o); + const n = function(r) { + if (r.clonable == !1) + return; + const o = r.id, a = { forNode: {} }, l = r.clone(a); + l && (i[o] = l, l.pos = [r.pos[0] + 5, r.pos[1] + 5], r.graph.add(l, { addedBy: "cloneSelection", prevNodeID: o, prevNode: r, cloneData: a }), e[l.id] = l); + }; + for (var s in this.selected_nodes) + n(this.selected_nodes[s]); + for (const r of t) { + const o = i[r.origin_id], a = i[r.target_id]; + o && a && o.connect(r.origin_slot, a, r.target_slot); + } + Object.keys(e).length && this.selectNodes(Object.values(e)), this.graph.afterChange(), this.setDirty(!0, !0); + } + processDrop(e) { + let t = e; + t.preventDefault(), this.adjustMouseEvent(t); + var i = t.clientX, n = t.clientY, s = !this.viewport || this.viewport && i >= this.viewport[0] && i < this.viewport[0] + this.viewport[2] && n >= this.viewport[1] && n < this.viewport[1] + this.viewport[3]; + if (s) { + var r = [t.canvasX, t.canvasY], o = this.graph ? this.graph.getNodeOnPos(r[0], r[1]) : null; + if (!o) { + var a = null; + this.onDropItem && (a = this.onDropItem(t)), a || this.checkDropItem(t); + return; + } + if (o.onDropFile || o.onDropData) { + var l = t.dataTransfer.files; + if (l && l.length) + for (var h = 0; h < l.length; h++) { + var p = t.dataTransfer.files[0], f = p.name; + if (ce.getFileExtension(f), o.onDropFile && o.onDropFile(p), o.onDropData) { + var c = new FileReader(); + c.onload = function(g) { + var d = g.target.result; + o.onDropData(d, f, p); + }; + var v = p.type.split("/")[0]; + v == "text" || v == "" ? c.readAsText(p) : v == "image" ? c.readAsDataURL(p) : c.readAsArrayBuffer(p); + } + } + } + return !!(o.onDropItem && o.onDropItem(t) || this.onDropItem && this.onDropItem(t)); + } + } + checkDropItem(e) { + let t = e; + if (t.dataTransfer.files.length) { + var i = t.dataTransfer.files[0], n = ce.getFileExtension(i.name).toLowerCase(), s = u.node_types_by_file_extension[n]; + if (s) { + this.graph.beforeChange(); + var r = u.createNode(s.type); + r.pos = [t.canvasX, t.canvasY], this.graph.add(r), r.onDropFile && r.onDropFile(i), this.graph.afterChange(); + } + } + } + processNodeDblClicked(e) { + this.onShowNodePanel ? this.onShowNodePanel(e) : this.showShowNodePanel(e), this.onNodeDblClicked && this.onNodeDblClicked(e), this.setDirty(!0); + } + processNodeSelected(e, t) { + this.selectNode(e, t && (t.shiftKey || t.ctrlKey || this.multi_select)), this.onNodeSelected && this.onNodeSelected(e); + } + /** selects a given node (or adds it to the current selection) */ + selectNode(e, t = !1) { + e == null ? this.deselectAllNodes() : this.selectNodes([e], t); + } + /** selects several nodes (or adds them to the current selection) */ + selectNodes(e, t = !1) { + t || this.deselectAllNodes(), e = e || this.graph._nodes, typeof e == "string" && (e = [e]); + for (var i in e) { + var n = e[i]; + if (n.is_selected) { + this.deselectNode(n); + continue; + } + if (!n.is_selected && n.onSelected && n.onSelected(), n.is_selected = !0, this.selected_nodes[n.id] = n, n.inputs) + for (var s = 0; s < n.inputs.length; ++s) + this.highlighted_links[n.inputs[s].link] = !0; + if (n.outputs) + for (var s = 0; s < n.outputs.length; ++s) { + var r = n.outputs[s]; + if (r.links) + for (var o = 0; o < r.links.length; ++o) + this.highlighted_links[r.links[o]] = !0; + } + } + this.onSelectionChange && this.onSelectionChange(this.selected_nodes), this.setDirty(!0); + } + /** removes a node from the current selection */ + deselectNode(e) { + if (e.is_selected) { + if (e.onDeselected && e.onDeselected(), e.is_selected = !1, this.onNodeDeselected && this.onNodeDeselected(e), e.inputs) + for (var t = 0; t < e.inputs.length; ++t) + delete this.highlighted_links[e.inputs[t].link]; + if (e.outputs) + for (var t = 0; t < e.outputs.length; ++t) { + var i = e.outputs[t]; + if (i.links) + for (var n = 0; n < i.links.length; ++n) + delete this.highlighted_links[i.links[n]]; + } + } + } + /** removes all nodes from the current selection */ + deselectAllNodes() { + if (this.graph) { + for (var e = this.graph._nodes, t = 0, i = e.length; t < i; ++t) { + var n = e[t]; + n.is_selected && (n.onDeselected && n.onDeselected(), n.is_selected = !1, this.onNodeDeselected && this.onNodeDeselected(n)); + } + this.selected_nodes = {}, this.current_node = null, this.highlighted_links = {}, this.onSelectionChange && this.onSelectionChange(this.selected_nodes), this.setDirty(!0); + } + } + /** deletes all nodes in the current selection from the graph */ + deleteSelectedNodes() { + this.graph.beforeChange(); + for (var e in this.selected_nodes) { + var t = this.selected_nodes[e]; + if (!t.block_delete) { + if (t.inputs && t.inputs.length && t.outputs && t.outputs.length && u.isValidConnection(t.inputs[0].type, t.outputs[0].type) && t.inputs[0].link && t.outputs[0].links && t.outputs[0].links.length) { + var i = t.graph.links[t.inputs[0].link], n = t.graph.links[t.outputs[0].links[0]], s = t.getInputNode(0), r = t.getOutputNodes(0)[0]; + s && r && s.connect(i.origin_slot, r, n.target_slot); + } + this.graph.remove(t), this.onNodeDeselected && this.onNodeDeselected(t); + } + } + this.selected_nodes = {}, this.current_node = null, this.highlighted_links = {}, this.setDirty(!0), this.graph.afterChange(); + } + /** centers the camera on a given node */ + centerOnNode(e) { + this.ds.offset[0] = -e.pos[0] - e.size[0] * 0.5 + this.canvas.width * 0.5 / this.ds.scale, this.ds.offset[1] = -e.pos[1] - e.size[1] * 0.5 + this.canvas.height * 0.5 / this.ds.scale, this.setDirty(!0, !0); + } + /** + * adds some useful properties to a mouse event, like the position in graph coordinates + * @method adjustMouseEvent + **/ + adjustMouseEvent(e) { + let t = e; + var i = 0, n = 0; + if (this.canvas) { + var s = this.canvas.getBoundingClientRect(); + i = t.clientX - s.left, n = t.clientY - s.top; + } else + i = t.clientX, n = t.clientY; + return this.last_mouse_position[0] = i, this.last_mouse_position[1] = n, t.canvasX = i / this.ds.scale - this.ds.offset[0], t.canvasY = n / this.ds.scale - this.ds.offset[1], t; + } + /** process an event on widgets */ + processNodeWidgets(e, t, i, n) { + if (!e.widgets || !e.widgets.length || u.ignore_all_widget_events) + return null; + for (var s = t[0] - e.pos[0], r = t[1] - e.pos[1], o = e.size[0], a = this, l = this.getCanvasWindow(), h = 0; h < e.widgets.length; ++h) { + var p = e.widgets[h]; + if (!(!p || p.disabled)) { + var f = p.computeSize ? p.computeSize(o)[1] : u.NODE_WIDGET_HEIGHT, c = p.width || o; + if (!(p != n && (s < 6 || s > c - 12 || r < p.last_y || r > p.last_y + f || p.last_y === void 0))) { + var v = p.value; + switch (p.type) { + case "button": + i.type === u.pointerevents_method + "down" && (p.callback && setTimeout(function() { + p.callback(p, a, e, t, i); + }, 20), p.clicked = !0, this.dirty_canvas = !0); + break; + case "slider": + p.options.max - p.options.min; + var g = Te((s - 15) / (c - 30), 0, 1); + p.value = p.options.min + (p.options.max - p.options.min) * g, p.callback && setTimeout(function() { + E(p, p.value); + }, 20), this.dirty_canvas = !0; + break; + case "number": + case "combo": + var v = p.value; + if (i.type == u.pointerevents_method + "move" && p.type == "number") + i.deltaX && (p.value += i.deltaX * (p.options.step || 0.1)), p.options.min != null && p.value < p.options.min && (p.value = p.options.min), p.options.max != null && p.value > p.options.max && (p.value = p.options.max); + else if (i.type == u.pointerevents_method + "down") { + var d = p.options.values; + if (d && typeof d == "function") { + let O = p.options.values; + d = O(p, e); + } + var _ = null; + p.type != "number" && (_ = Array.isArray(d) ? d : Object.keys(d)); + var y = s < 40 ? -1 : s > c - 40 ? 1 : 0; + if (p.type == "number") + p.value += y * (p.options.step || 0.1), p.options.min != null && p.value < p.options.min && (p.value = p.options.min), p.options.max != null && p.value > p.options.max && (p.value = p.options.max); + else if (y) { + var b = -1; + this.last_mouseclick = 0, d.constructor === Object ? b = _.indexOf(String(p.value)) + y : b = _.indexOf(p.value) + y, b >= _.length && (b = _.length - 1), b < 0 && (b = 0), Array.isArray(d) ? p.value = d[b] : p.value = b; + } else { + let O = function(M, L, B) { + let G = M.content; + return d != _ && (G = m.indexOf(G)), this.value = G, E(this, G), a.dirty_canvas = !0, !1; + }; + var m = d != _ ? Object.values(d) : d; + let A = Array.from(m).map((M) => ({ content: M })); + new X( + A, + { + scale: Math.max(1, this.ds.scale), + event: i, + className: "dark", + callback: O.bind(p) + }, + l + ); + } + } else if (i.type == u.pointerevents_method + "up" && p.type == "number") { + var y = s < 40 ? -1 : s > c - 40 ? 1 : 0; + i.click_time < 200 && y == 0 && this.prompt( + "Value", + p.value, + function(A) { + this.value = Number(A), E(this, this.value); + }.bind(p), + i + ); + } + v != p.value && setTimeout( + function() { + E(this, this.value); + }.bind(p), + 20 + ), this.dirty_canvas = !0; + break; + case "toggle": + i.type == u.pointerevents_method + "down" && (p.value = !p.value, setTimeout(function() { + E(p, p.value); + }, 20)); + break; + case "string": + case "text": + i.type == u.pointerevents_method + "down" && this.prompt( + "Value", + p.value, + function(O) { + this.value = O, E(this, O); + }.bind(p), + i, + p.options ? p.options.multiline : !1, + p.options.inputStyle + ); + break; + default: + p.mouse && (this.dirty_canvas = p.mouse(i, [s, r], e)); + break; + } + return v != p.value && (e.onWidgetChanged && e.onWidgetChanged(p, v), e.graph._version++), p; + } + } + } + function E(T, O) { + T.value = O, T.options && T.options.property && e.properties[T.options.property] !== void 0 && e.setProperty(T.options.property, O), T.callback && T.callback(T.value, a, e, t, i); + } + return null; + } + adjustNodesSize() { + for (var e = this.graph._nodes, t = 0; t < e.length; ++t) + e[t].size = e[t].computeSize(); + this.setDirty(!0, !0); + } + /** resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode */ + resize(e, t) { + if (!e && !t) { + var i = this.canvas.parentNode; + e = i.offsetWidth, t = i.offsetHeight; + } + this.canvas.width == e && this.canvas.height == t || (this.canvas.width = e, this.canvas.height = t, this.bgcanvas.width = this.canvas.width, this.bgcanvas.height = this.canvas.height, this.adjustCanvasForHiDPI(), this.setDirty(!0, !0)); + } + isAreaClicked(e, t, i, n, s) { + var r = this.offset_mouse; + u.isInsideRectangle(r[0], r[1], e, t, i, n), r = this.last_click_position; + var o = r && u.isInsideRectangle(r[0], r[1], e, t, i, n), a = o && !this.block_click; + return o && s && this.blockClick(), a; + } + /** + * 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(e) { + if (!e) { + this.live_mode = !this.live_mode, this.dirty_canvas = !0, this.dirty_bgcanvas = !0; + return; + } + var t = this, i = this.live_mode ? 1.1 : 0.9; + this.live_mode && (this.live_mode = !1, this.editor_alpha = 0.1); + var n = setInterval(function() { + t.editor_alpha *= i, t.dirty_canvas = !0, t.dirty_bgcanvas = !0, i < 1 && t.editor_alpha < 0.01 && (clearInterval(n), i < 1 && (t.live_mode = !0)), i > 1 && t.editor_alpha > 0.99 && (clearInterval(n), t.editor_alpha = 1); + }, 1); + } + onNodeSelectionChange() { + } + touchHandler(e) { + } + convertOffsetToCanvas(e) { + return this.ds.convertOffsetToCanvas(e); + } + convertCanvasToOffset(e, t = [0, 0]) { + return this.ds.convertCanvasToOffset(e, t); + } + /** converts event coordinates from canvas2D to graph coordinates */ + convertEventToCanvasOffset(e) { + var t = this.canvas.getBoundingClientRect(); + return this.convertCanvasToOffset([ + e.clientX - t.left, + e.clientY - t.top + ]); + } + addGraphInputNode(e, t, i) { + const n = this.graph.findNodesByClass($).find((o) => o.properties.name === t); + if (n) { + this.selectNodes([n]); + return; + } + (!i || i === "") && (i = "*"); + const s = [ + this.canvas.width * 0.25 / this.ds.scale - this.ds.offset[0], + this.canvas.height * 0.5 / this.ds.scale - this.ds.offset[1] + ]; + this.graph.beforeChange(); + const r = e.addGraphInput(t, i, s); + if (r) { + const o = r.innerNode; + this.selectNodes([o]), this.graph.afterChange(); + } else + console.error("graph input node not found:", i); + } + addGraphOutputNode(e, t, i) { + const n = this.graph.findNodesByClass(Q).find((o) => o.properties.name === t); + if (n) { + this.selectNodes([n]); + return; + } + (!i || i === "") && (i = "*"); + const s = [ + this.canvas.width * 0.75 / this.ds.scale - this.ds.offset[0], + this.canvas.height * 0.5 / this.ds.scale - this.ds.offset[1] + ]; + this.graph.beforeChange(); + const r = e.addGraphOutput(t, i, s); + if (r) { + const o = r.innerNode; + this.selectNodes([o]), this.graph.afterChange(); + } else + console.error("graph output node not found:", i); + } + getCanvasMenuOptions() { + return C.prototype.getCanvasMenuOptions.apply(this, arguments); + } + getNodeMenuOptions(e) { + return C.prototype.getNodeMenuOptions.apply(this, arguments); + } + getLinkMenuOptions(e) { + return C.prototype.getLinkMenuOptions.apply(this, arguments); + } + getGroupMenuOptions(e) { + return C.prototype.getGroupMenuOptions.apply(this, arguments); + } + checkPanels() { + C.prototype.checkPanels.apply(this, arguments); + } + closePanels() { + C.prototype.closePanels.apply(this, arguments); + } + createDialog(e, t) { + return C.prototype.createDialog.apply(this, arguments); + } + createPanel(e, t = {}) { + return C.prototype.createPanel.apply(this, arguments); + } + showSearchBox(e, t = {}) { + return C.prototype.showSearchBox.apply(this, arguments); + } + prompt(e = "", t, i, n, s = !1, r = null) { + return C.prototype.prompt.apply(this, arguments); + } + showConnectionMenu(e = {}) { + return C.prototype.showConnectionMenu.apply(this, arguments); + } + showLinkMenu(e, t) { + return C.prototype.showLinkMenu.apply(this, arguments); + } + showEditPropertyValue(e, t, i) { + return C.prototype.showEditPropertyValue.apply(this, arguments); + } + showShowNodePanel(e) { + C.prototype.showShowNodePanel.apply(this, arguments); + } + showSubgraphPropertiesDialog(e) { + return C.prototype.showSubgraphPropertiesDialog.apply(this, arguments); + } + showSubgraphPropertiesDialogRight(e) { + return C.prototype.showSubgraphPropertiesDialogRight.apply(this, arguments); + } + processContextMenu(e, t) { + C.prototype.processContextMenu.apply(this, arguments); + } + /* + * Events + */ + processMouseMove(e) { + return ge.prototype.processMouseMove.apply(this, arguments); + } + processMouseDown(e) { + return ge.prototype.processMouseDown.apply(this, arguments); + } + processMouseUp(e) { + return ge.prototype.processMouseUp.apply(this, arguments); + } + processMouseWheel(e) { + return ge.prototype.processMouseWheel.apply(this, arguments); + } + /* + * Rendering + */ + setZoom(e, t) { + U.prototype.setZoom.apply(this, arguments); + } + bringToFront(e) { + U.prototype.bringToFront.apply(this, arguments); + } + sendToBack(e) { + U.prototype.sendToBack.apply(this, arguments); + } + computeVisibleNodes(e, t = []) { + return U.prototype.computeVisibleNodes.apply(this, arguments); + } + draw(e = !1, t = !1) { + U.prototype.draw.apply(this, arguments); + } + drawFrontCanvas() { + U.prototype.drawFrontCanvas.apply(this, arguments); + } + drawSubgraphPanel(e) { + U.prototype.drawSubgraphPanel.apply(this, arguments); + } + drawSubgraphPanelLeft(e, t, i) { + U.prototype.drawSubgraphPanelLeft.apply(this, arguments); + } + drawSubgraphPanelRight(e, t, i) { + U.prototype.drawSubgraphPanelRight.apply(this, arguments); + } + drawButton(e, t, i, n, s, r = u.NODE_DEFAULT_COLOR, o = "#555", a = u.NODE_TEXT_COLOR, l = !0) { + return U.prototype.drawButton.apply(this, arguments); + } + drawBackCanvas() { + U.prototype.drawBackCanvas.apply(this, arguments); + } + renderInfo(e, t = 10, i) { + U.prototype.renderInfo.apply(this, arguments); + } + drawNode(e, t) { + U.prototype.drawNode.apply(this, arguments); + } + drawLinkTooltip(e, t) { + U.prototype.drawLinkTooltip.apply(this, arguments); + } + drawNodeShape(e, t, i, n, s, r, o) { + U.prototype.drawNodeShape.apply(this, arguments); + } + drawConnections(e) { + U.prototype.drawConnections.apply(this, arguments); + } + renderLink(e, t, i, n, s, r, o, a, l, h) { + U.prototype.renderLink.apply(this, arguments); + } + computeConnectionPoint(e, t, i, n = w.RIGHT, s = w.LEFT) { + return U.prototype.computeConnectionPoint.apply(this, arguments); + } + drawExecutionOrder(e) { + U.prototype.drawExecutionOrder.apply(this, arguments); + } + drawNodeWidgets(e, t, i, n) { + U.prototype.drawNodeWidgets.apply(this, arguments); + } + drawGroups(e, t) { + U.prototype.drawGroups.apply(this, arguments); + } + /* + * ComfyUI Extension + */ + updateBackground(e, t) { + this._bg_img = new Image(), this._bg_img.name = e, this._bg_img.src = e, this._bg_img.onload = () => { + this.draw(!0, !0); + }, this.background_image = e, this.clear_background = !0, this.clear_background_color = t, this._pattern = null; + } +}; +let N = ce; +N.DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; +N.node_colors = { + red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" }, + brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, + green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" }, + blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" }, + pale_blue: { color: "#2a363b", bgcolor: "#3f5159", groupcolor: "#3f789e" }, + cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" }, + purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, + yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, + black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } +}; +N.DEFAULT_LINK_TYPE_COLORS = { + [I.ACTION]: u.ACTION_LINK_COLOR, + [I.EVENT]: u.EVENT_LINK_COLOR, + number: "#AAA", + node: "#DCA" +}; +N.DEFAULT_CONNECTION_COLORS = { + input_off: "#778", + input_on: "#7F7", + //"#BBD" + output_off: "#778", + output_on: "#7F7" + //"#BBD" +}; +N.DEFAULT_CONNECTION_COLORS_BY_TYPE = { + number: "#7F7", + string: "#77F", + boolean: "#F77" +}; +N.DEFAULT_CONNECTION_COLORS_BY_TYPE_OFF = { + number: "#474", + string: "#447", + boolean: "#744" +}; +N.active_canvas = null; +N.active_node = null; +N.onMenuCollapseAll = C.onMenuCollapseAll; +N.onMenuNodeEdit = C.onMenuNodeEdit; +N.onShowPropertyEditor = C.onShowPropertyEditor; +N.onGroupAdd = C.onGroupAdd; +N.onMenuAdd = C.onMenuAdd; +N.showMenuNodeOptionalInputs = C.showMenuNodeOptionalInputs; +N.showMenuNodeOptionalOutputs = C.showMenuNodeOptionalOutputs; +N.onShowMenuNodeProperties = C.onShowMenuNodeProperties; +N.onResizeNode = C.onResizeNode; +N.onMenuResizeNode = C.onMenuResizeNode; +N.onMenuNodeCollapse = C.onMenuNodeCollapse; +N.onMenuNodePin = C.onMenuNodePin; +N.onMenuNodeMode = C.onMenuNodeMode; +N.onMenuNodeColors = C.onMenuNodeColors; +N.onMenuNodeShapes = C.onMenuNodeShapes; +N.onMenuNodeRemove = C.onMenuNodeRemove; +N.onMenuNodeClone = C.onMenuNodeClone; +N.onMenuNodeToSubgraph = C.onMenuNodeToSubgraph; +N.onMenuNodeToSubgraphInputs = C.onMenuNodeToSubgraphInputs; +N.onMenuNodeToSubgraphOutputs = C.onMenuNodeToSubgraphOutputs; +N.onMenuNodeToParentGraph = C.onMenuNodeToParentGraph; +var j = /* @__PURE__ */ ((e) => (e[e.SEPARATOR = 0] = "SEPARATOR", e))(j || {}); +class X { + static trigger(t, i, n, s) { + var r = document.createEvent("CustomEvent"); + return r.initCustomEvent(i, !0, !0, n), r.target = s, t.dispatchEvent && t.dispatchEvent(r), r; + } + static isCursorOverElement(t, i) { + var n = t.clientX, s = t.clientY, r = i.getBoundingClientRect(); + return r ? s > r.top && s < r.top + r.height && n > r.left && n < r.left + r.width : !1; + } + static closeAllContextMenus(t) { + t = t || window; + var i = t.document.querySelectorAll(".litecontextmenu"); + if (i.length) { + var n = Array.from(i); + for (const s of n) + s.close(); + } + } + constructor(t, i = {}, n) { + this.options = i; + var s = this; + i.parentMenu && (i.parentMenu.constructor !== this.constructor ? (console.error( + "parentMenu must be of class ContextMenu, ignoring it" + ), i.parentMenu = null) : (this.parentMenu = i.parentMenu, this.parentMenu.lock = !0, this.parentMenu.current_submenu = this)); + var r = null; + i.event && (r = i.event.constructor.name), r !== "MouseEvent" && r !== "CustomEvent" && r !== "PointerEvent" && (console.error( + "Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. (" + r + ")" + ), i.event = null); + var o = document.createElement("div"); + o.className = "litegraph litecontextmenu litemenubar-panel", i.className && (o.className += " " + i.className), o.style.pointerEvents = "none", setTimeout(function() { + o.style.pointerEvents = "auto"; + }, 100), u.pointerListenerAdd( + o, + "up", + function(d) { + return d.preventDefault(), !0; + }, + !0 + ), o.addEventListener( + "contextmenu", + function(d) { + return d.button != 2 || d.preventDefault(), !1; + }, + !0 + ), o.close = () => { + o.parentNode.removeChild(o); + }, u.pointerListenerAdd( + o, + "down", + function(d) { + if (d.button == 2) + return s.close(), d.preventDefault(), !0; + }, + !0 + ); + function a(d) { + var _ = parseInt(o.style.top); + return o.style.top = (_ + d.deltaY * i.scroll_speed).toFixed() + "px", d.preventDefault(), !0; + } + if (i.scroll_speed || (i.scroll_speed = 0.1), o.addEventListener("wheel", a, !0), o.addEventListener("mousewheel", a, !0), this.root = o, i.title) { + var l = document.createElement("div"); + l.className = "litemenu-title", l.innerHTML = i.title, o.appendChild(l); + } + this.values = []; + for (let d = 0; d < t.length; d++) { + let _ = t[d], y = ""; + _ === 0 ? y = "" : typeof _ == "string" ? y = _ : y = _.content, this.addItem(y, _, i); + } + u.pointerListenerAdd(o, "enter", function(d) { + o.closing_timer && clearTimeout(o.closing_timer); + }); + var h = document; + i.event && i.event.target instanceof Node && (h = i.event.target.ownerDocument), h || (h = document), h.fullscreenElement ? h.fullscreenElement.appendChild(o) : h.body.appendChild(o); + var p = i.left || 0, f = i.top || 0; + if (i.event) { + if (p = i.event.clientX - 10, f = i.event.clientY - 10, i.title && (f -= 20), i.parentMenu) { + var c = i.parentMenu.root.getBoundingClientRect(); + p = c.left + c.width; + } + var v = document.body.getBoundingClientRect(), g = o.getBoundingClientRect(); + v.height == 0 && console.error("document.body height is 0. That is dangerous, set html,body { height: 100%; }"), v.width && p > v.width - g.width - 10 && (p = v.width - g.width - 10), v.height && f > v.height - g.height - 10 && (f = v.height - g.height - 10); + } + o.style.left = p + "px", o.style.top = f + "px", i.scale && (o.style.transform = "scale(" + i.scale + ")"); + } + addItem(t, i, n = {}) { + var s = this, r = document.createElement("div"); + r.className = "litemenu-entry submenu"; + var o = !1; + typeof i == "string" && (i = { content: i }), i === 0 ? r.classList.add("separator") : (r.innerHTML = i.title ? i.title : t, i.disabled && (o = !0, r.classList.add("disabled")), (i.submenu || i.has_submenu) && r.classList.add("has_submenu"), typeof i == "function" ? r.dataset.value = t : r.dataset.value = "" + this.values.length, i.className && (r.className += " " + i.className)), this.values.push(i), this.root.appendChild(r), o || r.addEventListener("click", h), n.autoopen && u.pointerListenerAdd(r, "enter", l); + let a = this; + function l(p) { + var f = this.value; + !f || !f.has_submenu || h.call(this, p); + } + function h(p) { + let f = parseInt(this.dataset.value); + var c = a.values[f]; + u.debug && console.debug("ContextMenu inner_onclick", f, c); + const v = N.active_canvas; + if (!v) + return; + const g = v.adjustMouseEvent(p); + var d = !0; + if (s.current_submenu && s.current_submenu.close(g), n.callback) { + var _ = n.callback.call( + this, + c, + n, + g, + s, + n.node + ); + _ === !0 && (d = !1); + } + if (c && typeof c == "object") { + if (c.callback && !n.ignore_item_callbacks && c.disabled !== !0) { + var _ = c.callback.call( + this, + c, + n, + g, + s, + n.extra + ); + _ === !0 && (d = !1); + } + if (c.submenu) { + if (!c.submenu.options) + throw "ContextMenu submenu needs options"; + new X(c.submenu.options, { + callback: c.submenu.callback, + event: g, + parentMenu: s, + ignore_item_callbacks: c.submenu.ignore_item_callbacks, + title: c.submenu.title, + extra: c.submenu.extra, + autoopen: n.autoopen + }), d = !1; + } + } + d && !s.lock && s.close(); + } + return r; + } + close(t, i) { + this.root.parentNode && this.root.parentNode.removeChild(this.root), this.parentMenu && !i && (this.parentMenu.lock = !1, this.parentMenu.current_submenu = null, t === void 0 ? this.parentMenu.close() : t && !X.isCursorOverElement(t, this.parentMenu.root) && X.trigger(this.parentMenu.root, u.pointerevents_method + "leave", t)), this.current_submenu && this.current_submenu.close(t, !0), this.root.closing_timer && clearTimeout(this.root.closing_timer); + } + getTopMenu() { + return this.options.parentMenu ? this.options.parentMenu.getTopMenu() : this; + } + getFirstEvent() { + return this.options.parentMenu ? this.options.parentMenu.getFirstEvent() : this.options.event; + } +} +export { + Ae as BASE_SLOT_TYPES, + k as BuiltInSlotShape, + I as BuiltInSlotType, + X as ContextMenu, + j as ContextMenuSpecialItem, + w as Dir, + Be as DragAndScale, + $ as GraphInput, + Q as GraphOutput, + Y as LConnectionKind, + Pe as LGraph, + N as LGraphCanvas, + ge as LGraphCanvas_Events, + U as LGraphCanvas_Rendering, + C as LGraphCanvas_UI, + me as LGraphGroup, + ae as LGraphNode, + xe as LGraphStatus, + Xe as LINK_RENDER_MODE_NAMES, + he as LLink, + ue as LayoutDirection, + de as LinkRenderMode, + u as LiteGraph, + Oe as NODE_MODE_COLORS, + re as NODE_MODE_NAMES, + Z as NodeMode, + Ie as SLOT_SHAPE_NAMES, + ne as Subgraph, + se as TitleMode, + Te as clamp, + J as getLitegraphTypeName, + we as getSlotTypesIn, + Ye as getSlotTypesInFormatted, + Le as getSlotTypesOut, + We as getSlotTypesOutFormatted, + Ce as getStaticProperty, + ye as getStaticPropertyOnInstance, + Se as isValidLitegraphType, + Ee as makeDraggable, + Re as reassignGraphIDs, + ve as toHashMap +}; diff --git a/web/lib/litegraph.extensions.js b/web/lib/litegraph.extensions.js deleted file mode 100644 index 32853fe49..000000000 --- a/web/lib/litegraph.extensions.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Changes the background color of the canvas. - * - * @method updateBackground - * @param {image} String - * @param {clearBackgroundColor} String - * @ - */ -LGraphCanvas.prototype.updateBackground = function (image, clearBackgroundColor) { - this._bg_img = new Image(); - this._bg_img.name = image; - this._bg_img.src = image; - this._bg_img.onload = () => { - this.draw(true, true); - }; - this.background_image = image; - - this.clear_background = true; - this.clear_background_color = clearBackgroundColor; - this._pattern = null -} diff --git a/web/scripts/app.js b/web/scripts/app.js index 6a2c63290..faa33b122 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -4,6 +4,10 @@ import { ComfyUI, $el } from "./ui.js"; import { api } from "./api.js"; import { defaultGraph } from "./defaultGraph.js"; import { getPngMetadata, importA1111, getLatentMetadata } from "./pnginfo.js"; +import { LiteGraph } from "../lib/litegraph.core.js" +import ComfyGraph from "./graph.js"; +import ComfyGraphCanvas from "./graphCanvas.js"; +import { ComfyBackendNode } from "./graphNode.js"; /** * @typedef {import("types/comfy").ComfyExtension} ComfyExtension @@ -690,270 +694,6 @@ export class ComfyApp { }); } - /** - * Handle mouse - * - * Move group by header - */ - #addProcessMouseHandler() { - const self = this; - - const origProcessMouseDown = LGraphCanvas.prototype.processMouseDown; - LGraphCanvas.prototype.processMouseDown = function(e) { - const res = origProcessMouseDown.apply(this, arguments); - - this.selected_group_moving = false; - - if (this.selected_group && !this.selected_group_resizing) { - var font_size = - this.selected_group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; - var height = font_size * 1.4; - - // Move group by header - if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, this.selected_group.pos[0], this.selected_group.pos[1], this.selected_group.size[0], height)) { - this.selected_group_moving = true; - } - } - - return res; - } - - const origProcessMouseMove = LGraphCanvas.prototype.processMouseMove; - LGraphCanvas.prototype.processMouseMove = function(e) { - const orig_selected_group = this.selected_group; - - if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving) { - this.selected_group = null; - } - - const res = origProcessMouseMove.apply(this, arguments); - - if (orig_selected_group && !this.selected_group_resizing && !this.selected_group_moving) { - this.selected_group = orig_selected_group; - } - - return res; - }; - } - - /** - * Handle keypress - * - * Ctrl + M mute/unmute selected nodes - */ - #addProcessKeyHandler() { - const self = this; - const origProcessKey = LGraphCanvas.prototype.processKey; - LGraphCanvas.prototype.processKey = function(e) { - const res = origProcessKey.apply(this, arguments); - - if (res === false) { - return res; - } - - if (!this.graph) { - return; - } - - var block_default = false; - - if (e.target.localName == "input") { - return; - } - - if (e.type == "keydown") { - // Ctrl + M mute/unmute - if (e.keyCode == 77 && e.ctrlKey) { - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].mode === 2) { // never - this.selected_nodes[i].mode = 0; // always - } else { - this.selected_nodes[i].mode = 2; // never - } - } - } - block_default = true; - } - - if (e.keyCode == 66 && e.ctrlKey) { - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].mode === 4) { // never - this.selected_nodes[i].mode = 0; // always - } else { - this.selected_nodes[i].mode = 4; // never - } - } - } - block_default = true; - } - } - - this.graph.change(); - - if (block_default) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } - - return res; - }; - } - - /** - * Draws group header bar - */ - #addDrawGroupsHandler() { - const self = this; - - const origDrawGroups = LGraphCanvas.prototype.drawGroups; - LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { - if (!this.graph) { - return; - } - - var groups = this.graph._groups; - - ctx.save(); - ctx.globalAlpha = 0.7 * this.editor_alpha; - - for (var i = 0; i < groups.length; ++i) { - var group = groups[i]; - - if (!LiteGraph.overlapBounding(this.visible_area, group._bounding)) { - continue; - } //out of the visible area - - ctx.fillStyle = group.color || "#335"; - ctx.strokeStyle = group.color || "#335"; - var pos = group._pos; - var size = group._size; - ctx.globalAlpha = 0.25 * this.editor_alpha; - ctx.beginPath(); - var font_size = - group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; - ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], font_size * 1.4); - ctx.fill(); - ctx.globalAlpha = this.editor_alpha; - } - - ctx.restore(); - - const res = origDrawGroups.apply(this, arguments); - return res; - } - } - - /** - * Draws node highlights (executing, drag drop) and progress bar - */ - #addDrawNodeHandler() { - const origDrawNodeShape = LGraphCanvas.prototype.drawNodeShape; - const self = this; - - LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) { - const res = origDrawNodeShape.apply(this, arguments); - - const nodeErrors = self.lastNodeErrors?.[node.id]; - - let color = null; - let lineWidth = 1; - if (node.id === +self.runningNodeId) { - color = "#0f0"; - } else if (self.dragOverNode && node.id === self.dragOverNode.id) { - color = "dodgerblue"; - } - else if (nodeErrors?.errors) { - color = "red"; - lineWidth = 2; - } - else if (self.lastExecutionError && +self.lastExecutionError.node_id === node.id) { - color = "#f0f"; - lineWidth = 2; - } - - if (color) { - const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; - ctx.lineWidth = lineWidth; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) - ctx.rect(-6, -6 - LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT); - else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed)) - ctx.roundRect( - -6, - -6 - LiteGraph.NODE_TITLE_HEIGHT, - 12 + size[0] + 1, - 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, - this.round_radius * 2 - ); - else if (shape == LiteGraph.CARD_SHAPE) - ctx.roundRect( - -6, - -6 - LiteGraph.NODE_TITLE_HEIGHT, - 12 + size[0] + 1, - 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, - [this.round_radius * 2, this.round_radius * 2, 2, 2] - ); - else if (shape == LiteGraph.CIRCLE_SHAPE) - ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2); - ctx.strokeStyle = color; - ctx.stroke(); - ctx.strokeStyle = fgcolor; - ctx.globalAlpha = 1; - } - - if (self.progress && node.id === +self.runningNodeId) { - ctx.fillStyle = "green"; - ctx.fillRect(0, 0, size[0] * (self.progress.value / self.progress.max), 6); - ctx.fillStyle = bgcolor; - } - - // Highlight inputs that failed validation - if (nodeErrors) { - ctx.lineWidth = 2; - ctx.strokeStyle = "red"; - for (const error of nodeErrors.errors) { - if (error.extra_info && error.extra_info.input_name) { - const inputIndex = node.findInputSlot(error.extra_info.input_name) - if (inputIndex !== -1) { - let pos = node.getConnectionPos(true, inputIndex); - ctx.beginPath(); - ctx.arc(pos[0] - node.pos[0], pos[1] - node.pos[1], 12, 0, 2 * Math.PI, false) - ctx.stroke(); - } - } - } - } - - return res; - }; - - const origDrawNode = LGraphCanvas.prototype.drawNode; - LGraphCanvas.prototype.drawNode = function (node, ctx) { - var editor_alpha = this.editor_alpha; - var old_color = node.bgcolor; - - if (node.mode === 2) { // never - this.editor_alpha = 0.4; - } - - if (node.mode === 4) { // never - node.bgcolor = "#FF00FF"; - this.editor_alpha = 0.2; - } - - const res = origDrawNode.apply(this, arguments); - - this.editor_alpha = editor_alpha; - node.bgcolor = old_color; - - return res; - }; - } - /** * Handles updates from the API socket */ @@ -1056,11 +796,8 @@ export class ComfyApp { canvasEl.tabIndex = "1"; document.body.prepend(canvasEl); - this.#addProcessMouseHandler(); - this.#addProcessKeyHandler(); - - this.graph = new LGraph(); - const canvas = (this.canvas = new LGraphCanvas(canvasEl, this.graph)); + this.graph = new ComfyGraph(); + const canvas = (this.canvas = new ComfyGraphCanvas(canvasEl, this.graph)); this.ctx = canvasEl.getContext("2d"); LiteGraph.release_link_on_empty_shows_menu = true; @@ -1106,8 +843,6 @@ export class ComfyApp { // Save current workflow automatically setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000); - this.#addDrawNodeHandler(); - this.#addDrawGroupsHandler(); this.#addApiUpdateHandlers(); this.#addDropHandler(); this.#addPasteHandler(); @@ -1130,74 +865,92 @@ export class ComfyApp { async registerNodesFromDefs(defs) { await this.#invokeExtensionsAsync("addCustomNodeDefs", defs); + // // Generate list of known widgets + // const widgets = Object.assign( + // {}, + // ComfyWidgets, + // ...(await this.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean) + // ); + // Generate list of known widgets - const widgets = Object.assign( - {}, - ComfyWidgets, - ...(await this.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean) - ); + const customWidgets = (await app.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean); + ComfyWidgets.customWidgets = customWidgets; // Register a node for each definition for (const nodeId in defs) { const nodeData = defs[nodeId]; - const node = Object.assign( - function ComfyNode() { - var inputs = nodeData["input"]["required"]; - if (nodeData["input"]["optional"] != undefined){ - inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"]) - } - const config = { minWidth: 1, minHeight: 1 }; - for (const inputName in inputs) { - const inputData = inputs[inputName]; - const type = inputData[0]; - if(inputData[1]?.forceInput) { - this.addInput(inputName, type); - } else { - if (Array.isArray(type)) { - // Enums - Object.assign(config, widgets.COMBO(this, inputName, inputData, app) || {}); - } else if (`${type}:${inputName}` in widgets) { - // Support custom widgets by Type:Name - Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData, app) || {}); - } else if (type in widgets) { - // Standard type widgets - Object.assign(config, widgets[type](this, inputName, inputData, app) || {}); - } else { - // Node connection inputs - this.addInput(inputName, type); - } - } - } + const ctor = class extends ComfyBackendNode { + constructor(title) { + super(title, nodeId, nodeData); + } + } - for (const o in nodeData["output"]) { - const output = nodeData["output"][o]; - const outputName = nodeData["output_name"][o] || output; - const outputShape = nodeData["output_is_list"][o] ? LiteGraph.GRID_SHAPE : LiteGraph.CIRCLE_SHAPE ; - this.addOutput(outputName, output, { shape: outputShape }); - } + const node = { + class: ctor, + title: nodeData.display_name || nodeData.name, + desc: `ComfyNode: ${nodeId}` + } - const s = this.computeSize(); - s[0] = Math.max(config.minWidth, s[0] * 1.5); - s[1] = Math.max(config.minHeight, s[1]); - this.size = s; - this.serialize_widgets = true; + // const node = Object.assign( + // function ComfyNode() { + // var inputs = nodeData["input"]["required"]; + // if (nodeData["input"]["optional"] != undefined){ + // inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"]) + // } + // const config = { minWidth: 1, minHeight: 1 }; + // for (const inputName in inputs) { + // const inputData = inputs[inputName]; + // const type = inputData[0]; - app.#invokeExtensionsAsync("nodeCreated", this); - }, - { - title: nodeData.display_name || nodeData.name, - comfyClass: nodeData.name, - } - ); - node.prototype.comfyClass = nodeData.name; + // if(inputData[1]?.forceInput) { + // this.addInput(inputName, type); + // } else { + // if (Array.isArray(type)) { + // // Enums + // Object.assign(config, widgets.COMBO(this, inputName, inputData, app) || {}); + // } else if (`${type}:${inputName}` in widgets) { + // // Support custom widgets by Type:Name + // Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData, app) || {}); + // } else if (type in widgets) { + // // Standard type widgets + // Object.assign(config, widgets[type](this, inputName, inputData, app) || {}); + // } else { + // // Node connection inputs + // this.addInput(inputName, type); + // } + // } + // } - this.#addNodeContextMenuHandler(node); - this.#addDrawBackgroundHandler(node, app); - this.#addNodeKeyHandler(node); + // for (const o in nodeData["output"]) { + // const output = nodeData["output"][o]; + // const outputName = nodeData["output_name"][o] || output; + // const outputShape = nodeData["output_is_list"][o] ? LiteGraph.GRID_SHAPE : LiteGraph.CIRCLE_SHAPE ; + // this.addOutput(outputName, output, { shape: outputShape }); + // } + + // const s = this.computeSize(); + // s[0] = Math.max(config.minWidth, s[0] * 1.5); + // s[1] = Math.max(config.minHeight, s[1]); + // this.size = s; + // this.serialize_widgets = true; + + // app.#invokeExtensionsAsync("nodeCreated", this); + // }, + // { + // title: nodeData.display_name || nodeData.name, + // comfyClass: nodeData.name, + // } + // ); + // node.prototype.comfyClass = nodeData.name; + node.type = nodeId; + + // this.#addNodeContextMenuHandler(node); + // this.#addDrawBackgroundHandler(node, app); + // this.#addNodeKeyHandler(node); await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData); - LiteGraph.registerNodeType(nodeId, node); + LiteGraph.registerNodeType(node); node.category = nodeData.category; } } diff --git a/web/scripts/graph.js b/web/scripts/graph.js new file mode 100644 index 000000000..1442782be --- /dev/null +++ b/web/scripts/graph.js @@ -0,0 +1,5 @@ +import { LGraph } from "../lib/litegraph.core.js" + +export default class ComfyGraph extends LGraph { + +} diff --git a/web/scripts/graphCanvas.js b/web/scripts/graphCanvas.js new file mode 100644 index 000000000..b04cdbf2e --- /dev/null +++ b/web/scripts/graphCanvas.js @@ -0,0 +1,236 @@ +import { LGraphCanvas } from "../lib/litegraph.core.js" + +export default class ComfyGraphCanvas extends LGraphCanvas { + processKey(e) { + const res = super.processKey(e); + + if (res === false) { + return res; + } + + if (!this.graph) { + return; + } + + var block_default = false; + + if (e.target.localName == "input") { + return; + } + + if (e.type == "keydown") { + // Ctrl + M mute/unmute + if (e.keyCode == 77 && e.ctrlKey) { + if (this.selected_nodes) { + for (var i in this.selected_nodes) { + if (this.selected_nodes[i].mode === 2) { // never + this.selected_nodes[i].mode = 0; // always + } else { + this.selected_nodes[i].mode = 2; // never + } + } + } + block_default = true; + } + + if (e.keyCode == 66 && e.ctrlKey) { + if (this.selected_nodes) { + for (var i in this.selected_nodes) { + if (this.selected_nodes[i].mode === 4) { // never + this.selected_nodes[i].mode = 0; // always + } else { + this.selected_nodes[i].mode = 4; // never + } + } + } + block_default = true; + } + } + + this.graph.change(); + + if (block_default) { + e.preventDefault(); + e.stopImmediatePropagation(); + return false; + } + + return res; + } + + processMouseDown(e) { + const res = super.processMouseDown(e); + + this.selected_group_moving = false; + + if (this.selected_group && !this.selected_group_resizing) { + var font_size = + this.selected_group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; + var height = font_size * 1.4; + + // Move group by header + if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, this.selected_group.pos[0], this.selected_group.pos[1], this.selected_group.size[0], height)) { + this.selected_group_moving = true; + } + } + + return res; + } + + processMouseMove(e) { + const orig_selected_group = this.selected_group; + + if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving) { + this.selected_group = null; + } + + const res = super.processMouseMove(e); + + if (orig_selected_group && !this.selected_group_resizing && !this.selected_group_moving) { + this.selected_group = orig_selected_group; + } + + return res; + } + + /** + * Draws group header bar + */ + drawGroups(canvas, ctx) { + if (!this.graph) { + return; + } + + var groups = this.graph._groups; + + ctx.save(); + ctx.globalAlpha = 0.7 * this.editor_alpha; + + for (var i = 0; i < groups.length; ++i) { + var group = groups[i]; + + if (!LiteGraph.overlapBounding(this.visible_area, group._bounding)) { + continue; + } //out of the visible area + + ctx.fillStyle = group.color || "#335"; + ctx.strokeStyle = group.color || "#335"; + var pos = group._pos; + var size = group._size; + ctx.globalAlpha = 0.25 * this.editor_alpha; + ctx.beginPath(); + var font_size = + group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; + ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], font_size * 1.4); + ctx.fill(); + ctx.globalAlpha = this.editor_alpha; + } + + ctx.restore(); + + const res = super.drawGroups(canvas, ctx); + return res; + } + + /** + * Draws node highlights (executing, drag drop) and progress bar + */ + drawNodeShape(node, ctx, size, fgcolor, bgcolor, selected, mouse_over) { + const res = super.drawNodeShape(node, ctx, size, fgcolor, bgcolor, selected, mouse_over); + + const nodeErrors = self.lastNodeErrors?.[node.id]; + + let color = null; + let lineWidth = 1; + if (node.id === +self.runningNodeId) { + color = "#0f0"; + } else if (self.dragOverNode && node.id === self.dragOverNode.id) { + color = "dodgerblue"; + } + else if (nodeErrors?.errors) { + color = "red"; + lineWidth = 2; + } + else if (self.lastExecutionError && +self.lastExecutionError.node_id === node.id) { + color = "#f0f"; + lineWidth = 2; + } + + if (color) { + const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; + ctx.lineWidth = lineWidth; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE) + ctx.rect(-6, -6 - LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT); + else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed)) + ctx.roundRect( + -6, + -6 - LiteGraph.NODE_TITLE_HEIGHT, + 12 + size[0] + 1, + 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, + this.round_radius * 2 + ); + else if (shape == LiteGraph.CARD_SHAPE) + ctx.roundRect( + -6, + -6 - LiteGraph.NODE_TITLE_HEIGHT, + 12 + size[0] + 1, + 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, + [this.round_radius * 2, this.round_radius * 2, 2, 2] + ); + else if (shape == LiteGraph.CIRCLE_SHAPE) + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2); + ctx.strokeStyle = color; + ctx.stroke(); + ctx.strokeStyle = fgcolor; + ctx.globalAlpha = 1; + } + + if (self.progress && node.id === +self.runningNodeId) { + ctx.fillStyle = "green"; + ctx.fillRect(0, 0, size[0] * (self.progress.value / self.progress.max), 6); + ctx.fillStyle = bgcolor; + } + + // Highlight inputs that failed validation + if (nodeErrors) { + ctx.lineWidth = 2; + ctx.strokeStyle = "red"; + for (const error of nodeErrors.errors) { + if (error.extra_info && error.extra_info.input_name) { + const inputIndex = node.findInputSlot(error.extra_info.input_name) + if (inputIndex !== -1) { + let pos = node.getConnectionPos(true, inputIndex); + ctx.beginPath(); + ctx.arc(pos[0] - node.pos[0], pos[1] - node.pos[1], 12, 0, 2 * Math.PI, false) + ctx.stroke(); + } + } + } + } + + return res; + } + + drawNode(node, ctx) { + var editor_alpha = this.editor_alpha; + var old_color = node.bgcolor; + + if (node.mode === 2) { // never + this.editor_alpha = 0.4; + } + + if (node.mode === 4) { // never + node.bgcolor = "#FF00FF"; + this.editor_alpha = 0.2; + } + + const res = super.drawNode(node, ctx); + + this.editor_alpha = editor_alpha; + node.bgcolor = old_color; + + return res; + } +} diff --git a/web/scripts/graphNode.js b/web/scripts/graphNode.js new file mode 100644 index 000000000..39b42a11c --- /dev/null +++ b/web/scripts/graphNode.js @@ -0,0 +1,86 @@ +import { LGraphNode, LGraphCanvas, BuiltInSlotType, BuiltInSlotShape } from "../lib/litegraph.core.js" +import { ComfyWidgets } from "./widgets.js"; +import { iterateNodeDefOutputs, iterateNodeDefInputs } from "./nodeDef.js"; + +export class ComfyGraphNode extends LGraphNode { + +} + +// comfy class -> input name -> input config +const defaultInputConfigs = {} + +export class ComfyBackendNode extends ComfyGraphNode { + constructor(title, comfyClass, nodeDef) { + super(title) + this.type = comfyClass; // XXX: workaround dependency in LGraphNode.addInput() + this.displayName = nodeDef.display_name; + this.comfyNodeDef = nodeDef; + this.comfyClass = comfyClass; + this.isBackendNode = true; + + const color = LGraphCanvas.node_colors["yellow"]; + if (this.color == null) + this.color = color.color + if (this.bgColor == null) + this.bgColor = color.bgColor + + this.#setup(nodeDef) + + // if (nodeDef.output_node) { + // this.addOutput("OUTPUT", BuiltInSlotType.EVENT, { color_off: "rebeccapurple", color_on: "rebeccapurple" }); + // } + } + + get isOutputNode() { + return this.comfyNodeDef.output_node; + } + + #setup(nodeDef) { + defaultInputConfigs[this.type] = {} + + const widgets = Object.assign({}, ComfyWidgets, ComfyWidgets.customWidgets); + + for (const [inputName, inputData] of iterateNodeDefInputs(nodeDef)) { + const config = {}; + + const [type, opts] = inputData; + + if (opts?.forceInput) { + if (Array.isArray(type)) { + throw new Error(`Can't have forceInput set to true for an enum type! ${type}`) + } + this.addInput(inputName, type); + } else { + if (Array.isArray(type)) { + // Enums + Object.assign(config, widgets.COMBO(this, inputName, inputData) || {}); + } else if (`${type}:${inputName}` in widgets) { + // Support custom ComfyWidgets by Type:Name + Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData) || {}); + } else if (type in widgets) { + // Standard type ComfyWidgets + Object.assign(config, widgets[type](this, inputName, inputData) || {}); + } else { + // Node connection inputs (backend) + this.addInput(inputName, type); + } + } + + if ("widgetNodeType" in config) + ComfyBackendNode.defaultInputConfigs[this.type][inputName] = config.config + } + + for (const output of iterateNodeDefOutputs(nodeDef)) { + const outputShape = output.is_list ? BuiltInSlotShape.GRID_SHAPE : BuiltInSlotShape.CIRCLE_SHAPE; + this.addOutput(output.name, output.type, { shape: outputShape }); + } + + this.serialize_widgets = false; + // app.#invokeExtensionsAsync("nodeCreated", this); + } + + // onExecuted(outputData) { + // console.warn("onExecuted outputs", outputData) + // this.triggerSlot(0, outputData) + // } +} diff --git a/web/scripts/nodeDef.js b/web/scripts/nodeDef.js new file mode 100644 index 000000000..ffda4101d --- /dev/null +++ b/web/scripts/nodeDef.js @@ -0,0 +1,26 @@ +import { ComfyWidgets } from "./widgets.js" +import { range } from "./utils.js" + +export function isBackendNodeDefInputType(inputName, type) { + const widgets = Object.assign({}, ComfyWidgets, ComfyWidgets.customWidgets); + return !Array.isArray(type) && !(type in ComfyWidgets) && !(`${type}:${inputName}` in ComfyWidgets); +} + +export function iterateNodeDefInputs(def) { + var inputs = def.input.required || {} + if (def.input.optional != null) { + inputs = Object.assign({}, def.input.required, def.input.optional) + } + return Object.entries(inputs); +} + +export function iterateNodeDefOutputs(def) { + const outputCount = def.output ? def.output.length : 0; + return range(outputCount).map(i => { + return { + type: def.output[i], + name: def.output_name[i] || def.output[i], + is_list: def.output_is_list[i], + } + }) +} diff --git a/web/scripts/utils.js b/web/scripts/utils.js new file mode 100644 index 000000000..fccaaff4b --- /dev/null +++ b/web/scripts/utils.js @@ -0,0 +1,3 @@ +export function range(size, startAt = 0) { + return [...Array(size).keys()].map(i => i + startAt); +} diff --git a/web/scripts/widgets.js b/web/scripts/widgets.js index adf5f26fa..63036a45a 100644 --- a/web/scripts/widgets.js +++ b/web/scripts/widgets.js @@ -157,7 +157,7 @@ function addMultilineWidget(node, name, opts, app) { // Calculate it here instead computeSize(node.size); } - const visible = app.canvas.ds.scale > 0.5 && this.type === "customtext"; + const visible = window.app.canvas.ds.scale > 0.5 && this.type === "customtext"; const margin = 10; const elRect = ctx.canvas.getBoundingClientRect(); const transform = new DOMMatrix() @@ -176,7 +176,7 @@ function addMultilineWidget(node, name, opts, app) { position: "absolute", background: (!node.color)?'':node.color, color: (!node.color)?'':'white', - zIndex: app.graph._nodes.indexOf(node), + zIndex: window.app.graph._nodes.indexOf(node), }); this.inputEl.hidden = !visible; }, @@ -195,11 +195,11 @@ function addMultilineWidget(node, name, opts, app) { node.addCustomWidget(widget); - app.canvas.onDrawBackground = function () { + window.app.canvas.onDrawBackground = function () { // Draw node isnt fired once the node is off the screen // if it goes off screen quickly, the input may not be removed // this shifts it off screen so it can be moved back if the node is visible. - for (let n in app.graph._nodes) { + for (let n in window.app.graph._nodes) { n = graph._nodes[n]; for (let w in n.widgets) { let wid = n.widgets[w]; @@ -251,7 +251,7 @@ function addMultilineWidget(node, name, opts, app) { } function isSlider(display, app) { - if (app.ui.settings.getSettingValue("Comfy.DisableSliders")) { + if (window.app.ui.settings.getSettingValue("Comfy.DisableSliders")) { return "number" } @@ -321,7 +321,7 @@ export const ComfyWidgets = { const img = new Image(); img.onload = () => { node.imgs = [img]; - app.graph.setDirtyCanvas(true); + window.app.graph.setDirtyCanvas(true); }; let folder_separator = name.lastIndexOf("/"); let subfolder = ""; @@ -329,7 +329,7 @@ export const ComfyWidgets = { subfolder = name.substring(0, folder_separator); name = name.substring(folder_separator + 1); } - img.src = api.apiURL(`/view?filename=${name}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}`); + img.src = api.apiURL(`/view?filename=${name}&type=input&subfolder=${subfolder}${window.app.getPreviewFormatParam()}`); node.setSizeForImage?.(); } @@ -457,3 +457,5 @@ export const ComfyWidgets = { return { widget: uploadWidget }; }, }; + +export const customWidgets = []