From 142c22ea41b2a6a5646d025414568c6a209d3cbd Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Mon, 7 Oct 2024 11:15:29 -0400 Subject: [PATCH] Typescript conversion 0.7.84 (#194) * Convert litegraph.js to TS * Overhaul static litegraph.d.ts with updated types * Fix rename oversights and revert fix unused param - Some functions were broken by merging updated TS function signatures which included param renames - Removal of unused params does not belong in the TS conversion PR, and has been reverted * Remove legacy static .d.ts file * Add callback decl from #180 Support allowing links to widgets (#180) c23e610c11391486b28a14320ab31f69cdc43ee4 * Convert NodeId to string | number Results in significantly less downstream changes, despite being a change from the old static file. --------- Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com> --- public/litegraph.d.ts | 1646 ------------------------------------ src/ContextMenu.ts | 21 +- src/DragAndScale.ts | 34 +- src/LGraph.ts | 191 +++-- src/LGraphCanvas.ts | 532 ++++++++---- src/LGraphGroup.ts | 27 +- src/LGraphNode.ts | 328 +++++-- src/LLink.ts | 25 +- src/LiteGraphGlobal.ts | 668 +++++++-------- src/draw.ts | 3 +- src/interfaces.ts | 138 +++ src/litegraph.js | 23 - src/litegraph.ts | 106 +++ src/types/events.ts | 40 + src/types/globalEnums.ts | 51 ++ src/types/serialisation.ts | 61 ++ src/types/widgets.ts | 124 +++ vite.config.mts | 7 +- 18 files changed, 1703 insertions(+), 2322 deletions(-) delete mode 100644 public/litegraph.d.ts create mode 100644 src/interfaces.ts delete mode 100755 src/litegraph.js create mode 100644 src/litegraph.ts create mode 100644 src/types/events.ts create mode 100644 src/types/globalEnums.ts create mode 100644 src/types/serialisation.ts create mode 100644 src/types/widgets.ts diff --git a/public/litegraph.d.ts b/public/litegraph.d.ts deleted file mode 100644 index 20371d9d6..000000000 --- a/public/litegraph.d.ts +++ /dev/null @@ -1,1646 +0,0 @@ -// Type definitions for litegraph.js 0.7.0 -// Project: litegraph.js -// Definitions by: NateScarlet - -import { LGraphBadge, BadgePosition } from './LGraphBadge'; - -export type Vector2 = [number, number]; -export type Vector4 = [number, number, number, number]; -export type widgetTypes = - | "number" - | "slider" - | "combo" - | "text" - | "toggle" - | "button"; -export type SlotShape = - | typeof LiteGraph.BOX_SHAPE - | typeof LiteGraph.CIRCLE_SHAPE - | typeof LiteGraph.ARROW_SHAPE - | typeof LiteGraph.SQUARE_SHAPE - | number; // For custom shapes - -/** https://github.com/jagenjo/litegraph.js/tree/master/guides#node-slots */ -export interface INodeSlot { - name: string; - type: string | -1; - label?: string; - dir?: - | typeof LiteGraph.UP - | typeof LiteGraph.RIGHT - | typeof LiteGraph.DOWN - | typeof LiteGraph.LEFT; - color_on?: string; - color_off?: string; - shape?: SlotShape; - locked?: boolean; - nameLocked?: boolean; -} - -export interface INodeInputSlot extends INodeSlot { - link: LLink["id"] | null; -} -export interface INodeOutputSlot extends INodeSlot { - links: LLink["id"][] | null; -} - -export type WidgetCallback = ( - this: T, - value: T["value"], - graphCanvas: LGraphCanvas, - node: LGraphNode, - pos: Vector2, - event?: MouseEvent -) => void; - -export interface IWidget { - // linked widgets, e.g. seed+seedControl - linkedWidgets: IWidget[]; - - name: string | null; - value: TValue; - options?: TOptions; - type?: widgetTypes; - y?: number; - property?: string; - last_y?: number; - clicked?: boolean; - marker?: boolean; - callback?: WidgetCallback; - /** Called by `LGraphCanvas.drawNodeWidgets` */ - draw?( - ctx: CanvasRenderingContext2D, - node: LGraphNode, - width: number, - posY: number, - height: number - ): void; - /** - * Called by `LGraphCanvas.processNodeWidgets` - * https://github.com/jagenjo/litegraph.js/issues/76 - */ - mouse?( - event: MouseEvent, - pos: Vector2, - node: LGraphNode - ): boolean; - /** Called by `LGraphNode.computeSize` */ - computeSize?(width: number): [number, number]; -} -export interface IButtonWidget extends IWidget { - type: "button"; -} -export interface IToggleWidget - extends IWidget { - type: "toggle"; -} -export interface ISliderWidget - extends IWidget { - type: "slider"; -} -export interface INumberWidget extends IWidget { - type: "number"; -} -export interface IComboWidget - extends IWidget< - string[], - { - values: - | string[] - | ((widget: IComboWidget, node: LGraphNode) => string[]); - } - > { - type: "combo"; -} - -export interface ITextWidget extends IWidget { - type: "text"; -} - -export interface IContextMenuItem { - content: string; - callback?: ContextMenuEventListener; - /** Used as innerHTML for extra child element */ - title?: string; - disabled?: boolean; - has_submenu?: boolean; - submenu?: { - options: ContextMenuItem[]; - } & IContextMenuOptions; - className?: string; -} -export interface IContextMenuOptions { - callback?: ContextMenuEventListener; - ignore_item_callbacks?: Boolean; - event?: MouseEvent | CustomEvent; - parentMenu?: ContextMenu; - autoopen?: boolean; - title?: string; - extra?: any; -} - -export type ContextMenuItem = IContextMenuItem | null; -export type ContextMenuEventListener = ( - value: ContextMenuItem, - options: IContextMenuOptions, - event: MouseEvent, - parentMenu: ContextMenu | undefined, - node: LGraphNode -) => boolean | void; - -export type LinkReleaseContext = { - node_to?: LGraphNode; - node_from?: LGraphNode; - slot_from: INodeSlot; - type_filter_in?: string; - type_filter_out?: string; -}; - -export type ConnectingLink = { - node: LGraphNode; - slot: number; - input: INodeInputSlot | null; - output: INodeOutputSlot | null; - pos: Vector2; -}; - -export type LinkReleaseContextExtended = { - links: ConnectingLink[]; -}; - -export type LiteGraphCanvasEventType = "empty-release" | "empty-double-click" | "group-double-click"; - -export type LiteGraphCanvasEvent = CustomEvent<{ - subType: string; - originalEvent: Event; - linkReleaseContext?: LinkReleaseContextExtended; - group?: LGraphGroup; -}>; - -export type LiteGraphCanvasGroupEvent = CustomEvent<{ - subType: "group-double-click"; - originalEvent: MouseEvent; - group: LGraphGroup; -}>; - -export type SlotTitleMode = - typeof LiteGraph.NORMAL_TITLE | - typeof LiteGraph.NO_TITLE | - typeof LiteGraph.TRANSPARENT_TITLE | - typeof LiteGraph.AUTOHIDE_TITLE; - -export const LiteGraph: { - DEFAULT_GROUP_FONT_SIZE: any; - overlapBounding(visible_area: any, _bounding: any): unknown; - release_link_on_empty_shows_menu: boolean; - alt_drag_do_clone_nodes: boolean; - GRID_SHAPE: number; - VERSION: number; - - CANVAS_GRID_SIZE: number; - - NODE_TITLE_HEIGHT: number; - NODE_TITLE_TEXT_Y: number; - NODE_SLOT_HEIGHT: number; - NODE_WIDGET_HEIGHT: number; - NODE_WIDTH: number; - NODE_MIN_WIDTH: number; - NODE_COLLAPSED_RADIUS: number; - NODE_COLLAPSED_WIDTH: number; - NODE_TITLE_COLOR: string; - NODE_TEXT_SIZE: number; - NODE_TEXT_COLOR: string; - NODE_SUBTEXT_SIZE: number; - NODE_DEFAULT_COLOR: string; - NODE_DEFAULT_BGCOLOR: string; - NODE_DEFAULT_BOXCOLOR: string; - NODE_DEFAULT_SHAPE: string; - DEFAULT_SHADOW_COLOR: string; - DEFAULT_GROUP_FONT: number; - - LINK_COLOR: string; - EVENT_LINK_COLOR: string; - CONNECTING_LINK_COLOR: string; - - MAX_NUMBER_OF_NODES: number; //avoid infinite loops - DEFAULT_POSITION: Vector2; //default node position - VALID_SHAPES: ["default", "box", "round", "card"]; //,"circle" - - //shapes are used for nodes but also for slots - BOX_SHAPE: 1; - ROUND_SHAPE: 2; - CIRCLE_SHAPE: 3; - CARD_SHAPE: 4; - ARROW_SHAPE: 5; - SQUARE_SHAPE: 6; - - //enums - INPUT: 1; - OUTPUT: 2; - - EVENT: -1; //for outputs - ACTION: -1; //for inputs - - ALWAYS: 0; - ON_EVENT: 1; - NEVER: 2; - ON_TRIGGER: 3; - - UP: 1; - DOWN: 2; - LEFT: 3; - RIGHT: 4; - CENTER: 5; - - HIDDEN_LINK: -1; - STRAIGHT_LINK: 0; - LINEAR_LINK: 1; - SPLINE_LINK: 2; - - NORMAL_TITLE: 0; - NO_TITLE: 1; - TRANSPARENT_TITLE: 2; - AUTOHIDE_TITLE: 3; - - node_images_path: string; - - debug: boolean; - catch_exceptions: boolean; - throw_errors: boolean; - /** if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits */ - allow_scripts: boolean; - /** node types by string */ - registered_node_types: Record; - /** used for dropping files in the canvas */ - node_types_by_file_extension: Record; - /** node types by class name */ - Nodes: Record; - - /** used to add extra features to the search box */ - searchbox_extras: Record< - string, - { - data: { outputs: string[][]; title: string }; - desc: string; - type: string; - } - >; - highlight_selected_group: boolean; - - createNode(type: string): T; - /** Register a node class so it can be listed when the user wants to create a new one */ - registerNodeType(type: string, base: { new(): LGraphNode }): void; - /** removes a node type from the system */ - unregisterNodeType(type: string): void; - /** Removes all previously registered node's types. */ - clearRegisteredTypes(): void; - /** - * Create a new node type by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. - * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. - * @param name node name with namespace (p.e.: 'math/sum') - * @param func - * @param param_types an array containing the type of every parameter, otherwise parameters will accept any type - * @param return_type string with the return type, otherwise it will be generic - * @param properties properties to be configurable - */ - wrapFunctionAsNode( - name: string, - func: (...args: any[]) => any, - param_types?: string[], - return_type?: string, - properties?: object - ): void; - - /** - * Adds this method to all node types, existing and to be created - * (You can add it to LGraphNode.prototype but then existing node types wont have it) - */ - addNodeMethod(name: string, func: (...args: any[]) => any): void; - - /** - * Create a node of a given type with a name. The node is not attached to any graph yet. - * @param type full name of the node class. p.e. "math/sin" - * @param name a name to distinguish from other nodes - * @param options to set options - */ - createNode( - type: string, - title: string, - options: object - ): T; - - /** - * Returns a registered node type with a given name - * @param type full name of the node class. p.e. "math/sin" - */ - getNodeType(type: string): LGraphNodeConstructor; - - /** - * Returns a list of node types matching one category - * @method getNodeTypesInCategory - * @param {String} category category name - * @param {String} filter only nodes with ctor.filter equal can be shown - * @return {Array} array with all the node classes - */ - getNodeTypesInCategory( - category: string, - filter: string - ): LGraphNodeConstructor[]; - - /** - * Returns a list with all the node type categories - * @method getNodeTypesCategories - * @param {String} filter only nodes with ctor.filter equal can be shown - * @return {Array} array with all the names of the categories - */ - getNodeTypesCategories(filter: string): string[]; - - /** debug purposes: reloads all the js scripts that matches a wildcard */ - reloadNodes(folder_wildcard: string): void; - - getTime(): number; - LLink: typeof LLink; - LGraph: typeof LGraph; - DragAndScale: typeof DragAndScale; - compareObjects(a: object, b: object): boolean; - distance(a: Vector2, b: Vector2): number; - colorToString(c: string): string; - isInsideRectangle( - x: number, - y: number, - left: number, - top: number, - width: number, - height: number - ): boolean; - growBounding(bounding: Vector4, x: number, y: number): Vector4; - isInsideBounding(p: Vector2, bb: Vector4): boolean; - hex2num(hex: string): [number, number, number]; - num2hex(triplet: [number, number, number]): string; - ContextMenu: typeof ContextMenu; - extendClass(target: A, origin: B): A & B; - getParameterNames(func: string): string[]; -}; - -export type serializedLGraph< - TNode = ReturnType, - // https://github.com/jagenjo/litegraph.js/issues/74 - TLink = [number, number, number, number, number, string], - TGroup = ReturnType -> = { - last_node_id: LGraph["last_node_id"]; - last_link_id: LGraph["last_link_id"]; - nodes: TNode[]; - links: TLink[]; - groups: TGroup[]; - config: LGraph["config"]; - version: typeof LiteGraph.VERSION; -}; - -export declare class LGraph { - static supported_types: string[]; - static STATUS_STOPPED: 1; - static STATUS_RUNNING: 2; - extra: any; - - constructor(o?: object); - - filter: string; - catch_errors: boolean; - /** custom data */ - config: object; - elapsed_time: number; - fixedtime: number; - fixedtime_lapse: number; - globaltime: number; - inputs: any; - iteration: number; - last_link_id: number; - last_node_id: number; - last_update_time: number; - links: Record; - list_of_graphcanvas: LGraphCanvas[]; - outputs: any; - runningtime: number; - starttime: number; - status: typeof LGraph.STATUS_RUNNING | typeof LGraph.STATUS_STOPPED; - - private _nodes: LGraphNode[]; - private _groups: LGraphGroup[]; - private _nodes_by_id: Record; - /** nodes that are executable sorted in execution order */ - private _nodes_executable: - | (LGraphNode & { onExecute: NonNullable }[]) - | null; - /** nodes that contain onExecute */ - private _nodes_in_order: LGraphNode[]; - private _version: number; - - get nodes(): LGraphNode[]; - get groups(): LGraphGroup[]; - - getSupportedTypes(): string[]; - /** Removes all nodes from this graph */ - clear(): void; - /** Attach Canvas to this graph */ - attachCanvas(graphCanvas: LGraphCanvas): void; - /** Detach Canvas to this graph */ - detachCanvas(graphCanvas: LGraphCanvas): void; - /** - * Starts running this graph every interval milliseconds. - * @param interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate - */ - start(interval?: number): void; - /** Stops the execution loop of the graph */ - stop(): void; - /** - * Run N steps (cycles) of the graph - * @param num number of steps to run, default is 1 - */ - runStep(num?: number, do_not_catch_errors?: boolean): void; - /** - * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than - * nodes with only inputs. - */ - updateExecutionOrder(): void; - /** This is more internal, it computes the executable nodes in order and returns it */ - computeExecutionOrder(only_onExecute: boolean, set_level?: any): T; - /** - * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. - * It doesn't include the node itself - * @return an array with all the LGraphNodes that affect this node, in order of execution - */ - getAncestors(node: LGraphNode): LGraphNode[]; - /** - * Positions every node in a more readable manner - */ - arrange(margin?: number, layout?: string): void; - /** - * Returns the amount of time the graph has been running in milliseconds - * @return number of milliseconds the graph has been running - */ - getTime(): number; - - /** - * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant - * @return number of milliseconds the graph has been running - */ - getFixedTime(): number; - - /** - * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct - * if the nodes are using graphical actions - * @return number of milliseconds it took the last cycle - */ - getElapsedTime(): number; - /** - * Sends an event to all the nodes, useful to trigger stuff - * @param eventName the name of the event (function to be called) - * @param params parameters in array format - */ - sendEventToAllNodes(eventName: string, params: any[], mode?: any): void; - - sendActionToCanvas(action: any, params: any[]): void; - /** - * Adds a new node instance to this graph - * @param node the instance of the node - */ - add(node: LGraphNode | LGraphGroup, skip_compute_order?: boolean): void; - /** - * Called when a new node is added - * @param node the instance of the node - */ - onNodeAdded(node: LGraphNode): void; - /** Removes a node from the graph */ - remove(node: LGraphNode): void; - /** Returns a node by its id. */ - getNodeById(id: number | string): LGraphNode | undefined; - /** - * Returns a list of nodes that matches a class - * @param classObject the class itself (not an string) - * @return a list with all the nodes of this type - */ - findNodesByClass( - classObject: LGraphNodeConstructor - ): T[]; - /** - * Returns a list of nodes that matches a type - * @param type the name of the node type - * @return a list with all the nodes of this type - */ - findNodesByType(type: string): T[]; - /** - * Returns the first node that matches a name in its title - * @param title the name of the node to search - * @return the node or null - */ - findNodeByTitle(title: string): T | null; - /** - * Returns a list of nodes that matches a name - * @param title the name of the node to search - * @return a list with all the nodes with this name - */ - findNodesByTitle(title: string): T[]; - /** - * Returns the top-most node in this position of the canvas - * @param x the x coordinate in canvas space - * @param y the y coordinate in canvas space - * @param nodes_list a list with all the nodes to search from, by default is all the nodes in the graph - * @return the node at this position or null - */ - getNodeOnPos( - x: number, - y: number, - node_list?: LGraphNode[], - margin?: number - ): T | null; - /** - * Returns the top-most group in that position - * @param x the x coordinate in canvas space - * @param y the y coordinate in canvas space - * @return the group or null - */ - getGroupOnPos(x: number, y: number): LGraphGroup | null; - - onAction(action: any, param: any): void; - trigger(action: any, param: any): void; - /** Tell this graph it has a global graph input of this type */ - addInput(name: string, type: string, value?: any): void; - /** Assign a data to the global graph input */ - setInputData(name: string, data: any): void; - /** Returns the current value of a global graph input */ - getInputData(name: string): T; - /** Changes the name of a global graph input */ - renameInput(old_name: string, name: string): false | undefined; - /** Changes the type of a global graph input */ - changeInputType(name: string, type: string): false | undefined; - /** Removes a global graph input */ - removeInput(name: string): boolean; - /** Creates a global graph output */ - addOutput(name: string, type: string, value: any): void; - /** Assign a data to the global output */ - setOutputData(name: string, value: string): void; - /** Returns the current value of a global graph output */ - getOutputData(name: string): T; - - /** Renames a global graph output */ - renameOutput(old_name: string, name: string): false | undefined; - /** Changes the type of a global graph output */ - changeOutputType(name: string, type: string): false | undefined; - /** Removes a global graph output */ - removeOutput(name: string): boolean; - triggerInput(name: string, value: any): void; - setCallback(name: string, func: (...args: any[]) => any): void; - beforeChange(info?: LGraphNode): void; - afterChange(info?: LGraphNode): void; - connectionChange(node: LGraphNode): void; - /** returns if the graph is in live mode */ - isLive(): boolean; - /** clears the triggered slot animation in all links (stop visual animation) */ - clearTriggeredSlots(): void; - /* Called when something visually changed (not the graph!) */ - change(): void; - setDirtyCanvas(fg: boolean, bg?: boolean): void; - /** Destroys a link */ - removeLink(link_id: number): void; - /** Creates a Object containing all the info about this graph, it can be serialized */ - serialize(option?: { sortNodes: boolean }): T; - /** - * Configure a graph from a JSON string - * @param data configure a graph from a JSON string - * @returns if there was any error parsing - */ - configure(data: object, keep_old?: boolean): boolean | undefined; - load(url: string): void; -} - -export type SerializedLLink = [number, string, number, number, number, number]; -export declare class LLink { - id: number; - type: string; - origin_id: number; - origin_slot: number; - target_id: number; - target_slot: number; - constructor( - id: number, - type: string, - origin_id: number, - origin_slot: number, - target_id: number, - target_slot: number - ); - configure(o: LLink | SerializedLLink): void; - serialize(): SerializedLLink; -} - -export type SerializedLGraphNode = { - id: T["id"]; - type: T["type"]; - pos: T["pos"]; - size: T["size"]; - flags: T["flags"]; - mode: T["mode"]; - inputs: T["inputs"]; - outputs: T["outputs"]; - title: T["title"]; - properties: T["properties"]; - widgets_values?: IWidget["value"][]; -}; - -/** https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#lgraphnode */ -export declare class LGraphNode { - onResize?: Function; - - // Used in group node - setInnerNodes(nodes: LGraphNode[]); - - static title_color: string; - static title: string; - static type: null | string; - static widgets_up: boolean; - constructor(title?: string); - - title: string; - type: null | string; - size: Vector2; - badges: (LGraphBadge | (() => LGraphBadge))[]; - badgePosition: BadgePosition; - graph: null | LGraph; - graph_version: number; - pos: Vector2; - is_selected: boolean; - mouseOver: boolean; - - id: number; - - //inputs available: array of inputs - inputs: INodeInputSlot[]; - outputs: INodeOutputSlot[]; - connections: any[]; - - //local data - properties: Record; - properties_info: any[]; - - flags: Partial<{ - collapsed: boolean - }>; - - color: string; - bgcolor: string; - boxcolor: string; - shape: - | typeof LiteGraph.BOX_SHAPE - | typeof LiteGraph.ROUND_SHAPE - | typeof LiteGraph.CIRCLE_SHAPE - | typeof LiteGraph.CARD_SHAPE - | typeof LiteGraph.ARROW_SHAPE; - - serialize_widgets: boolean; - skip_list: boolean; - - /** Used in `LGraphCanvas.onMenuNodeMode` */ - mode?: - | typeof LiteGraph.ON_EVENT - | typeof LiteGraph.ON_TRIGGER - | typeof LiteGraph.NEVER - | typeof LiteGraph.ALWAYS; - - widgets: IWidget[]; - - /** If set to true widgets do not start after the slots */ - widgets_up: boolean; - /** widgets start at y distance from the top of the node */ - widgets_start_y: number; - /** if you render outside the node, it will be clipped */ - clip_area: boolean; - /** if set to false it wont be resizable with the mouse */ - resizable: boolean; - /** slots are distributed horizontally */ - horizontal: boolean; - /** if true, the node will show the bgcolor as 'red' */ - has_errors?: boolean; - - /** configure a node from an object containing the serialized info */ - configure(info: SerializedLGraphNode): void; - /** serialize the content */ - serialize(): SerializedLGraphNode; - /** Creates a clone of this node */ - clone(): this; - /** serialize and stringify */ - toString(): string; - /** get the title string */ - getTitle(): string; - /** sets the value of a property */ - setProperty(name: string, value: any): void; - /** sets the output data */ - setOutputData(slot: number, data: any): void; - /** sets the output data */ - setOutputDataType(slot: number, type: string): void; - /** - * Retrieves the input data (data traveling through the connection) from one slot - * @param slot - * @param force_update if set to true it will force the connected node of this slot to output data into this link - * @return data or if it is not connected returns undefined - */ - getInputData(slot: number, force_update?: boolean): T; - /** - * Retrieves the input data type (in case this supports multiple input types) - * @param slot - * @return datatype in string format - */ - getInputDataType(slot: number): string; - /** - * Retrieves the input data from one slot using its name instead of slot number - * @param slot_name - * @param force_update if set to true it will force the connected node of this slot to output data into this link - * @return data or if it is not connected returns null - */ - getInputDataByName(slot_name: string, force_update?: boolean): T; - /** tells you if there is a connection in one input slot */ - isInputConnected(slot: number): boolean; - /** tells you info about an input connection (which node, type, etc) */ - getInputInfo( - slot: number - ): { link: number; name: string; type: string | 0 } | null; - /** Returns the link info in the connection of an input slot */ - getInputLink(slot: number): LLink | null; - /** returns the node connected in the input slot */ - getInputNode(slot: number): LGraphNode | null; - /** returns the value of an input with this name, otherwise checks if there is a property with that name */ - getInputOrProperty(name: string): T; - /** tells you the last output data that went in that slot */ - getOutputData(slot: number): T | null; - /** tells you info about an output connection (which node, type, etc) */ - getOutputInfo( - slot: number - ): { name: string; type: string; links: number[] } | null; - /** tells you if there is a connection in one output slot */ - isOutputConnected(slot: number): boolean; - /** tells you if there is any connection in the output slots */ - isAnyOutputConnected(): boolean; - /** retrieves all the nodes connected to this output slot */ - getOutputNodes(slot: number): LGraphNode[]; - /** Triggers an event in this node, this will trigger any output with the same name */ - trigger(action: string, param: any): void; - /** - * Triggers an slot event in this node - * @param slot the index of the output slot - * @param param - * @param link_id in case you want to trigger and specific output link in a slot - */ - triggerSlot(slot: number, param: any, link_id?: number): void; - /** - * clears the trigger slot animation - * @param slot the index of the output slot - * @param link_id in case you want to trigger and specific output link in a slot - */ - clearTriggeredSlot(slot: number, link_id?: number): void; - /** changes node size and triggers callback */ - setSize(size: Vector2): void; - /** - * add a new property to this node - * @param name - * @param default_value - * @param type string defining the output type ("vec3","number",...) - * @param extra_info this can be used to have special properties of the property (like values, etc) - */ - addProperty( - name: string, - default_value: any, - type: string, - extra_info?: object - ): T; - /** - * add a new output slot to use in this node - * @param name - * @param type string defining the output type ("vec3","number",...) - * @param extra_info this can be used to have special properties of an output (label, special color, position, etc) - */ - addOutput( - name: string, - type: string | -1, - extra_info?: Partial - ): INodeOutputSlot; - /** - * add a new output slot to use in this node - * @param array of triplets like [[name,type,extra_info],[...]] - */ - addOutputs( - array: [string, string | -1, Partial | undefined][] - ): void; - /** remove an existing output slot */ - removeOutput(slot: number): void; - /** - * add a new input slot to use in this node - * @param name - * @param type string defining the input type ("vec3","number",...), it its a generic one use 0 - * @param extra_info this can be used to have special properties of an input (label, color, position, etc) - */ - addInput( - name: string, - type: string | -1, - extra_info?: Partial - ): INodeInputSlot; - /** - * add several new input slots in this node - * @param array of triplets like [[name,type,extra_info],[...]] - */ - addInputs( - array: [string, string | -1, Partial | undefined][] - ): void; - /** remove an existing input slot */ - removeInput(slot: number): void; - /** - * add an special connection to this node (used for special kinds of graphs) - * @param name - * @param type string defining the input type ("vec3","number",...) - * @param pos position of the connection inside the node - * @param direction if is input or output - */ - addConnection( - name: string, - type: string, - pos: Vector2, - direction: string - ): { - name: string; - type: string; - pos: Vector2; - direction: string; - links: null; - }; - /** computes the minimum size of a node according to its inputs and output slots */ - computeSize(minHeight?: Vector2): Vector2; - /** returns all the info available about a property of this node */ - getPropertyInfo(property: string): object; - /** - * https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#node-widgets - * @return created widget - */ - addWidget( - type: T["type"], - name: string, - value: T["value"], - callback?: WidgetCallback | string, - options?: T["options"] - ): T; - - addCustomWidget(customWidget: T): T; - - /** - * returns the bounding of the object, used for rendering purposes - * @method getBounding - * @param out [optional] a place to store the output, to free garbage - * @param compute_outer [optional] set to true to include the shadow and connection points in the bounding calculation - * @return the bounding box in format of [topleft_cornerx, topleft_cornery, width, height] - */ - getBounding(out?: Vector4, compute_outer?: boolean): Vector4; - /** checks if a point is inside the shape of a node */ - isPointInside( - x: number, - y: number, - margin?: number, - skipTitle?: boolean - ): boolean; - /** checks if a point is inside a node slot, and returns info about which slot */ - getSlotInPosition( - x: number, - y: number - ): { - input?: INodeInputSlot; - output?: INodeOutputSlot; - slot: number; - link_pos: Vector2; - }; - /** - * returns the input slot with a given name (used for dynamic slots), -1 if not found - * @param name the name of the slot - * @return the slot (-1 if not found) - */ - findInputSlot(name: string): number; - /** - * returns the output slot with a given name (used for dynamic slots), -1 if not found - * @param name the name of the slot - * @return the slot (-1 if not found) - */ - findOutputSlot(name: string): number; - /** - * connect this node output to the input of another node - * @param slot (could be the number of the slot or the string with the name of the slot) - * @param targetNode the target node - * @param targetSlot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) - * @return {Object} the link_info is created, otherwise null - */ - connect( - slot: number | string, - targetNode: LGraphNode, - targetSlot: number | string - ): T | null; - /** - * disconnect one output to an specific node - * @param slot (could be the number of the slot or the string with the name of the slot) - * @param target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] - * @return if it was disconnected successfully - */ - disconnectOutput(slot: number | string, targetNode?: LGraphNode): boolean; - /** - * disconnect one input - * @param slot (could be the number of the slot or the string with the name of the slot) - * @return if it was disconnected successfully - */ - disconnectInput(slot: number | string): boolean; - /** - * returns the center of a connection point in canvas coords - * @param is_input true if if a input slot, false if it is an output - * @param slot (could be the number of the slot or the string with the name of the slot) - * @param out a place to store the output, to free garbage - * @return the position - **/ - getConnectionPos( - is_input: boolean, - slot: number | string, - out?: Vector2 - ): Vector2; - /** Force align to grid */ - alignToGrid(): void; - /** Console output */ - trace(msg: string): void; - /** Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ - setDirtyCanvas(fg: boolean, bg: boolean): void; - loadImage(url: string): void; - /** Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ - captureInput(v: any): void; - - get collapsed(): boolean; - get collapsible(): boolean; - /** Collapse the node to make it smaller on the canvas */ - collapse(force: boolean): void; - - get pinned(): boolean; - /** Forces the node to do not move or realign on Z */ - pin(v?: boolean): void; - localToScreen(x: number, y: number, graphCanvas: LGraphCanvas): Vector2; - - // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-appearance - onDrawBackground?( - ctx: CanvasRenderingContext2D, - canvas: HTMLCanvasElement - ): void; - onDrawForeground?( - ctx: CanvasRenderingContext2D, - canvas: HTMLCanvasElement - ): void; - - // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-behaviour - onMouseDown?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseMove?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseUp?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseEnter?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseLeave?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onKey?(event: KeyboardEvent, pos: Vector2, graphCanvas: LGraphCanvas): void; - - onDblClick?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - - onNodeTitleDblClick?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - - onInputDblClick?( - slot: number, - event: MouseEvent, - graphCanvas: LGraphCanvas - ): void; - - onOutputDblClick?( - slot: number, - event: MouseEvent, - graphCanvas: LGraphCanvas - ): void; - - /** Called by `LGraphCanvas.selectNodes` */ - onSelected?(): void; - /** Called by `LGraphCanvas.deselectNode` */ - onDeselected?(): void; - /** Called by `LGraph.runStep` `LGraphNode.getInputData` */ - onExecute?(): void; - /** Called by `LGraph.serialize` */ - onSerialize?(o: SerializedLGraphNode): void; - /** Called by `LGraph.configure` */ - onConfigure?(o: SerializedLGraphNode): void; - /** - * when added to graph (warning: this is called BEFORE the node is configured when loading) - * Called by `LGraph.add` - */ - onAdded?(graph: LGraph): void; - /** - * when removed from graph - * Called by `LGraph.remove` `LGraph.clear` - */ - onRemoved?(): void; - /** - * if returns false the incoming connection will be canceled - * Called by `LGraph.connect` - * @param inputIndex target input slot number - * @param outputType type of output slot - * @param outputSlot output slot object - * @param outputNode node containing the output - * @param outputIndex index of output slot - */ - onConnectInput?( - inputIndex: number, - outputType: INodeOutputSlot["type"], - outputSlot: INodeOutputSlot, - outputNode: LGraphNode, - outputIndex: number - ): boolean; - /** - * if returns false the incoming connection will be canceled - * Called by `LGraph.connect` - * @param outputIndex target output slot number - * @param inputType type of input slot - * @param inputSlot input slot object - * @param inputNode node containing the input - * @param inputIndex index of input slot - */ - onConnectOutput?( - outputIndex: number, - inputType: INodeInputSlot["type"], - inputSlot: INodeInputSlot, - inputNode: LGraphNode, - inputIndex: number - ): boolean; - - /** - * Called just before connection (or disconnect - if input is linked). - * A convenient place to switch to another input, or create new one. - * This allow for ability to automatically add slots if needed - * @param inputIndex - * @return selected input slot index, can differ from parameter value - */ - onBeforeConnectInput?( - inputIndex: number - ): number; - - /** a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info or output_info ) */ - onConnectionsChange( - type: number, - slotIndex: number, - isConnected: boolean, - link: LLink, - ioSlot: (INodeOutputSlot | INodeInputSlot) - ): void; - - /** - * if returns false, will abort the `LGraphNode.setProperty` - * Called when a property is changed - * @param property - * @param value - * @param prevValue - */ - onPropertyChanged?(property: string, value: any, prevValue: any): void | boolean; - - /** Called by `LGraphCanvas.processContextMenu` */ - getMenuOptions?(graphCanvas: LGraphCanvas): ContextMenuItem[]; - getSlotMenuOptions?(slot: INodeSlot): ContextMenuItem[]; - - get width(): number; - get height(): number; - drawBadges?(ctx: CanvasRenderingContext2D, options: { gap?: number }): void; -} - -export type LGraphNodeConstructor = { - nodeData: any; // Used by group node. - new(): T; -}; - -export type SerializedLGraphGroup = { - title: LGraphGroup["title"]; - bounding: LGraphGroup["_bounding"]; - color: LGraphGroup["color"]; - font: LGraphGroup["font"]; -}; -export declare class LGraphGroup { - title: string; - private _bounding: Vector4; - color: string; - font: string; - size: Vector2; - pos: Vector2; - font_size: number; - flags: Record; - - get nodes(): LGraphNode[]; - get titleHeight(): number; - get selected(): boolean; - - // Pinned group cannot be selected. - get pinned(): boolean; - pin(): void; - unpin(): void; - - configure(o: SerializedLGraphGroup): void; - serialize(): SerializedLGraphGroup; - resize(width: number, height: number): void; - move(deltaX: number, deltaY: number, ignoreNodes?: boolean): void; - recomputeInsideNodes(): void; - isPointInside: LGraphNode["isPointInside"]; - setDirtyCanvas: LGraphNode["setDirtyCanvas"]; - addNodes(nodes: LGraphNode[], padding?: number): void; - getMenuOptions(): ContextMenuItem[]; -} - -export declare class DragAndScale { - constructor(element?: HTMLElement, skipEvents?: boolean); - offset: [number, number]; - scale: number; - max_scale: number; - min_scale: number; - onredraw: Function | null; - enabled: boolean; - last_mouse: Vector2; - element: HTMLElement | null; - visible_area: Vector4; - bindEvents(element: HTMLElement): void; - computeVisibleArea(): void; - onMouse(e: MouseEvent): void; - toCanvasContext(ctx: CanvasRenderingContext2D): void; - convertOffsetToCanvas(pos: Vector2): Vector2; - convertCanvasToOffset(pos: Vector2): Vector2; - mouseDrag(x: number, y: number): void; - changeScale(value: number, zooming_center?: Vector2): void; - changeDeltaScale(value: number, zooming_center?: Vector2): void; - reset(): void; -} - -/** - * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. - * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked - * - * @param canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) - * @param graph - * @param options { skip_rendering, autoresize } - */ -export declare class LGraphCanvas { - static node_colors: Record< - string, - { - color: string; - bgcolor: string; - groupcolor: string; - } - >; - static link_type_colors: Record; - static gradients: object; - static search_limit: number; - - static getFileExtension(url: string): string; - static decodeHTML(str: string): string; - - static onMenuCollapseAll(): void; - static onMenuNodeEdit(): void; - static onShowPropertyEditor( - item: any, - options: any, - e: any, - menu: any, - node: any - ): void; - /** Create menu for `Add Group` */ - static onGroupAdd: ContextMenuEventListener; - /** Create menu for `Add Node` */ - static onMenuAdd: ContextMenuEventListener; - static showMenuNodeOptionalInputs: ContextMenuEventListener; - static showMenuNodeOptionalOutputs: ContextMenuEventListener; - static onShowMenuNodeProperties: ContextMenuEventListener; - static onResizeNode: ContextMenuEventListener; - static onMenuNodeCollapse: ContextMenuEventListener; - static onMenuNodePin: ContextMenuEventListener; - static onMenuNodeMode: ContextMenuEventListener; - static onMenuNodeColors: ContextMenuEventListener; - static onMenuNodeShapes: ContextMenuEventListener; - static onMenuNodeRemove: ContextMenuEventListener; - static onMenuNodeClone: ContextMenuEventListener; - - constructor( - canvas: HTMLCanvasElement | string, - graph?: LGraph, - options?: { - skip_render?: boolean; - autoresize?: boolean; - } - ); - - static active_canvas: HTMLCanvasElement; - - allow_dragcanvas: boolean; - allow_dragnodes: boolean; - /** allow to control widgets, buttons, collapse, etc */ - allow_interaction: boolean; - /** allows to change a connection with having to redo it again */ - allow_reconnect_links: boolean; - /** allow selecting multi nodes without pressing extra keys */ - multi_select: boolean; - /** No effect */ - allow_searchbox: boolean; - always_render_background: boolean; - autoresize?: boolean; - background_image: string; - bgcanvas: HTMLCanvasElement; - bgctx: CanvasRenderingContext2D; - canvas: HTMLCanvasElement; - canvas_mouse: Vector2; - clear_background: boolean; - connecting_node: LGraphNode | null; - connections_width: number; - ctx: CanvasRenderingContext2D; - current_node: LGraphNode | null; - default_connection_color: { - input_off: string; - input_on: string; - output_off: string; - output_on: string; - }; - default_link_color: string; - dirty_area: Vector4 | null; - dirty_bgcanvas?: boolean; - dirty_canvas?: boolean; - drag_mode: boolean; - dragging_canvas: boolean; - dragging_rectangle: Vector4 | null; - ds: DragAndScale; - /** used for transition */ - editor_alpha: number; - filter: any; - fps: number; - frame: number; - graph: LGraph; - highlighted_links: Record; - highquality_render: boolean; - inner_text_font: string; - is_rendering: boolean; - last_draw_time: number; - last_mouse: Vector2; - /** - * Possible duplicated with `last_mouse` - * https://github.com/jagenjo/litegraph.js/issues/70 - */ - last_mouse_position: Vector2; - /** Timestamp of last mouse click, defaults to 0 */ - last_mouseclick: number; - links_render_mode: - | typeof LiteGraph.STRAIGHT_LINK - | typeof LiteGraph.LINEAR_LINK - | typeof LiteGraph.SPLINE_LINK - | typeof LiteGraph.HIDDEN_LINK - | number; // Custom render mode - live_mode: boolean; - node_capturing_input: LGraphNode | null; - node_dragged: LGraphNode | null; - node_in_panel: LGraphNode | null; - node_over: LGraphNode | null; - node_title_color: string; - node_widget: [LGraphNode, IWidget] | null; - last_mouse_dragging: boolean; - /** Implement this function to allow conversion of widget types to input types, e.g. number -> INT or FLOAT for widget link validation checks */ - getWidgetLinkType?: (widget: IWidget, node: LGraphNode) => string | null | undefined; - - /** Called by `LGraphCanvas.drawBackCanvas` */ - onDrawBackground: - | ((ctx: CanvasRenderingContext2D, visibleArea: Vector4) => void) - | null; - /** Called by `LGraphCanvas.drawFrontCanvas` */ - onDrawForeground: - | ((ctx: CanvasRenderingContext2D, visibleArea: Vector4) => void) - | null; - onDrawOverlay: ((ctx: CanvasRenderingContext2D) => void) | null; - /** Called by `LGraphCanvas.processMouseDown` */ - onMouse: ((event: MouseEvent) => boolean) | null; - /** Called by `LGraphCanvas.drawFrontCanvas` and `LGraphCanvas.drawLinkTooltip` */ - onDrawLinkTooltip: ((ctx: CanvasRenderingContext2D, link: LLink, _this: this) => void) | null; - /** Called by `LGraphCanvas.selectNodes` */ - onNodeMoved: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.processNodeSelected` */ - onNodeSelected: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.deselectNode` */ - onNodeDeselected: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.processNodeDblClicked` */ - onShowNodePanel: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.processNodeDblClicked` */ - onNodeDblClicked: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.selectNodes` */ - onSelectionChange: ((nodes: Record) => void) | null; - /** Called by `LGraphCanvas.showSearchBox` */ - onSearchBox: - | (( - helper: Element, - value: string, - graphCanvas: LGraphCanvas - ) => string[]) - | null; - onSearchBoxSelection: - | ((name: string, event: MouseEvent, graphCanvas: LGraphCanvas) => void) - | null; - pause_rendering: boolean; - render_canvas_border: boolean; - render_collapsed_slots: boolean; - render_connection_arrows: boolean; - render_connections_border: boolean; - render_connections_shadows: boolean; - render_curved_connections: boolean; - render_execution_order: boolean; - render_only_selected: boolean; - render_shadows: boolean; - render_title_colored: boolean; - round_radius: number; - selected_group: null | LGraphGroup; - selected_group_resizing: boolean; - selected_nodes: Record; - show_info: boolean; - title_text_font: string; - /** set to true to render title bar with gradients */ - use_gradients: boolean; - visible_area: DragAndScale["visible_area"]; - visible_links: LLink[]; - visible_nodes: LGraphNode[]; - zoom_modify_alpha: boolean; - zoom_speed: number; - //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle - mouse: Vector2; - //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle - graph_mouse: Vector2; - - pointer_is_down?: boolean; - read_only: boolean; - - /** clears all the data inside */ - clear(): void; - /** assigns a graph, you can reassign graphs to the same canvas */ - setGraph(graph: LGraph, skipClear?: boolean): void; - /** opens a graph contained inside a node in the current graph */ - openSubgraph(graph: LGraph): void; - /** closes a subgraph contained inside a node */ - closeSubgraph(): void; - /** assigns a canvas */ - setCanvas(canvas: HTMLCanvasElement, skipEvents?: boolean): void; - /** binds mouse, keyboard, touch and drag events to the canvas */ - bindEvents(): void; - /** unbinds mouse events from the canvas */ - unbindEvents(): void; - - /** - * this function allows to render the canvas using WebGL instead of Canvas2D - * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL - **/ - enableWebGL(): void; - - /** - * marks as dirty the canvas, this way it will be rendered again - * @param fg if the foreground canvas is dirty (the one containing the nodes) - * @param bg if the background canvas is dirty (the one containing the wires) - */ - setDirty(fg: boolean, bg: boolean): void; - - /** - * Used to attach the canvas in a popup - * @return the window where the canvas is attached (the DOM root node) - */ - getCanvasWindow(): Window; - /** starts rendering the content of the canvas when needed */ - startRendering(): void; - /** stops rendering the content of the canvas (to save resources) */ - stopRendering(): void; - - processMouseDown(e: MouseEvent): boolean | undefined; - processMouseMove(e: MouseEvent): boolean | undefined; - processMouseUp(e: MouseEvent): boolean | undefined; - processMouseWheel(e: MouseEvent): boolean | undefined; - - /** returns true if a position (in graph space) is on top of a node little corner box */ - isOverNodeBox(node: LGraphNode, canvasX: number, canvasY: number): boolean; - /** returns true if a position (in graph space) is on top of a node input slot */ - isOverNodeInput( - node: LGraphNode, - canvasX: number, - canvasY: number, - slotPos: Vector2 - ): boolean; - - /** process a key event */ - processKey(e: KeyboardEvent): boolean | undefined; - - copyToClipboard(): void; - pasteFromClipboard(): void; - processDrop(e: DragEvent): void; - checkDropItem(e: DragEvent): void; - processNodeDblClicked(n: LGraphNode): void; - processNodeSelected(n: LGraphNode, e: MouseEvent): void; - processNodeDeselected(node: LGraphNode): void; - - /** selects a given node (or adds it to the current selection) */ - selectNode(node: LGraphNode, add?: boolean): void; - /** selects several nodes (or adds them to the current selection) */ - selectNodes(nodes?: LGraphNode[], add?: boolean): void; - /** removes a node from the current selection */ - deselectNode(node: LGraphNode): void; - /** removes all nodes from the current selection */ - deselectAllNodes(): void; - /** deletes all nodes in the current selection from the graph */ - deleteSelectedNodes(): void; - - /** centers the camera on a given node */ - centerOnNode(node: LGraphNode): void; - /** changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom */ - setZoom(value: number, center: Vector2): void; - /** brings a node to front (above all other nodes) */ - bringToFront(node: LGraphNode): void; - /** sends a node to the back (below all other nodes) */ - sendToBack(node: LGraphNode): void; - /** checks which nodes are visible (inside the camera area) */ - computeVisibleNodes(nodes: LGraphNode[]): LGraphNode[]; - /** renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) */ - draw(forceFG?: boolean, forceBG?: boolean): void; - /** draws the front canvas (the one containing all the nodes) */ - drawFrontCanvas(): void; - /** draws some useful stats in the corner of the canvas */ - renderInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void; - /** draws the back canvas (the one containing the background and the connections) */ - drawBackCanvas(): void; - /** draws the given node inside the canvas */ - drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void; - /** draws graphic for node's slot */ - drawSlotGraphic(ctx: CanvasRenderingContext2D, pos: number[], shape: SlotShape, horizontal: boolean): void; - /** draws the shape of the given node in the canvas */ - drawNodeShape( - node: LGraphNode, - ctx: CanvasRenderingContext2D, - size: [number, number], - fgColor: string, - bgColor: string, - selected: boolean, - mouseOver: boolean - ): void; - drawSelectionBounding(ctx: CanvasRenderingContext2D, area: Vector4, options?: { - shape?: SlotShape, - title_height?: number, - title_mode?: SlotTitleMode, - fgcolor?: string, - padding?: number, - }): void; - /** draws every connection visible in the canvas */ - drawConnections(ctx: CanvasRenderingContext2D): void; - /** - * draws a link between two points - * @param a start pos - * @param b end pos - * @param link the link object with all the link info - * @param skipBorder ignore the shadow of the link - * @param flow show flow animation (for events) - * @param color the color for the link - * @param startDir the direction enum - * @param endDir the direction enum - * @param numSublines number of sublines (useful to represent vec3 or rgb) - **/ - renderLink( - a: Vector2, - b: Vector2, - link: object, - skipBorder: boolean, - flow: boolean, - color?: string, - startDir?: number, - endDir?: number, - numSublines?: number - ): void; - - computeConnectionPoint( - a: Vector2, - b: Vector2, - t: number, - startDir?: number, - endDir?: number - ): void; - - drawExecutionOrder(ctx: CanvasRenderingContext2D): void; - /** draws the widgets stored inside a node */ - drawNodeWidgets( - node: LGraphNode, - posY: number, - ctx: CanvasRenderingContext2D, - activeWidget: object - ): void; - /** process an event on widgets */ - processNodeWidgets( - node: LGraphNode, - pos: Vector2, - event: Event, - activeWidget: object - ): void; - /** draws every group area in the background */ - drawGroups(canvas: any, ctx: CanvasRenderingContext2D): void; - adjustNodesSize(): void; - /** resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode */ - resize(width?: number, height?: number): void; - /** - * switches to live mode (node shapes are not rendered, only the content) - * this feature was designed when graphs where meant to create user interfaces - **/ - switchLiveMode(transition?: boolean): void; - onNodeSelectionChange(): void; - touchHandler(event: TouchEvent): void; - - showLinkMenu(link: LLink, e: any): false; - prompt( - title: string, - value: any, - callback: Function, - event: any - ): HTMLDivElement; - showSearchBox(event?: MouseEvent, options?: LinkReleaseContext): void; - showConnectionMenu(optPass?: { - nodeFrom?: LGraphNode; - slotFrom?: INodeSlot | string | number; - nodeTo?: LGraphNode; - slotTo?: INodeSlot | string | number; - e?: MouseEvent; - allow_searchbox?: boolean; - showSearchBox?: (event?: MouseEvent, options?: LinkReleaseContext) => void; - }): void; - showEditPropertyValue(node: LGraphNode, property: any, options: any): void; - createDialog( - html: string, - options?: { position?: Vector2; event?: MouseEvent } - ): void; - - convertOffsetToCanvas: DragAndScale["convertOffsetToCanvas"]; - convertCanvasToOffset: DragAndScale["convertCanvasToOffset"]; - /** converts event coordinates from canvas2D to graph coordinates */ - convertEventToCanvasOffset(e: MouseEvent): Vector2; - /** adds some useful properties to a mouse event, like the position in graph coordinates */ - adjustMouseEvent(e: MouseEvent): void; - - getCanvasMenuOptions(): ContextMenuItem[]; - getNodeMenuOptions(node: LGraphNode): ContextMenuItem[]; - getGroupMenuOptions(): ContextMenuItem[]; - /** Called by `getCanvasMenuOptions`, replace default options */ - getMenuOptions?(): ContextMenuItem[]; - /** Called by `getCanvasMenuOptions`, append to default options */ - getExtraMenuOptions?(): ContextMenuItem[]; - /** Called when mouse right click */ - processContextMenu(node: LGraphNode, event: Event): void; -} - -export declare class ContextMenu { - static trigger( - element: HTMLElement, - event_name: string, - params: any, - origin: any - ): void; - static isCursorOverElement(event: MouseEvent, element: HTMLElement): void; - static closeAllContextMenus(window: Window): void; - constructor(values: ContextMenuItem[], options?: IContextMenuOptions, window?: Window); - options: IContextMenuOptions; - parentMenu?: ContextMenu; - lock: boolean; - current_submenu?: ContextMenu; - addItem( - name: string, - value: ContextMenuItem, - options?: IContextMenuOptions - ): void; - close(e?: MouseEvent, ignore_parent_menu?: boolean): void; - getTopMenu(): void; - getFirstEvent(): void; -} - -export declare function clamp(v: number, min: number, max: number): number; - -export { LGraphBadge, BadgePosition } from "./LGraphBadge"; diff --git a/src/ContextMenu.ts b/src/ContextMenu.ts index afa3de1fb..41c4dc52a 100644 --- a/src/ContextMenu.ts +++ b/src/ContextMenu.ts @@ -1,4 +1,5 @@ // @ts-nocheck +import type { IContextMenuOptions, IContextMenuValue } from "./interfaces" import { LiteGraph } from "./litegraph"; /* LiteGraph GUI elements used for canvas editing *************************************/ @@ -15,7 +16,13 @@ import { LiteGraph } from "./litegraph"; * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position */ export class ContextMenu { - constructor(values, options) { + options?: IContextMenuOptions + parentMenu?: ContextMenu + root: HTMLDivElement + current_submenu?: ContextMenu + lock?: boolean + + constructor(values: IContextMenuValue[] | string[], options: IContextMenuOptions) { options = options || {}; this.options = options; var that = this; @@ -206,7 +213,7 @@ export class ContextMenu { } } - addItem(name, value, options) { + addItem(name: string, value: IContextMenuValue, options: IContextMenuOptions): HTMLElement { var that = this; options = options || {}; @@ -347,7 +354,7 @@ export class ContextMenu { return element; } - close(e, ignore_parent_menu) { + close(e?: MouseEvent, ignore_parent_menu?: boolean): void { if (this.root.parentNode) { this.root.parentNode.removeChild(this.root); } @@ -374,7 +381,7 @@ export class ContextMenu { } //this code is used to trigger events easily (used in the context menu mouseleave - static trigger(element, event_name, params, origin) { + static trigger(element: HTMLDivElement, event_name: string, params: MouseEvent, origin?: undefined) { var evt = document.createEvent("CustomEvent"); evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail evt.srcElement = origin; @@ -388,21 +395,21 @@ export class ContextMenu { } //returns the top most menu - getTopMenu() { + getTopMenu(): ContextMenu { if (this.options.parentMenu) { return this.options.parentMenu.getTopMenu(); } return this; } - getFirstEvent() { + getFirstEvent(): MouseEvent { if (this.options.parentMenu) { return this.options.parentMenu.getFirstEvent(); } return this.options.event; } - static isCursorOverElement(event, element) { + static isCursorOverElement(event: MouseEvent, element: HTMLDivElement): boolean { var left = event.clientX; var top = event.clientY; var rect = element.getBoundingClientRect(); diff --git a/src/DragAndScale.ts b/src/DragAndScale.ts index 5744c14e6..b79f843fd 100644 --- a/src/DragAndScale.ts +++ b/src/DragAndScale.ts @@ -1,11 +1,27 @@ -// @ts-nocheck +import type { Point, Rect, Rect32 } from "./interfaces" import { LiteGraph } from "./litegraph"; //**************************************** //Scale and Offset export class DragAndScale { - constructor(element, skip_events) { + max_scale: number + min_scale: number + offset: Point + scale: number + enabled: boolean + last_mouse: Point + element?: HTMLCanvasElement + visible_area: Rect32 + _binded_mouse_callback + dragging?: boolean + viewport?: Rect + + onredraw?(das: DragAndScale): void + /** @deprecated */ + onmouse?(e: any): boolean + + constructor(element?: HTMLCanvasElement, skip_events?: boolean) { this.offset = new Float32Array([0, 0]); this.scale = 1; this.max_scale = 10; @@ -41,7 +57,7 @@ export class DragAndScale { element.addEventListener("wheel", this._binded_mouse_callback, false); } - computeVisibleArea(viewport) { + computeVisibleArea(viewport: Rect): void { if (!this.element) { this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; return; @@ -134,12 +150,12 @@ export class DragAndScale { } } - toCanvasContext(ctx) { + toCanvasContext(ctx: CanvasRenderingContext2D): void { ctx.scale(this.scale, this.scale); ctx.translate(this.offset[0], this.offset[1]); } - convertOffsetToCanvas(pos) { + convertOffsetToCanvas(pos: Point): Point { //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]]; return [ (pos[0] + this.offset[0]) * this.scale, @@ -147,7 +163,7 @@ export class DragAndScale { ]; } - convertCanvasToOffset(pos, out) { + convertCanvasToOffset(pos: Point, out?: Point): Point { out = out || [0, 0]; out[0] = pos[0] / this.scale - this.offset[0]; out[1] = pos[1] / this.scale - this.offset[1]; @@ -163,7 +179,7 @@ export class DragAndScale { } } - changeScale(value, zooming_center) { + changeScale(value: number, zooming_center?: Point): void { if (value < this.min_scale) { value = this.min_scale; } else if (value > this.max_scale) { @@ -207,11 +223,11 @@ export class DragAndScale { } } - changeDeltaScale(value, zooming_center) { + changeDeltaScale(value: number, zooming_center?: Point) { this.changeScale(this.scale * value, zooming_center); } - reset() { + reset(): void { this.scale = 1; this.offset[0] = 0; this.offset[1] = 0; diff --git a/src/LGraph.ts b/src/LGraph.ts index a91102ac9..e3a889728 100644 --- a/src/LGraph.ts +++ b/src/LGraph.ts @@ -1,13 +1,21 @@ // @ts-nocheck +import type { Dictionary, IContextMenuValue, ISlotType, MethodNames, Point } from "./interfaces" +import type { ISerialisedGraph } from "@/types/serialisation" +import type { LGraphEventMode } from "./types/globalEnums" import { LiteGraph } from "./litegraph"; import { LGraphCanvas } from "./LGraphCanvas"; import { LGraphGroup } from "./LGraphGroup"; -import { LGraphNode } from "./LGraphNode"; -import { LLink } from "./LLink"; +import { type NodeId, LGraphNode } from "./LGraphNode"; +import { type LinkId, LLink } from "./LLink"; + +interface IGraphInput { + name: string + type: string + value?: unknown +} + +type ParamsArray, K extends MethodNames> = Parameters[1] extends undefined ? Parameters | Parameters[0] : Parameters -//********************************************************************************* -// 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: @@ -26,7 +34,71 @@ export class LGraph { static STATUS_STOPPED = 1; static STATUS_RUNNING = 2; - constructor(o) { + _version: number + links: Record + list_of_graphcanvas?: LGraphCanvas[] + status: number + last_node_id: number + last_link_id: number + /** The largest ID created by this graph */ + last_reroute_id: number + _nodes: LGraphNode[] + _nodes_by_id: Record + _nodes_in_order: LGraphNode[] + _nodes_executable: LGraphNode[] + _groups: LGraphGroup[] + iteration: number + globaltime: number + runningtime: number + fixedtime: number + fixedtime_lapse: number + elapsed_time: number + last_update_time: number + starttime: number + catch_errors: boolean + execution_timer_id: number + errors_in_execution: boolean + execution_time: number + _last_trigger_time?: number + filter?: string + _subgraph_node?: LGraphNode + config: { align_to_grid?: any; links_ontop?: any } + vars: Dictionary + nodes_executing: boolean[] + nodes_actioning: (string | boolean)[] + nodes_executedAction: string[] + extra: Record + inputs: Dictionary + outputs: Dictionary + onInputsOutputsChange?(): void + onInputAdded?(name: string, type: string): void + onAfterStep?(): void + onBeforeStep?(): void + onPlayEvent?(): void + onStopEvent?(): void + onAfterExecute?(): void + onExecuteStep?(): void + onNodeAdded?(node: LGraphNode): void + onNodeRemoved?(node: LGraphNode): void + onTrigger?(action: string, param: unknown): void + onInputRenamed?(old_name: string, name: string): void + onInputTypeChanged?(name: string, type: string): void + onInputRemoved?(name: string): void + onOutputAdded?(name: string, type: string): void + onOutputRenamed?(old_name: string, name: string): void + onOutputTypeChanged?(name: string, type: string): void + onOutputRemoved?(name: string): void + onBeforeChange?(graph: LGraph, info: LGraphNode): void + onAfterChange?(graph: LGraph, info: LGraphNode): void + onConnectionChange?(node: LGraphNode): void + on_change?(graph: LGraph): void + onSerialize?(data: ISerialisedGraph): void + onConfigure?(data: ISerialisedGraph): void + onGetNodeMenuOptions?(options: IContextMenuValue[], node: LGraphNode): void + + private _input_nodes?: LGraphNode[] + + constructor(o?: ISerialisedGraph) { if (LiteGraph.debug) { console.log("Graph created"); } @@ -38,14 +110,14 @@ export class LGraph { } } //used to know which types of connections support this graph (some graphs do not allow certain types) - getSupportedTypes() { + getSupportedTypes(): string[] { return this.supported_types || LGraph.supported_types; } /** * Removes all nodes from this graph * @method clear */ - clear() { + clear(): void { this.stop(); this.status = LGraph.STATUS_STOPPED; @@ -130,7 +202,7 @@ export class LGraph { * @method attachCanvas * @param {GraphCanvas} graph_canvas */ - attachCanvas(graphcanvas) { + attachCanvas(graphcanvas: LGraphCanvas): void { if (graphcanvas.constructor != LGraphCanvas) { throw "attachCanvas expects a LGraphCanvas instance"; } @@ -150,7 +222,7 @@ export class LGraph { * @method detachCanvas * @param {GraphCanvas} graph_canvas */ - detachCanvas(graphcanvas) { + detachCanvas(graphcanvas: LGraphCanvas): void { if (!this.list_of_graphcanvas) { return; } @@ -167,7 +239,7 @@ export class LGraph { * @method start * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate */ - start(interval) { + start(interval?: number): void { if (this.status == LGraph.STATUS_RUNNING) { return; } @@ -215,7 +287,7 @@ export class LGraph { * Stops the execution loop of the graph * @method stop execution */ - stop() { + stop(): void { if (this.status == LGraph.STATUS_STOPPED) { return; } @@ -242,7 +314,7 @@ export class LGraph { * @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) */ - runStep(num, do_not_catch_errors, limit) { + runStep(num: number, do_not_catch_errors: boolean, limit?: number): void { num = num || 1; var start = LiteGraph.getTime(); @@ -329,7 +401,7 @@ export class LGraph { * nodes with only inputs. * @method updateExecutionOrder */ - updateExecutionOrder() { + updateExecutionOrder(): void { this._nodes_in_order = this.computeExecutionOrder(false); this._nodes_executable = []; for (var i = 0; i < this._nodes_in_order.length; ++i) { @@ -339,8 +411,7 @@ export class LGraph { } } //This is more internal, it computes the executable nodes in order and returns it - computeExecutionOrder(only_onExecute, - set_level) { + computeExecutionOrder(only_onExecute: boolean, set_level?: boolean): LGraphNode[] { var L = []; var S = []; var M = {}; @@ -480,7 +551,7 @@ export class LGraph { * @method getAncestors * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution */ - getAncestors(node) { + getAncestors(node: LGraphNode): LGraphNode[] { var ancestors = []; var pending = [node]; var visited = {}; @@ -512,7 +583,7 @@ export class LGraph { * Positions every node in a more readable manner * @method arrange */ - arrange(margin, layout) { + arrange(margin?: number, layout?: string): void { margin = margin || 100; const nodes = this.computeExecutionOrder(false, true); @@ -556,7 +627,7 @@ export class LGraph { * @method getTime * @return {number} number of milliseconds the graph has been running */ - getTime() { + getTime(): number { return this.globaltime; } /** @@ -564,7 +635,7 @@ export class LGraph { * @method getFixedTime * @return {number} number of milliseconds the graph has been running */ - getFixedTime() { + getFixedTime(): number { return this.fixedtime; } /** @@ -573,7 +644,7 @@ export class LGraph { * @method getElapsedTime * @return {number} number of milliseconds it took the last cycle */ - getElapsedTime() { + getElapsedTime(): number { return this.elapsed_time; } /** @@ -582,7 +653,7 @@ export class LGraph { * @param {String} eventname the name of the event (function to be called) * @param {Array} params parameters in array format */ - sendEventToAllNodes(eventname, params, mode) { + sendEventToAllNodes(eventname: string, params?: object | object[], mode?: LGraphEventMode): void { mode = mode || LiteGraph.ALWAYS; var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; @@ -613,7 +684,7 @@ export class LGraph { } } } - sendActionToCanvas(action, params) { + sendActionToCanvas>(action: T, params?: ParamsArray): void { if (!this.list_of_graphcanvas) { return; } @@ -630,7 +701,7 @@ export class LGraph { * @method add * @param {LGraphNode} node the instance of the node */ - add(node, skip_compute_order) { + add(node: LGraphNode | LGraphGroup, skip_compute_order?: boolean): LGraphNode | null { if (!node) { return; } @@ -707,7 +778,7 @@ export class LGraph { * @method remove * @param {LGraphNode} node the instance of the node */ - remove(node) { + remove(node: LGraphNode | LGraphGroup): void { if (node.constructor === LiteGraph.LGraphGroup) { var index = this._groups.indexOf(node); if (index != -1) { @@ -799,7 +870,7 @@ export class LGraph { * @method getNodeById * @param {Number} id */ - getNodeById(id) { + getNodeById(id: NodeId): LGraphNode | null { if (id == null) { return null; } @@ -811,7 +882,7 @@ export class LGraph { * @param {Class} classObject the class itself (not an string) * @return {Array} a list with all the nodes of this type */ - findNodesByClass(classObject, result) { + findNodesByClass(classObject: Function, result: LGraphNode[]): LGraphNode[] { result = result || []; result.length = 0; for (var i = 0, l = this._nodes.length; i < l; ++i) { @@ -827,7 +898,7 @@ export class LGraph { * @param {String} type the name of the node type * @return {Array} a list with all the nodes of this type */ - findNodesByType(type, result) { + findNodesByType(type: string, result: LGraphNode[]): LGraphNode[] { var type = type.toLowerCase(); result = result || []; result.length = 0; @@ -844,7 +915,7 @@ export class LGraph { * @param {String} name the name of the node to search * @return {Node} the node or null */ - findNodeByTitle(title) { + findNodeByTitle(title: string): LGraphNode { for (var i = 0, l = this._nodes.length; i < l; ++i) { if (this._nodes[i].title == title) { return this._nodes[i]; @@ -858,7 +929,7 @@ export class LGraph { * @param {String} name the name of the node to search * @return {Array} a list with all the nodes with this name */ - findNodesByTitle(title) { + findNodesByTitle(title: string): LGraphNode[] { var result = []; for (var i = 0, l = this._nodes.length; i < l; ++i) { if (this._nodes[i].title == title) { @@ -875,7 +946,7 @@ export class LGraph { * @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 */ - getNodeOnPos(x, y, nodes_list, margin) { + getNodeOnPos(x: number, y: number, nodes_list?: LGraphNode[], margin?: number): LGraphNode { nodes_list = nodes_list || this._nodes; var nRet = null; for (var i = nodes_list.length - 1; i >= 0; i--) { @@ -899,7 +970,7 @@ export class LGraph { * @param {number} y the y coordinate in canvas space * @return {LGraphGroup | null} the group or null */ - getGroupOnPos(x, y, { margin = 2 } = {}) { + getGroupOnPos(x: number, y: number, { margin = 2 } = {}) { return this._groups.reverse().find(g => g.isPointInside(x, y, margin, /* skip_title */ true)); } @@ -933,7 +1004,7 @@ export class LGraph { this.updateExecutionOrder(); } // ********** GLOBALS ***************** - onAction(action, param, options) { + onAction(action: string, param: unknown, options: { action_call?: string }): void { this._input_nodes = this.findNodesByClass( LiteGraph.GraphInput, this._input_nodes @@ -948,7 +1019,7 @@ export class LGraph { break; } } - trigger(action, param) { + trigger(action: string, param: unknown) { if (this.onTrigger) { this.onTrigger(action, param); } @@ -960,7 +1031,7 @@ export class LGraph { * @param {String} type * @param {*} value [optional] */ - addInput(name, type, value) { + addInput(name: string, type: string, value?: unknown): void { var input = this.inputs[name]; if (input) { //already exist @@ -986,7 +1057,7 @@ export class LGraph { * @param {String} name * @param {*} data */ - setInputData(name, data) { + setInputData(name: string, data: unknown): void { var input = this.inputs[name]; if (!input) { return; @@ -999,7 +1070,7 @@ export class LGraph { * @param {String} name * @return {*} the data */ - getInputData(name) { + getInputData(name: string): unknown { var input = this.inputs[name]; if (!input) { return null; @@ -1012,7 +1083,7 @@ export class LGraph { * @param {String} old_name * @param {String} new_name */ - renameInput(old_name, name) { + renameInput(old_name: string, name: string): boolean { if (name == old_name) { return; } @@ -1044,7 +1115,7 @@ export class LGraph { * @param {String} name * @param {String} type */ - changeInputType(name, type) { + changeInputType(name: string, type: string): boolean { if (!this.inputs[name]) { return false; } @@ -1067,7 +1138,7 @@ export class LGraph { * @param {String} name * @param {String} type */ - removeInput(name) { + removeInput(name: string): boolean { if (!this.inputs[name]) { return false; } @@ -1091,7 +1162,7 @@ export class LGraph { * @param {String} type * @param {*} value */ - addOutput(name, type, value) { + addOutput(name: string, type: string, value: unknown): void { this.outputs[name] = { name: name, type: type, value: value }; this._version++; @@ -1109,7 +1180,7 @@ export class LGraph { * @param {String} name * @param {String} value */ - setOutputData(name, value) { + setOutputData(name: string, value: unknown): void { var output = this.outputs[name]; if (!output) { return; @@ -1122,7 +1193,7 @@ export class LGraph { * @param {String} name * @return {*} the data */ - getOutputData(name) { + getOutputData(name: string): unknown { var output = this.outputs[name]; if (!output) { return null; @@ -1135,7 +1206,7 @@ export class LGraph { * @param {String} old_name * @param {String} new_name */ - renameOutput(old_name, name) { + renameOutput(old_name: string, name: string): boolean { if (!this.outputs[old_name]) { return false; } @@ -1163,7 +1234,7 @@ export class LGraph { * @param {String} name * @param {String} type */ - changeOutputType(name, type) { + changeOutputType(name: string, type: string): boolean { if (!this.outputs[name]) { return false; } @@ -1185,7 +1256,7 @@ export class LGraph { * @method removeOutput * @param {String} name */ - removeOutput(name) { + removeOutput(name: string): boolean { if (!this.outputs[name]) { return false; } @@ -1201,33 +1272,33 @@ export class LGraph { } return true; } - triggerInput(name, value) { + 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, func) { + setCallback(name: string, func: 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(info) { + beforeChange(info?: LGraphNode): void { if (this.onBeforeChange) { this.onBeforeChange(this, info); } this.sendActionToCanvas("onBeforeChange", this); } //used to resend actions, called after any change is made to the graph - afterChange(info) { + afterChange(info?: LGraphNode): void { if (this.onAfterChange) { this.onAfterChange(this, info); } this.sendActionToCanvas("onAfterChange", this); } - connectionChange(node, link_info) { + connectionChange(node: LGraphNode, link_info): void { this.updateExecutionOrder(); if (this.onConnectionChange) { this.onConnectionChange(node); @@ -1239,7 +1310,7 @@ export class LGraph { * returns if the graph is in live mode * @method isLive */ - isLive() { + isLive(): boolean { if (!this.list_of_graphcanvas) { return false; } @@ -1256,7 +1327,7 @@ export class LGraph { * clears the triggered slot animation in all links (stop visual animation) * @method clearTriggeredSlots */ - clearTriggeredSlots() { + clearTriggeredSlots(): void { for (var i in this.links) { var link_info = this.links[i]; if (!link_info) { @@ -1268,7 +1339,7 @@ export class LGraph { } } /* Called when something visually changed (not the graph!) */ - change() { + change(): void { if (LiteGraph.debug) { console.log("Graph changed"); } @@ -1277,7 +1348,7 @@ export class LGraph { this.on_change(this); } } - setDirtyCanvas(fg, bg) { + setDirtyCanvas(fg: boolean, bg?: boolean): void { this.sendActionToCanvas("setDirty", [fg, bg]); } /** @@ -1285,7 +1356,7 @@ export class LGraph { * @method removeLink * @param {Number} link_id */ - removeLink(link_id) { + removeLink(link_id: LinkId): void { var link = this.links[link_id]; if (!link) { return; @@ -1301,7 +1372,7 @@ export class LGraph { * @method serialize * @return {Object} value of the node */ - serialize(option = { sortNodes: false }) { + serialize(option?: { sortNodes: boolean }): ISerialisedGraph { var nodes_info = []; nodes_info = ( option?.sortNodes ? @@ -1357,7 +1428,7 @@ export class LGraph { * @param {String} str configure a graph from a JSON string * @param {Boolean} returns if there was any error parsing */ - configure(data, keep_old) { + configure(data: ISerialisedGraph, keep_old?: boolean): boolean { if (!data) { return; } @@ -1451,7 +1522,7 @@ export class LGraph { this.setDirtyCanvas(true, true); return error; } - load(url, callback) { + load(url: string | Blob | URL | File, callback: () => void) { var that = this; //from file @@ -1486,7 +1557,7 @@ export class LGraph { console.error("Error loading graph:", err); }; } - onNodeTrace(node, msg, color) { + onNodeTrace(node?: LGraphNode, msg?: string, color?) { //TODO } } diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 248b7fd38..891fa6871 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -1,13 +1,85 @@ // @ts-nocheck -import { IWidget } from "public/litegraph"; +import type { INodeSlot, INodeInputSlot, INodeOutputSlot, Point, Rect, Size, ISlotType, CanvasColour, Dictionary, Rect32, ConnectingLink, IOptionalInputsData, IContextMenuValue } from "./interfaces" +import type { CanvasDragEvent, CanvasMouseEvent } from "./types/events" +import type { RenderShape, TitleMode } from "./types/globalEnums" +import type { IWidget } from "./types/widgets" +import { LLink } from "./LLink" +import { LGraphGroup } from "./LGraphGroup" import { DragAndScale } from "./DragAndScale"; import { drawSlot, LabelPosition } from "./draw"; import { LGraphNode, LiteGraph, clamp } from "./litegraph"; import { isInsideRectangle, distance, overlapBounding, LiteGraphGlobal } from "./LiteGraphGlobal"; +import { LGraphNode, NodeId } from "./LGraphNode" +import { LGraph } from "./LGraph" + +interface IShowSearchOptions { + node_to?: LGraphNode + node_from?: LGraphNode + slot_from: INodeOutputSlot | INodeInputSlot + type_filter_in?: ISlotType + type_filter_out?: ISlotType + + // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out + do_type_filter?: boolean + show_general_if_none_on_typefilter?: boolean + show_general_after_typefiltered?: boolean + hide_on_mouse_leave?: boolean + show_all_if_empty?: boolean + show_all_on_open?: boolean +} + +interface INodeFromTo { + // input + nodeFrom?: LGraphNode + // input + slotFrom?: INodeOutputSlot | INodeInputSlot + // output + nodeTo?: LGraphNode + // output + slotTo?: INodeOutputSlot | INodeInputSlot + // pass the event coords +} + +interface ICreateNodeOptions extends INodeFromTo { + // FIXME: Should not be optional + position?: Point //,e: e + + // FIXME: Should not be optional + // choose a nodetype to add, AUTO to set at first good + nodeType?: string //nodeNewType + // adjust x,y + posAdd?: Point //-alphaPosY*30] + // alpha, adjust the position x,y based on the new node size w,h + posSizeFix?: Point //-alphaPosY*2*/ + e?: CanvasMouseEvent + allow_searchbox?: boolean + showSearchBox?: LGraphCanvas["showSearchBox"] + createDefaultNodeForSlot?: LGraphCanvas["createDefaultNodeForSlot"] +} + +interface IDialog extends HTMLDivElement { + modified?(): void + close?(): void + is_modified?: boolean +} + +interface IDialogOptions { + position?: Point + event?: MouseEvent + checkForInput?: boolean + closeOnLeave?: boolean + onclose?(): void +} + +interface IDrawSelectionBoundingOptions { + shape?: RenderShape + title_height?: number + title_mode?: TitleMode + fgcolor?: CanvasColour + padding?: number + collapsed?: boolean +} -//********************************************************************************* -// 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 @@ -56,8 +128,6 @@ export class LGraphCanvas { yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } }; - - public canvas: HTMLCanvasElement; public pointer_is_down: boolean = false; private _dragging_canvas: boolean = false; @@ -92,8 +162,154 @@ export class LGraphCanvas { }); } } + options: { skip_events?: any; viewport?: any; skip_render?: any; autoresize?: any } + background_image: string + ds: DragAndScale + zoom_modify_alpha: boolean + zoom_speed: number + title_text_font: string + inner_text_font: string + node_title_color: string + default_link_color: string + default_connection_color: { + input_off: string; input_on: string //"#BBD" + output_off: string; output_on: string //"#BBD" + } + default_connection_color_byType: Dictionary + default_connection_color_byTypeOff: Dictionary + highquality_render: boolean + use_gradients: boolean + editor_alpha: number + pause_rendering: boolean + clear_background: boolean + clear_background_color: string + render_only_selected: boolean + live_mode: boolean + show_info: boolean + allow_dragcanvas: boolean + allow_dragnodes: boolean + allow_interaction: boolean + multi_select: boolean + allow_searchbox: boolean + allow_reconnect_links: boolean + align_to_grid: boolean + drag_mode: boolean + dragging_rectangle?: Rect + filter?: string + always_render_background: boolean + render_shadows: boolean + render_canvas_border: boolean + render_connections_shadows: boolean + render_connections_border: boolean + render_curved_connections: boolean + render_connection_arrows: boolean + render_collapsed_slots: boolean + render_execution_order: boolean + render_title_colored: boolean + render_link_tooltip: boolean + links_render_mode: number + mouse: Point + graph_mouse: Point + onSearchBox?: (helper: Element, str: string, canvas: LGraphCanvas) => any + onSearchBoxSelection?: (name: any, event: any, canvas: LGraphCanvas) => void + onMouse?: (e: CanvasMouseEvent) => boolean + onDrawBackground?: (ctx: CanvasRenderingContext2D, visible_area: any) => void + onDrawForeground?: (arg0: CanvasRenderingContext2D, arg1: any) => void + connections_width: number + round_radius: number + current_node: LGraphNode + node_widget?: [LGraphNode, IWidget] + over_link_center: LLink + last_mouse_position: Point + visible_area?: Rect32 + visible_links?: LLink[] + connecting_links: ConnectingLink[] + viewport?: Rect + autoresize: boolean + static active_canvas: LGraphCanvas + static onMenuNodeOutputs?(entries: IOptionalInputsData[]): IOptionalInputsData[] + frame: number + last_draw_time: number + render_time: number + fps: number + selected_nodes: Dictionary + selected_group: LGraphGroup + visible_nodes: LGraphNode[] + node_dragged?: LGraphNode + node_over?: LGraphNode + node_capturing_input?: LGraphNode + highlighted_links: Dictionary - constructor(canvas, graph, options) { + dirty_canvas: boolean + dirty_bgcanvas: boolean + /** A map of nodes that require selective-redraw */ + dirty_nodes = new Map() + dirty_area?: Rect + // Unused + node_in_panel?: LGraphNode + last_mouse: Point + last_mouseclick: number + pointer_is_down: boolean + pointer_is_double: boolean + graph: LGraph + _graph_stack: LGraph[] + canvas: HTMLCanvasElement + bgcanvas: HTMLCanvasElement + ctx?: CanvasRenderingContext2D + _events_binded?: boolean + _mousedown_callback?(e: CanvasMouseEvent): boolean + _mousewheel_callback?(e: CanvasMouseEvent): boolean + _mousemove_callback?(e: CanvasMouseEvent): boolean + _mouseup_callback?(e: CanvasMouseEvent): boolean + _mouseout_callback?(e: CanvasMouseEvent): boolean + _key_callback?(e: KeyboardEvent): boolean + _ondrop_callback?(e: CanvasDragEvent): unknown + gl?: never + bgctx?: CanvasRenderingContext2D + is_rendering?: boolean + block_click?: boolean + last_click_position?: Point + resizing_node?: LGraphNode + selected_group_resizing?: boolean + last_mouse_dragging: boolean + onMouseDown: (arg0: CanvasMouseEvent) => void + _highlight_pos?: Point + _highlight_input?: INodeInputSlot + // TODO: Check if panels are used + node_panel + options_panel + onDropItem: (e: Event) => any + _bg_img: HTMLImageElement + _pattern?: CanvasPattern + _pattern_img: HTMLImageElement + // TODO: This looks like another panel thing + prompt_box: IDialog + search_box: HTMLDivElement + SELECTED_NODE: LGraphNode + NODEPANEL_IS_OPEN: boolean + getMenuOptions?(): IContextMenuValue[] + getExtraMenuOptions?(canvas: LGraphCanvas, options: IContextMenuValue[]): IContextMenuValue[] + static active_node: LGraphNode + onBeforeChange?(graph: LGraph): void + onAfterChange?(graph: LGraph): void + onClear?: () => void + onNodeMoved?: (node_dragged: LGraphNode) => void + onSelectionChange?: (selected_nodes: Dictionary) => void + onDrawLinkTooltip?: (ctx: CanvasRenderingContext2D, link: LLink, canvas?: LGraphCanvas) => boolean + onDrawOverlay?: (ctx: CanvasRenderingContext2D) => void + onRenderBackground?: (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => boolean + onNodeDblClicked?: (n: LGraphNode) => void + onShowNodePanel?: (n: LGraphNode) => void + onNodeSelected?: (node: LGraphNode) => void + onNodeDeselected?: (node: LGraphNode) => void + onRender?: (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => void + /** Implement this function to allow conversion of widget types to input types, e.g. number -> INT or FLOAT for widget link validation checks */ + getWidgetLinkType?: (widget: IWidget, node: LGraphNode) => string | null | undefined + + // FIXME: Has never worked - undefined + visible_rect?: Rect + + constructor(canvas: HTMLCanvasElement, graph: LGraph, options?: { viewport?: any; skip_events?: any; skip_render?: any; autoresize?: any }) { this.options = options = options || {}; //if(graph === undefined) @@ -219,7 +435,7 @@ export class LGraphCanvas { this.autoresize = options.autoresize; } - static getFileExtension(url) { + static getFileExtension(url: string): string { var question = url.indexOf("?"); if (question != -1) { url = url.substr(0, question); @@ -287,7 +503,7 @@ export class LGraphCanvas { event.preventDefault(); };*/ /* CONTEXT MENU ********************/ - static onGroupAdd(info, entry, mouse_event) { + static onGroupAdd(info: unknown, entry: unknown, mouse_event: MouseEvent): void { var canvas = LGraphCanvas.active_canvas; var ref_window = canvas.getCanvasWindow(); @@ -300,7 +516,7 @@ export class LGraphCanvas { * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} */ - static getBoundaryNodes(nodes) { + static getBoundaryNodes(nodes: LGraphNode[] | Dictionary): IBoundaryNodes { let top = null; let right = null; let bottom = null; @@ -337,7 +553,7 @@ export class LGraphCanvas { * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction) */ - static alignNodes(nodes, direction, align_to) { + static alignNodes(nodes: Dictionary, direction: Direction, align_to?: LGraphNode): void { if (!nodes) { return; } @@ -375,7 +591,7 @@ export class LGraphCanvas { canvas.dirty_canvas = true; canvas.dirty_bgcanvas = true; } - static onNodeAlign(value, options, event, prev_menu, node) { + static onNodeAlign(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): void { new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { event: event, callback: inner_clicked, @@ -386,7 +602,7 @@ export class LGraphCanvas { LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node); } } - static onGroupAlign(value, options, event, prev_menu) { + static onGroupAlign(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu): void { new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { event: event, callback: inner_clicked, @@ -397,7 +613,7 @@ export class LGraphCanvas { LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase()); } } - static onMenuAdd(node, options, e, prev_menu, callback) { + static onMenuAdd(node: LGraphNode, options: IContextMenuOptions, e: MouseEvent, prev_menu: ContextMenu, callback?: (node: LGraphNode) => void): boolean { var canvas = LGraphCanvas.active_canvas; var ref_window = canvas.getCanvasWindow(); @@ -471,11 +687,7 @@ export class LGraphCanvas { } static onMenuCollapseAll() { } static onMenuNodeEdit() { } - static showMenuNodeOptionalInputs(v, - options, - e, - prev_menu, - node) { + static showMenuNodeOptionalInputs(v: unknown, options: unknown, e: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): boolean { if (!node) { return; } @@ -558,11 +770,7 @@ export class LGraphCanvas { return false; } - static showMenuNodeOptionalOutputs(v, - options, - e, - prev_menu, - node) { + static showMenuNodeOptionalOutputs(v: unknown, options: unknown, e: unknown, prev_menu: ContextMenu, node: LGraphNode): boolean { if (!node) { return; } @@ -678,11 +886,7 @@ export class LGraphCanvas { return false; } - static onShowMenuNodeProperties(value, - options, - e, - prev_menu, - node) { + static onShowMenuNodeProperties(value: unknown, options: unknown, e: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): boolean { if (!node || !node.properties) { return; } @@ -740,12 +944,12 @@ export class LGraphCanvas { return false; } - static decodeHTML(str) { + static decodeHTML(str: string): string { var e = document.createElement("div"); e.innerText = str; return e.innerHTML; } - static onMenuResizeNode(value, options, e, menu, node) { + static onMenuResizeNode(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { if (!node) { return; } @@ -768,7 +972,7 @@ export class LGraphCanvas { node.setDirtyCanvas(true, true); } // TODO refactor :: this is used fot title but not for properties! - static onShowPropertyEditor(item, options, e, menu, node) { + static onShowPropertyEditor(item: { property: string; type: string }, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { var input_html = ""; var property = item.property || "title"; var value = node[property]; @@ -860,7 +1064,7 @@ export class LGraphCanvas { node.setDirtyCanvas(true, true); } } - static getPropertyPrintableValue(value, values) { + static getPropertyPrintableValue(value: unknown, values: unknown[] | object): string { if (!values) return String(value); @@ -879,7 +1083,7 @@ export class LGraphCanvas { return String(value) + " (" + desc_value + ")"; } } - static onMenuNodeCollapse(value, options, e, menu, node) { + static onMenuNodeCollapse(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { node.graph.beforeChange( /*?*/); var fApplyMultiNode = function (node) { @@ -897,9 +1101,9 @@ export class LGraphCanvas { node.graph.afterChange( /*?*/); } - static onMenuNodePin(value, options, e, menu, node) { + static onMenuNodePin(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { } - static onMenuNodeMode(value, options, e, menu, node) { + static onMenuNodeMode(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): boolean { new LiteGraph.ContextMenu( LiteGraph.NODE_MODES, { event: e, callback: inner_clicked, parentMenu: menu, node: node } @@ -931,7 +1135,7 @@ export class LGraphCanvas { return false; } - static onMenuNodeColors(value, options, e, menu, node) { + static onMenuNodeColors(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): boolean { if (!node) { throw "no node for color"; } @@ -997,7 +1201,7 @@ export class LGraphCanvas { return false; } - static onMenuNodeShapes(value, options, e, menu, node) { + static onMenuNodeShapes(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): boolean { if (!node) { throw "no node passed"; } @@ -1034,7 +1238,7 @@ export class LGraphCanvas { return false; } - static onMenuNodeRemove(value, options, e, menu, node) { + static onMenuNodeRemove(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { if (!node) { throw "no node passed"; } @@ -1062,7 +1266,7 @@ export class LGraphCanvas { graph.afterChange(); node.setDirtyCanvas(true, true); } - static onMenuNodeToSubgraph(value, options, e, menu, node) { + static onMenuNodeToSubgraph(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { var graph = node.graph; var graphcanvas = LGraphCanvas.active_canvas; if (!graphcanvas) //?? @@ -1081,7 +1285,7 @@ export class LGraphCanvas { graphcanvas.deselectAllNodes(); node.setDirtyCanvas(true, true); } - static onMenuNodeClone(value, options, e, menu, node) { + static onMenuNodeClone(value: IContextMenuValue, options: IContextMenuOptions, e: MouseEvent, menu: ContextMenu, node: LGraphNode): void { node.graph.beforeChange(); @@ -1122,7 +1326,7 @@ export class LGraphCanvas { * * @method clear */ - clear() { + clear(): void { this.frame = 0; this.last_draw_time = 0; this.render_time = 0; @@ -1167,7 +1371,7 @@ export class LGraphCanvas { * @method setGraph * @param {LGraph} graph */ - setGraph(graph, skip_clear) { + setGraph(graph: LGraph, skip_clear: boolean): void { if (this.graph == graph) { return; } @@ -1195,7 +1399,7 @@ export class LGraphCanvas { * @method getTopGraph * @return {LGraph} graph */ - getTopGraph() { + getTopGraph(): LGraph { if (this._graph_stack.length) return this._graph_stack[0]; return this.graph; @@ -1206,7 +1410,7 @@ export class LGraphCanvas { * @method openSubgraph * @param {LGraph} graph */ - openSubgraph(graph) { + openSubgraph(graph: LGraph): void { if (!graph) { throw "graph cannot be null"; } @@ -1234,7 +1438,7 @@ export class LGraphCanvas { * @method closeSubgraph * @param {LGraph} assigns a graph */ - closeSubgraph() { + closeSubgraph(): void { if (!this._graph_stack || this._graph_stack.length == 0) { return; } @@ -1257,7 +1461,7 @@ export class LGraphCanvas { * @method getCurrentGraph * @return {LGraph} the active graph */ - getCurrentGraph() { + getCurrentGraph(): LGraph { return this.graph; } /** @@ -1266,7 +1470,7 @@ export class LGraphCanvas { * @method setCanvas * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) */ - setCanvas(canvas, skip_events) { + setCanvas(canvas?: string | HTMLCanvasElement, skip_events?: boolean) { var that = this; if (canvas) { @@ -1337,12 +1541,12 @@ export class LGraphCanvas { } } //used in some events to capture them - _doNothing(e) { + _doNothing(e: Event) { //console.log("pointerevents: _doNothing "+e.type); e.preventDefault(); return false; } - _doReturnTrue(e) { + _doReturnTrue(e: Event) { e.preventDefault(); return true; } @@ -1350,7 +1554,7 @@ export class LGraphCanvas { * binds mouse, keyboard, touch and drag events to the canvas * @method bindEvents **/ - bindEvents() { + bindEvents(): void { if (this._events_binded) { console.warn("LGraphCanvas: events already binded"); return; @@ -1413,7 +1617,7 @@ export class LGraphCanvas { * unbinds mouse events from the canvas * @method unbindEvents **/ - unbindEvents() { + unbindEvents(): void { if (!this._events_binded) { console.warn("LGraphCanvas: no events binded"); return; @@ -1457,7 +1661,7 @@ export class LGraphCanvas { * 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 **/ - enableWebGL() { + enableWebGL(): void { if (typeof GL === "undefined") { throw "litegl.js must be included to use a WebGL canvas"; } @@ -1485,7 +1689,7 @@ export class LGraphCanvas { * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) */ - setDirty(fgcanvas, bgcanvas) { + setDirty(fgcanvas: boolean, bgcanvas?: boolean): void { if (fgcanvas) { this.dirty_canvas = true; } @@ -1499,7 +1703,7 @@ export class LGraphCanvas { * @method getCanvasWindow * @return {window} returns the window where the canvas is attached (the DOM root node) */ - getCanvasWindow() { + getCanvasWindow(): Window { if (!this.canvas) { return window; } @@ -1511,7 +1715,7 @@ export class LGraphCanvas { * * @method startRendering */ - startRendering() { + startRendering(): void { if (this.is_rendering) { return; } //already rendering @@ -1535,7 +1739,7 @@ export class LGraphCanvas { * * @method stopRendering */ - stopRendering() { + stopRendering(): void { this.is_rendering = false; /* if(this.rendering_timer_id) @@ -1547,7 +1751,7 @@ export class LGraphCanvas { } /* LiteGraphCanvas input */ //used to block future mouse events (because of im gui) - blockClick() { + blockClick(): void { this.block_click = true; this.last_mouseclick = 0; } @@ -1589,7 +1793,7 @@ export class LGraphCanvas { return null; } - processMouseDown(e: MouseEvent) { + processMouseDown(e: CanvasMouseEvent): boolean { if (this.set_canvas_dirty_on_mouse_event) this.dirty_canvas = true; @@ -2178,7 +2382,7 @@ export class LGraphCanvas { * Called when a mouse move event has to be processed * @method processMouseMove **/ - processMouseMove(e) { + processMouseMove(e: CanvasMouseEvent): boolean { this.link_over_widget = null; if (this.autoresize) { @@ -2439,7 +2643,7 @@ export class LGraphCanvas { * Called when a mouse up event has to be processed * @method processMouseUp **/ - processMouseUp(e) { + processMouseUp(e: CanvasMouseEvent): boolean { var is_primary = (e.isPrimary === undefined || e.isPrimary); @@ -2746,7 +2950,7 @@ export class LGraphCanvas { * Called when a mouse wheel event has to be processed * @method processMouseWheel **/ - processMouseWheel(e) { + processMouseWheel(e: WheelEvent): boolean { if (!this.graph || !this.allow_dragcanvas) { return; } @@ -2781,7 +2985,7 @@ export class LGraphCanvas { * returns true if a position (in graph space) is on top of a node little corner box * @method isOverNodeBox **/ - isOverNodeBox(node, canvasx, canvasy) { + isOverNodeBox(node: LGraphNode, canvasx: number, canvasy: number): boolean { var title_height = LiteGraph.NODE_TITLE_HEIGHT; if (isInsideRectangle( canvasx, @@ -2799,10 +3003,7 @@ export class LGraphCanvas { * returns the INDEX if a position (in graph space) is on top of a node input slot * @method isOverNodeInput **/ - isOverNodeInput(node, - canvasx, - canvasy, - slot_pos) { + isOverNodeInput(node: LGraphNode, canvasx: number, canvasy: number, slot_pos?: Point): number { if (node.inputs) { for (var i = 0, l = node.inputs.length; i < l; ++i) { var input = node.inputs[i]; @@ -2845,10 +3046,7 @@ export class LGraphCanvas { * returns the INDEX if a position (in graph space) is on top of a node output slot * @method isOverNodeOuput **/ - isOverNodeOutput(node, - canvasx, - canvasy, - slot_pos) { + isOverNodeOutput(node: LGraphNode, canvasx: number, canvasy: number, slot_pos?: Point): number { if (node.outputs) { for (var i = 0, l = node.outputs.length; i < l; ++i) { var output = node.outputs[i]; @@ -2888,7 +3086,7 @@ export class LGraphCanvas { * process a key event * @method processKey **/ - processKey(e) { + processKey(e: KeyboardEvent): boolean | null { if (!this.graph) { return; } @@ -2980,7 +3178,7 @@ export class LGraphCanvas { return false; } } - copyToClipboard(nodes) { + copyToClipboard(nodes?: Dictionary): void { var clipboard_info = { nodes: [], links: [] @@ -3136,7 +3334,7 @@ export class LGraphCanvas { this.graph.afterChange(); } - pasteFromClipboard(isConnectUnselected = false) { + pasteFromClipboard(isConnectUnselected = false): void { this.emitBeforeChange(); try { this._pasteFromClipboard(isConnectUnselected); @@ -3148,7 +3346,7 @@ export class LGraphCanvas { * process a item drop event on top the canvas * @method processDrop **/ - processDrop(e) { + processDrop(e: CanvasDragEvent): boolean { e.preventDefault(); this.adjustMouseEvent(e); var x = e.clientX; @@ -3223,7 +3421,7 @@ export class LGraphCanvas { return false; } //called if the graph doesn't have a default drop item behaviour - checkDropItem(e) { + checkDropItem(e: CanvasDragEvent): void { if (e.dataTransfer.files.length) { var file = e.dataTransfer.files[0]; var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase(); @@ -3240,7 +3438,7 @@ export class LGraphCanvas { } } } - processNodeDblClicked(n) { + processNodeDblClicked(n: LGraphNode): void { if (this.onShowNodePanel) { this.onShowNodePanel(n); } @@ -3251,7 +3449,7 @@ export class LGraphCanvas { this.setDirty(true); } - processNodeSelected(node, e) { + processNodeSelected(node: LGraphNode, e: CanvasMouseEvent): void { this.selectNode(node, e && (e.shiftKey || e.metaKey || e.ctrlKey || this.multi_select)); if (this.onNodeSelected) { this.onNodeSelected(node); @@ -3261,8 +3459,7 @@ export class LGraphCanvas { * selects a given node (or adds it to the current selection) * @method selectNode **/ - selectNode(node, - add_to_current_selection) { + selectNode(node: LGraphNode, add_to_current_selection?: boolean): void { if (node == null) { this.deselectAllNodes(); } else { @@ -3273,7 +3470,7 @@ export class LGraphCanvas { * selects several nodes (or adds them to the current selection) * @method selectNodes **/ - selectNodes(nodes, add_to_current_selection) { + selectNodes(nodes?: LGraphNode[] | Dictionary, add_to_current_selection?: boolean): void { if (!add_to_current_selection) { this.deselectAllNodes(); } @@ -3319,7 +3516,7 @@ export class LGraphCanvas { * removes a node from the current selection * @method deselectNode **/ - deselectNode(node) { + deselectNode(node: LGraphNode): void { if (!node.is_selected) { return; } @@ -3354,7 +3551,7 @@ export class LGraphCanvas { * removes all nodes from the current selection * @method deselectAllNodes **/ - deselectAllNodes() { + deselectAllNodes(): void { if (!this.graph) { return; } @@ -3383,7 +3580,7 @@ export class LGraphCanvas { * deletes all nodes in the current selection from the graph * @method deleteSelectedNodes **/ - deleteSelectedNodes() { + deleteSelectedNodes(): void { this.graph.beforeChange(); @@ -3417,7 +3614,7 @@ export class LGraphCanvas { * centers the camera on a given node * @method centerOnNode **/ - centerOnNode(node) { + centerOnNode(node: LGraphNode): void { const dpi = window?.devicePixelRatio || 1; this.ds.offset[0] = -node.pos[0] - @@ -3433,7 +3630,7 @@ export class LGraphCanvas { * adds some useful properties to a mouse event, like the position in graph coordinates * @method adjustMouseEvent **/ - adjustMouseEvent(e) { + adjustMouseEvent(e: CanvasMouseEvent | CanvasDragEvent | CanvasWheelEvent): asserts e is CanvasMouseEvent { var clientX_rel = 0; var clientY_rel = 0; @@ -3466,7 +3663,7 @@ export class LGraphCanvas { * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom * @method setZoom **/ - setZoom(value, zooming_center) { + setZoom(value: number, zooming_center: Point) { this.ds.changeScale(value, zooming_center); /* if(!zooming_center && this.canvas) @@ -3494,18 +3691,18 @@ export class LGraphCanvas { * converts a coordinate from graph coordinates to canvas2D coordinates * @method convertOffsetToCanvas **/ - convertOffsetToCanvas(pos, out) { + convertOffsetToCanvas(pos: Point, out: Point): Point { return this.ds.convertOffsetToCanvas(pos, out); } /** * converts a coordinate from Canvas2D coordinates to graph space * @method convertCanvasToOffset **/ - convertCanvasToOffset(pos, out) { + convertCanvasToOffset(pos: Point, out?: Point): Point { return this.ds.convertCanvasToOffset(pos, out); } //converts event coordinates from canvas2D to graph coordinates - convertEventToCanvasOffset(e) { + convertEventToCanvasOffset(e: MouseEvent): Point { var rect = this.canvas.getBoundingClientRect(); return this.convertCanvasToOffset([ e.clientX - rect.left, @@ -3516,7 +3713,7 @@ export class LGraphCanvas { * brings a node to front (above all other nodes) * @method bringToFront **/ - bringToFront(node) { + bringToFront(node: LGraphNode): void { var i = this.graph._nodes.indexOf(node); if (i == -1) { return; @@ -3529,7 +3726,7 @@ export class LGraphCanvas { * sends a node to the back (below all other nodes) * @method sendToBack **/ - sendToBack(node) { + sendToBack(node: LGraphNode): void { var i = this.graph._nodes.indexOf(node); if (i == -1) { return; @@ -3544,7 +3741,7 @@ export class LGraphCanvas { * checks which nodes are visible (inside the camera area) * @method computeVisibleNodes **/ - computeVisibleNodes(nodes, out) { + computeVisibleNodes(nodes?: LGraphNode[], out?: LGraphNode[]): LGraphNode[] { var visible_nodes = out || []; visible_nodes.length = 0; nodes = nodes || this.graph._nodes; @@ -3568,7 +3765,7 @@ export class LGraphCanvas { * 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 **/ - draw(force_canvas, force_bgcanvas) { + draw(force_canvas?: boolean, force_bgcanvas?: boolean): void { if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { return; } @@ -3602,7 +3799,7 @@ export class LGraphCanvas { * draws the front canvas (the one containing all the nodes) * @method drawFrontCanvas **/ - drawFrontCanvas() { + drawFrontCanvas(): void { this.dirty_canvas = false; if (!this.ctx) { @@ -3872,7 +4069,7 @@ export class LGraphCanvas { * draws the panel in the corner that shows subgraph properties * @method drawSubgraphPanel **/ - drawSubgraphPanel(ctx) { + drawSubgraphPanel(ctx: CanvasRenderingContext2D): void { var subgraph = this.graph; var subnode = subgraph._subgraph_node; if (!subnode) { @@ -3882,7 +4079,7 @@ export class LGraphCanvas { this.drawSubgraphPanelLeft(subgraph, subnode, ctx); this.drawSubgraphPanelRight(subgraph, subnode, ctx); } - drawSubgraphPanelLeft(subgraph, subnode, ctx) { + drawSubgraphPanelLeft(subgraph: LGraph, subnode: LGraphNode, ctx: CanvasRenderingContext2D): void { var num = subnode.inputs ? subnode.inputs.length : 0; var w = 200; var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); @@ -3951,7 +4148,7 @@ export class LGraphCanvas { this.showSubgraphPropertiesDialog(subnode); } } - drawSubgraphPanelRight(subgraph, subnode, ctx) { + drawSubgraphPanelRight(subgraph: LGraph, subnode: LGraphNode, ctx: CanvasRenderingContext2D): void { var num = subnode.outputs ? subnode.outputs.length : 0; var canvas_w = this.bgcanvas.width; var w = 200; @@ -4024,7 +4221,7 @@ export class LGraphCanvas { } } //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm - drawButton(x, y, w, h, text, bgcolor, hovercolor, textcolor) { + drawButton(x: number, y: number, w: number, h: number, text?: string, bgcolor?: CanvasColour, hovercolor?: CanvasColour, textcolor?: CanvasColour): boolean { var ctx = this.ctx; bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; hovercolor = hovercolor || "#555"; @@ -4061,7 +4258,7 @@ export class LGraphCanvas { this.blockClick(); return was_clicked; } - isAreaClicked(x, y, w, h, hold_click) { + isAreaClicked(x: number, y: number, w: number, h: number, hold_click: boolean): boolean { var pos = this.mouse; var hover = LiteGraph.isInsideRectangle(pos[0], pos[1], x, y, w, h); pos = this.last_click_position; @@ -4075,7 +4272,7 @@ export class LGraphCanvas { * draws some useful stats in the corner of the canvas * @method renderInfo **/ - renderInfo(ctx, x, y) { + renderInfo(ctx: CanvasRenderingContext2D, x: number, y: Number): void { x = x || 10; y = y || this.canvas.offsetHeight - 80; @@ -4100,7 +4297,7 @@ export class LGraphCanvas { * draws the back canvas (the one containing the background and the connections) * @method drawBackCanvas **/ - drawBackCanvas() { + drawBackCanvas(): void { var canvas = this.bgcanvas; if (canvas.width != this.canvas.width || canvas.height != this.canvas.height) { @@ -4277,7 +4474,7 @@ export class LGraphCanvas { * draws the given node inside the canvas * @method drawNode **/ - drawNode(node, ctx) { + drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void { var glow = false; this.current_node = node; @@ -4590,7 +4787,7 @@ export class LGraphCanvas { ctx.globalAlpha = 1.0; } //used by this.over_link_center - drawLinkTooltip(ctx, link) { + drawLinkTooltip(ctx: CanvasRenderingContext2D, link: LLink): void { var pos = link._pos; ctx.fillStyle = "black"; ctx.beginPath(); @@ -4648,13 +4845,15 @@ export class LGraphCanvas { * draws the shape of the given node in the canvas * @method drawNodeShape **/ - drawNodeShape(node, - ctx, - size, - fgcolor, - bgcolor, - selected, - mouse_over) { + drawNodeShape( + node: LGraphNode, + ctx: CanvasRenderingContext2D, + size: Size, + fgcolor: CanvasColour, + bgcolor: CanvasColour, + selected: boolean, + mouse_over: boolean + ): void { //bg rect ctx.strokeStyle = fgcolor; ctx.fillStyle = bgcolor; @@ -4936,11 +5135,16 @@ export class LGraphCanvas { * }} options */ drawSelectionBounding( - ctx, - area, + ctx: CanvasRenderingContext2D, + area: Rect, { - shape = LiteGraph.BOX_SHAPE, title_height = LiteGraph.NODE_TITLE_HEIGHT, title_mode = LiteGraph.NORMAL_TITLE, fgcolor = LiteGraph.NODE_BOX_OUTLINE_COLOR, padding = 6, collapsed = false - } = {} + shape = LiteGraph.BOX_SHAPE, + title_height = LiteGraph.NODE_TITLE_HEIGHT, + title_mode = LiteGraph.NORMAL_TITLE, + fgcolor = LiteGraph.NODE_BOX_OUTLINE_COLOR, + padding = 6, + collapsed = false, + }: IDrawSelectionBoundingOptions = {} ) { // Adjust area if title is transparent if (title_mode === LiteGraph.TRANSPARENT_TITLE) { @@ -4986,7 +5190,7 @@ export class LGraphCanvas { ctx.globalAlpha = 1; } - drawConnections(ctx) { + drawConnections(ctx: CanvasRenderingContext2D): void { var now = LiteGraph.getTime(); var visible_area = this.visible_area; LGraphCanvas.#margin_area[0] = visible_area[0] - 20; @@ -5117,16 +5321,16 @@ export class LGraphCanvas { * @param {number} end_dir the direction enum * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb) **/ - renderLink(ctx, - a, - b, - link, - skip_border, - flow, - color, - start_dir, - end_dir, - num_sublines) { + renderLink(ctx: CanvasRenderingContext2D, + a: Point, + b: Point, + link: LLink, + skip_border: boolean, + flow: number, + color: CanvasColour, + start_dir: LinkDirection, + end_dir: LinkDirection, + num_sublines?: number): void { if (link) { this.visible_links.push(link); } @@ -5387,11 +5591,11 @@ export class LGraphCanvas { } } //returns the link center point based on curvature - computeConnectionPoint(a, - b, - t, - start_dir, - end_dir) { + computeConnectionPoint(a: Point, + b: Point, + t: number, + start_dir: number, + end_dir: number): number[] { start_dir = start_dir || LiteGraph.RIGHT; end_dir = end_dir || LiteGraph.LEFT; @@ -5439,7 +5643,7 @@ export class LGraphCanvas { var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1]; return [x, y]; } - drawExecutionOrder(ctx) { + drawExecutionOrder(ctx: CanvasRenderingContext2D): void { ctx.shadowColor = "transparent"; ctx.globalAlpha = 0.25; @@ -5478,10 +5682,10 @@ export class LGraphCanvas { * draws the widgets stored inside a node * @method drawNodeWidgets **/ - drawNodeWidgets(node, - posY, - ctx, - active_widget) { + drawNodeWidgets(node: LGraphNode, + posY: number, + ctx: CanvasRenderingContext2D, + active_widget: IWidget) { if (!node.widgets || !node.widgets.length) { return 0; } @@ -5741,10 +5945,11 @@ export class LGraphCanvas { * process an event on widgets * @method processNodeWidgets **/ - processNodeWidgets(node, - pos, - event, - active_widget) { + processNodeWidgets(node: LGraphNode, + // TODO: Hitting enter does not trigger onWidgetChanged - may require a separate value processor for processKey + pos: Point, + event: CanvasMouseEvent, + active_widget?: IWidget): IWidget { if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { return null; } @@ -5953,7 +6158,7 @@ export class LGraphCanvas { * draws every group area in the background * @method drawGroups **/ - drawGroups(canvas, ctx) { + drawGroups(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): void { if (!this.graph) { return; } @@ -5975,7 +6180,7 @@ export class LGraphCanvas { ctx.restore(); } - adjustNodesSize() { + adjustNodesSize(): void { var nodes = this.graph._nodes; for (var i = 0; i < nodes.length; ++i) { nodes[i].size = nodes[i].computeSize(); @@ -5986,7 +6191,7 @@ export class LGraphCanvas { * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode * @method resize **/ - resize(width, height) { + resize(width?: number, height?: number): void { if (!width && !height) { var parent = this.canvas.parentNode; width = parent.offsetWidth; @@ -6008,7 +6213,7 @@ export class LGraphCanvas { * this feature was designed when graphs where meant to create user interfaces * @method switchLiveMode **/ - switchLiveMode(transition) { + switchLiveMode(transition: boolean): void { if (!transition) { this.live_mode = !this.live_mode; this.dirty_canvas = true; @@ -6040,17 +6245,16 @@ export class LGraphCanvas { } }, 1); } - onNodeSelectionChange(node) { - return; //disabled - } + // TODO: Not called. Confirm no consumers, then remove. + onNodeSelectionChange(): void { } /** * Determines the furthest nodes in each direction for the currently selected nodes * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} */ - boundaryNodesForSelection() { + boundaryNodesForSelection(): IBoundaryNodes { return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)); } - showLinkMenu(link, e) { + showLinkMenu(link: LLink, e: CanvasMouseEvent): boolean { var that = this; // console.log(link); var node_left = that.graph.getNodeById(link.origin_id); @@ -6102,7 +6306,7 @@ export class LGraphCanvas { return false; } - createDefaultNodeForSlot(optPass) { + createDefaultNodeForSlot(optPass: ICreateNodeOptions): boolean { var optPass = optPass || {}; var opts = Object.assign({ nodeFrom: null // input @@ -6270,7 +6474,7 @@ export class LGraphCanvas { } return false; } - showConnectionMenu(optPass) { + showConnectionMenu(optPass: ICreateNodeOptions & { e: MouseEvent }): void { var optPass = optPass || {}; var opts = Object.assign({ nodeFrom: null // input @@ -6389,7 +6593,7 @@ export class LGraphCanvas { } } // refactor: there are different dialogs, some uses createDialog some dont - prompt(title, value, callback, event, multiline) { + prompt(title: string, value: any, callback: (arg0: any) => void, event: CanvasMouseEvent, multiline?: boolean): HTMLDivElement { var that = this; var input_html = ""; title = title || ""; @@ -6521,7 +6725,7 @@ export class LGraphCanvas { return dialog; } - showSearchBox(event, options) { + showSearchBox(event: CanvasMouseEvent, options?: IShowSearchOptions): HTMLDivElement { // proposed defaults var def_options = { slot_from: null, @@ -7094,7 +7298,7 @@ export class LGraphCanvas { return dialog; } - showEditPropertyValue(node, property, options) { + showEditPropertyValue(node: LGraphNode, property: string, options: IDialogOptions): IDialog { if (!node || node.properties[property] === undefined) { return; } @@ -7227,7 +7431,7 @@ export class LGraphCanvas { return dialog; } // TODO refactor, theer are different dialog, some uses createDialog, some dont - createDialog(html, options) { + createDialog(html: string, options: IDialogOptions): IDialog { var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true }; options = Object.assign(def_options, options || {}); @@ -7514,7 +7718,7 @@ export class LGraphCanvas { return root; } - closePanels() { + closePanels(): void { var panel = document.querySelector("#node-panel"); if (panel) panel.close(); @@ -7611,7 +7815,7 @@ export class LGraphCanvas { graphcanvas.canvas.parentNode.appendChild(panel); } - showShowNodePanel(node) { + showShowNodePanel(node: LGraphNode): void { this.SELECTED_NODE = node; this.closePanels(); var ref_window = this.getCanvasWindow(); @@ -7757,7 +7961,7 @@ export class LGraphCanvas { this.canvas.parentNode.appendChild(panel); } - showSubgraphPropertiesDialog(node) { + showSubgraphPropertiesDialog(node: LGraphNode) { console.log("showing subgraph properties dialog"); var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); @@ -7809,7 +8013,7 @@ export class LGraphCanvas { this.canvas.parentNode.appendChild(panel); return panel; } - showSubgraphPropertiesDialogRight(node) { + showSubgraphPropertiesDialogRight(node: LGraphNode) { // console.log("showing subgraph properties dialog"); var that = this; @@ -7870,7 +8074,7 @@ export class LGraphCanvas { this.canvas.parentNode.appendChild(panel); return panel; } - checkPanels() { + checkPanels(): void { if (!this.canvas) return; var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); @@ -7882,7 +8086,7 @@ export class LGraphCanvas { panel.close(); } } - getCanvasMenuOptions() { + getCanvasMenuOptions(): IContextMenuValue[] { var options = null; var that = this; if (this.getMenuOptions) { @@ -7927,7 +8131,7 @@ export class LGraphCanvas { return options; } //called by processContextMenu to extract the menu list - getNodeMenuOptions(node) { + getNodeMenuOptions(node: LGraphNode): IContextMenuValue[] { var options = null; if (node.getMenuOptions) { @@ -8059,11 +8263,11 @@ export class LGraphCanvas { return options; } - getGroupMenuOptions(node) { + getGroupMenuOptions(node: LGraphGroup): IContextMenuValue[] { console.warn("LGraphCanvas.getGroupMenuOptions is deprecated, use LGraphGroup.getMenuOptions instead"); return node.getMenuOptions(); } - processContextMenu(node, event) { + processContextMenu(node: LGraphNode, event: CanvasMouseEvent): void { var that = this; var canvas = LGraphCanvas.active_canvas; var ref_window = canvas.getCanvasWindow(); diff --git a/src/LGraphGroup.ts b/src/LGraphGroup.ts index 505548bc3..89470a736 100644 --- a/src/LGraphGroup.ts +++ b/src/LGraphGroup.ts @@ -1,17 +1,40 @@ // @ts-nocheck +import type { Point, Size } from "./interfaces" +import type { LGraph } from "./LGraph" import { LiteGraph } from "./litegraph"; import { LGraphCanvas } from "./LGraphCanvas"; import { overlapBounding } from "./LiteGraphGlobal"; import { LGraphNode } from "./LGraphNode"; +export interface IGraphGroup { + _pos: Point + _size: Size + title: string +} + +export interface IGraphGroupFlags extends Record { + pinned?: true +} export class LGraphGroup { + pos: Point + color: string + title: string + font?: string + font_size: number + _bounding: Float32Array + _pos: Point + _size: Size + _nodes: LGraphNode[] + graph?: LGraph + flags: IGraphGroupFlags + size?: Size - constructor(title) { + constructor(title?: string) { this._ctor(title); } - _ctor(title) { + _ctor(title?: string) { this.title = title || "Group"; this.font_size = LiteGraph.DEFAULT_GROUP_FONT || 24; this.color = LGraphCanvas.node_colors.pale_blue diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index 3af9d4a61..9833511f2 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -1,9 +1,33 @@ // @ts-nocheck -import { BadgePosition } from "./LGraphBadge"; +import type { Dictionary, IContextMenuValue, IFoundSlot, INodeFlags, INodeInputSlot, INodeOutputSlot, IOptionalInputsData, ISlotType, Point, Rect, Size } from "./interfaces" +import type { LGraph } from "./LGraph" +import type { IWidget } from "./types/widgets" +import type { ISerialisedNode } from "./types/serialisation" +import type { RenderShape } from "./types/globalEnums" +import type { LGraphCanvas } from "./LGraphCanvas" +import type { CanvasMouseEvent } from "./types/events" +import { BadgePosition, LGraphBadge } from "./LGraphBadge"; import { LiteGraph } from "./litegraph"; import { isInsideRectangle } from "./LiteGraphGlobal"; import { LLink } from "./LLink"; +export type NodeId = number | string + +export interface INodePropertyInfo { + name: string + type: string + default_value: unknown +} + +export type INodeProperties = Dictionary & { + horizontal?: boolean +} + +interface IMouseOverData { + inputId: number + outputId: number +} + // ************************************************************* // Node CLASS ******* // ************************************************************* @@ -65,12 +89,151 @@ supported callbacks: */ export class LGraphNode { + // Static properties used by dynamic child classes + static title?: string + static MAX_CONSOLE?: number + static type?: string + static category?: string + static supported_extensions?: string[] + static filter?: string + static skip_list?: boolean - constructor(title) { + title?: string + graph: LGraph + id?: NodeId + type?: string + inputs: INodeInputSlot[] + outputs: INodeOutputSlot[] + // Not used + connections: unknown[] + properties: INodeProperties + properties_info: INodePropertyInfo[] + flags?: INodeFlags + widgets?: IWidget[] + + size: Point + pos: Point + _pos: Point + locked?: boolean + + // Execution order, automatically computed during run + order?: number + mode: number + last_serialization?: ISerialisedNode + serialize_widgets?: boolean + color: string + bgcolor: string + boxcolor: string + shape?: Rect + exec_version: number + action_call?: string + execute_triggered: number + action_triggered: number + widgets_up?: boolean + widgets_start_y?: number + lostFocusAt?: number + gotFocusAt?: number + badges: (LGraphBadge | (() => LGraphBadge))[] + badgePosition: BadgePosition + onOutputRemoved?(this: LGraphNode, slot: number): void + onInputRemoved?(this: LGraphNode, slot: number, input: INodeInputSlot): void + _collapsed_width: number + onBounding?(this: LGraphNode, out: Rect): void + horizontal?: boolean + console?: string[] + _level: number + _shape?: RenderShape + subgraph?: LGraph + skip_subgraph_button?: boolean + mouseOver?: IMouseOverData + is_selected?: boolean + redraw_on_mouse?: boolean + // Appears unused + optional_inputs? + // Appears unused + optional_outputs? + resizable?: boolean + clonable?: boolean + _relative_id?: number + clip_area?: boolean + ignore_remove?: boolean + has_errors?: boolean + removable?: boolean + block_delete?: boolean + + // Used in group node + setInnerNodes?(this: LGraphNode, nodes: LGraphNode[]): void + + onConnectInput?(this: LGraphNode, target_slot: number, type: unknown, output: INodeOutputSlot, node: LGraphNode, slot: number): boolean + onConnectOutput?(this: LGraphNode, slot: number, type: unknown, input: INodeInputSlot, target_node: number | LGraphNode, target_slot: number): boolean + onResize?(this: LGraphNode, size: Size): void + onPropertyChanged?(this: LGraphNode, name: string, value: unknown, prev_value?: unknown): boolean + onConnectionsChange?(this: LGraphNode, type: ISlotType, index: number, isConnected: boolean, link_info: LLink, inputOrOutput: INodeInputSlot | INodeOutputSlot): void + onInputAdded?(this: LGraphNode, input: INodeInputSlot): void + onOutputAdded?(this: LGraphNode, output: INodeOutputSlot): void + onConfigure?(this: LGraphNode, serialisedNode: ISerialisedNode): void + onSerialize?(this: LGraphNode, serialised: ISerialisedNode): any + onExecute?(this: LGraphNode, param?: unknown, options?: { action_call?: any }): void + onAction?(this: LGraphNode, action: string, param: unknown, options: { action_call?: string }): void + onDrawBackground?(this: LGraphNode, ctx: CanvasRenderingContext2D, canvas: LGraphCanvas, canvasElement: HTMLCanvasElement, mousePosition: Point): void + onNodeCreated?(this: LGraphNode): void + /** + * Callback invoked by {@link connect} to override the target slot index. Its return value overrides the target index selection. + * @param target_slot The current input slot index + * @param requested_slot The originally requested slot index - could be negative, or if using (deprecated) name search, a string + * @returns {number | null} If a number is returned, the connection will be made to that input index. + * If an invalid index or non-number (false, null, NaN etc) is returned, the connection will be cancelled. + */ + onBeforeConnectInput?(this: LGraphNode, target_slot: number, requested_slot: number | string): number | false | null + onShowCustomPanelInfo?(this: LGraphNode, panel: any): void + onAddPropertyToPanel?(this: LGraphNode, pName: string, panel: any): boolean + onWidgetChanged?(this: LGraphNode, name: string, value: unknown, old_value: unknown, w: IWidget): void + onDeselected?(this: LGraphNode): void + onKeyUp?(this: LGraphNode, e: KeyboardEvent): void + onKeyDown?(this: LGraphNode, e: KeyboardEvent): void + onSelected?(this: LGraphNode): void + getExtraMenuOptions?(this: LGraphNode, canvas: LGraphCanvas, options: IContextMenuValue[]): IContextMenuValue[] + getMenuOptions?(this: LGraphNode, canvas: LGraphCanvas): IContextMenuValue[] + onAdded?(this: LGraphNode, graph: LGraph): void + onDrawCollapsed?(this: LGraphNode, ctx: CanvasRenderingContext2D, cavnas: LGraphCanvas): boolean + onDrawForeground?(this: LGraphNode, ctx: CanvasRenderingContext2D, canvas: LGraphCanvas, canvasElement: HTMLCanvasElement): void + onMouseLeave?(this: LGraphNode, e: CanvasMouseEvent): void + getSlotMenuOptions?(this: LGraphNode, slot: any): IOptionalInputsData[] + // FIXME: Re-typing + onDropItem?(this: LGraphNode, event: Event): boolean + onDropData?(this: LGraphNode, data: string | ArrayBuffer, filename: any, file: any): void + onDropFile?(this: LGraphNode, file: any): void + onInputClick?(this: LGraphNode, index: number, e: CanvasMouseEvent): void + onInputDblClick?(this: LGraphNode, index: number, e: CanvasMouseEvent): void + onOutputClick?(this: LGraphNode, index: number, e: CanvasMouseEvent): void + onOutputDblClick?(this: LGraphNode, index: number, e: CanvasMouseEvent): void + // TODO: Return type + onGetPropertyInfo?(this: LGraphNode, property: string): any + onNodeOutputAdd?(this: LGraphNode, value): void + onNodeInputAdd?(this: LGraphNode, value): void + onMenuNodeInputs?(this: LGraphNode, entries: IOptionalInputsData[]): IOptionalInputsData[] + onMenuNodeOutputs?(this: LGraphNode, entries: IOptionalInputsData[]): IOptionalInputsData[] + onGetInputs?(this: LGraphNode): INodeInputSlot[] + onGetOutputs?(this: LGraphNode): INodeOutputSlot[] + onMouseUp?(this: LGraphNode, e: CanvasMouseEvent, pos: Point): void + onMouseEnter?(this: LGraphNode, e: CanvasMouseEvent): void + onMouseDown?(this: LGraphNode, e: CanvasMouseEvent, pos: Point, canvas: LGraphCanvas): boolean + onDblClick?(this: LGraphNode, e: CanvasMouseEvent, pos: Point, canvas: LGraphCanvas): void + onNodeTitleDblClick?(this: LGraphNode, e: CanvasMouseEvent, pos: Point, canvas: LGraphCanvas): void + onDrawTitle?(this: LGraphNode, ctx: CanvasRenderingContext2D): void + onDrawTitleText?(this: LGraphNode, ctx: CanvasRenderingContext2D, title_height: number, size: Size, scale: number, title_text_font: string, selected: boolean): void + onDrawTitleBox?(this: LGraphNode, ctx: CanvasRenderingContext2D, title_height: number, size: Size, scale: number): void + onDrawTitleBar?(this: LGraphNode, ctx: CanvasRenderingContext2D, title_height: number, size: Size, scale: number, fgcolor: any): void + onRemoved?(this: LGraphNode): void + onMouseMove?(this: LGraphNode, e: MouseEvent, pos: Point, arg2: LGraphCanvas): void + onPropertyChange?(this: LGraphNode): void + updateOutputData?(this: LGraphNode, origin_slot: number): void + + constructor(title: string) { this._ctor(title); } - _ctor(title) { + _ctor(title: string): void { this.title = title || "Unnamed"; this.size = [LiteGraph.NODE_WIDTH, 60]; this.graph = null; @@ -117,7 +280,7 @@ export class LGraphNode { * configure a node from an object containing the serialized info * @method configure */ - configure(info) { + configure(info: ISerialisedNode): void { if (this.graph) { this.graph._version++; } @@ -213,7 +376,7 @@ export class LGraphNode { * serialize the content * @method serialize */ - serialize() { + serialize(): ISerialisedNode { //create serialization object var o = { id: this.id, @@ -291,7 +454,7 @@ export class LGraphNode { } /* Creates a clone of this node */ - clone() { + clone(): LGraphNode { var node = LiteGraph.createNode(this.type); if (!node) { return null; @@ -331,7 +494,7 @@ export class LGraphNode { * serialize and stringify * @method toString */ - toString() { + toString(): string { return JSON.stringify(this.serialize()); } @@ -340,7 +503,7 @@ export class LGraphNode { * get the title string * @method getTitle */ - getTitle() { + getTitle(): string { return this.title || this.constructor.title; } @@ -350,7 +513,7 @@ export class LGraphNode { * @param {String} name * @param {*} value */ - setProperty(name, value) { + setProperty(name: string, value: TWidgetValue): void { if (!this.properties) { this.properties = {}; } @@ -381,7 +544,7 @@ export class LGraphNode { * @param {number} slot * @param {*} data */ - setOutputData(slot, data) { + setOutputData(slot: number, data: unknown): void { if (!this.outputs) { return; } @@ -418,7 +581,7 @@ export class LGraphNode { * @param {number} slot * @param {String} datatype */ - setOutputDataType(slot, type) { + setOutputDataType(slot: number, type: ISlotType): void { if (!this.outputs) { return; } @@ -448,7 +611,7 @@ export class LGraphNode { * @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 */ - getInputData(slot, force_update) { + getInputData(slot: number, force_update?: boolean): unknown { if (!this.inputs) { return; } //undefined; @@ -489,7 +652,7 @@ export class LGraphNode { * @param {number} slot * @return {String} datatype in string format */ - getInputDataType(slot) { + getInputDataType(slot: number): ISlotType { if (!this.inputs) { return null; } //undefined; @@ -521,8 +684,7 @@ export class LGraphNode { * @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 */ - getInputDataByName(slot_name, - force_update) { + getInputDataByName(slot_name: string, force_update: boolean): unknown { var slot = this.findInputSlot(slot_name); if (slot == -1) { return null; @@ -536,7 +698,7 @@ export class LGraphNode { * @param {number} slot * @return {boolean} */ - isInputConnected(slot) { + isInputConnected(slot: number): boolean { if (!this.inputs) { return false; } @@ -549,7 +711,7 @@ export class LGraphNode { * @param {number} slot * @return {Object} object or null { link: id, name: string, type: string or 0 } */ - getInputInfo(slot) { + getInputInfo(slot: number): INodeInputSlot { if (!this.inputs) { return null; } @@ -565,7 +727,7 @@ export class LGraphNode { * @param {number} slot * @return {LLink} object or null */ - getInputLink(slot) { + getInputLink(slot: number): LLink | null { if (!this.inputs) { return null; } @@ -582,7 +744,7 @@ export class LGraphNode { * @param {number} slot * @return {LGraphNode} node or null */ - getInputNode(slot) { + getInputNode(slot: number): LGraphNode { if (!this.inputs) { return null; } @@ -606,7 +768,7 @@ export class LGraphNode { * @param {string} name * @return {*} value */ - getInputOrProperty(name) { + getInputOrProperty(name: string): unknown { if (!this.inputs || !this.inputs.length) { return this.properties ? this.properties[name] : null; } @@ -629,7 +791,7 @@ export class LGraphNode { * @param {number} slot * @return {Object} object or null */ - getOutputData(slot) { + getOutputData(slot: number): unknown { if (!this.outputs) { return null; } @@ -647,7 +809,7 @@ export class LGraphNode { * @param {number} slot * @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } */ - getOutputInfo(slot) { + getOutputInfo(slot: number): INodeOutputSlot { if (!this.outputs) { return null; } @@ -663,7 +825,7 @@ export class LGraphNode { * @param {number} slot * @return {boolean} */ - isOutputConnected(slot) { + isOutputConnected(slot: number): boolean { if (!this.outputs) { return false; } @@ -679,7 +841,7 @@ export class LGraphNode { * @method isAnyOutputConnected * @return {boolean} */ - isAnyOutputConnected() { + isAnyOutputConnected(): boolean { if (!this.outputs) { return false; } @@ -697,7 +859,7 @@ export class LGraphNode { * @param {number} slot * @return {array} */ - getOutputNodes(slot) { + getOutputNodes(slot: number): LGraphNode[] { if (!this.outputs || this.outputs.length == 0) { return null; } @@ -725,7 +887,7 @@ export class LGraphNode { return r; } - addOnTriggerInput() { + addOnTriggerInput(): number { var trigS = this.findInputSlot("onTrigger"); if (trigS == -1) { //!trigS || var input = this.addInput("onTrigger", LiteGraph.EVENT, { optional: true, nameLocked: true }); @@ -734,7 +896,7 @@ export class LGraphNode { return trigS; } - addOnExecutedOutput() { + addOnExecutedOutput(): number { var trigS = this.findOutputSlot("onExecuted"); if (trigS == -1) { //!trigS || var output = this.addOutput("onExecuted", LiteGraph.ACTION, { optional: true, nameLocked: true }); @@ -743,7 +905,7 @@ export class LGraphNode { return trigS; } - onAfterExecuteNode(param, options) { + onAfterExecuteNode(param: unknown, options?: { action_call?: any }) { var trigS = this.findOutputSlot("onExecuted"); if (trigS != -1) { @@ -755,7 +917,7 @@ export class LGraphNode { } } - changeMode(modeTo) { + changeMode(modeTo: number): boolean { switch (modeTo) { case LiteGraph.ON_EVENT: // this.addOnExecutedOutput(); @@ -789,7 +951,7 @@ export class LGraphNode { * @param {*} param * @param {*} options */ - doExecute(param, options) { + doExecute(param?: unknown, options?: { action_call?: any }): void { options = options || {}; if (this.onExecute) { @@ -821,7 +983,7 @@ export class LGraphNode { * @param {String} action name * @param {*} param */ - actionDo(action, param, options) { + actionDo(action: string, param: unknown, options: { action_call?: string }): void { options = options || {}; if (this.onAction) { @@ -852,7 +1014,7 @@ export class LGraphNode { * @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all * @param {*} param */ - trigger(action, param, options) { + trigger(action: string, param: unknown, options: { action_call?: any }): void { if (!this.outputs || !this.outputs.length) { return; } @@ -875,7 +1037,7 @@ export class LGraphNode { * @param {*} param * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot */ - triggerSlot(slot, param, link_id, options) { + triggerSlot(slot: number, param: unknown, link_id: number, options: { action_call?: any }): void { options = options || {}; if (!this.outputs) { return; @@ -950,7 +1112,7 @@ export class LGraphNode { * @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 */ - clearTriggeredSlot(slot, link_id) { + clearTriggeredSlot(slot: number, link_id: number): void { if (!this.outputs) { return; } @@ -986,7 +1148,7 @@ export class LGraphNode { * @method setSize * @param {vec2} size */ - setSize(size) { + setSize(size: Size): void { this.size = size; if (this.onResize) this.onResize(this.size); @@ -1000,10 +1162,10 @@ export class LGraphNode { * @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) */ - addProperty(name, - default_value, - type, - extra_info) { + addProperty(name: string, + default_value: unknown, + type?: string, + extra_info?: Dictionary): INodePropertyInfo { var o = { name: name, type: type, default_value: default_value }; if (extra_info) { for (var i in extra_info) { @@ -1029,7 +1191,7 @@ export class LGraphNode { * @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) */ - addOutput(name, type, extra_info) { + addOutput(name?: string, type?: ISlotType, extra_info?: object): INodeOutputSlot { var output = { name: name, type: type, links: null }; if (extra_info) { for (var i in extra_info) { @@ -1057,7 +1219,7 @@ export class LGraphNode { * @method addOutputs * @param {Array} array of triplets like [[name,type,extra_info],[...]] */ - addOutputs(array) { + addOutputs(array: [string, ISlotType, Record][]): void { for (var i = 0; i < array.length; ++i) { var info = array[i]; var o = { name: info[0], type: info[1], link: null }; @@ -1088,7 +1250,7 @@ export class LGraphNode { * @method removeOutput * @param {number} slot */ - removeOutput(slot) { + removeOutput(slot: number): void { this.disconnectOutput(slot); this.outputs.splice(slot, 1); for (var i = slot; i < this.outputs.length; ++i) { @@ -1119,7 +1281,7 @@ export class LGraphNode { * @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) */ - addInput(name, type, extra_info) { + addInput(name: string, type: ISlotType, extra_info?: object): INodeInputSlot { type = type || 0; var input = { name: name, type: type, link: null }; if (extra_info) { @@ -1150,7 +1312,7 @@ export class LGraphNode { * @method addInputs * @param {Array} array of triplets like [[name,type,extra_info],[...]] */ - addInputs(array) { + addInputs(array: [string, ISlotType, Record][]): void { for (var i = 0; i < array.length; ++i) { var info = array[i]; var o = { name: info[0], type: info[1], link: null }; @@ -1180,7 +1342,7 @@ export class LGraphNode { * @method removeInput * @param {number} slot */ - removeInput(slot) { + removeInput(slot: number): void { this.disconnectInput(slot); var slot_info = this.inputs.splice(slot, 1); for (var i = slot; i < this.inputs.length; ++i) { @@ -1208,7 +1370,7 @@ export class LGraphNode { * @param {[x,y]} pos position of the connection inside the node * @param {string} direction if is input or output */ - addConnection(name, type, pos, direction) { + addConnection(name: string, type: string, pos: Point, direction: string) { var o = { name: name, type: type, @@ -1226,7 +1388,7 @@ export class LGraphNode { * @param {vec2} minHeight * @return {vec2} the total size */ - computeSize(out) { + computeSize(out?: Size): Size { if (this.constructor.size) { return this.constructor.size.concat(); } @@ -1313,7 +1475,7 @@ export class LGraphNode { return size; } - inResizeCorner(canvasX, canvasY) { + inResizeCorner(canvasX: number, canvasY: number): boolean { 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, @@ -1332,7 +1494,7 @@ export class LGraphNode { * @param {String} property name of the property * @return {Object} the object with all the available info */ - getPropertyInfo(property) { + getPropertyInfo(property: string) { var info = null; //there are several ways to define info about a property @@ -1378,7 +1540,7 @@ export class LGraphNode { * @param {Object} options the object that contains special properties of this widget * @return {Object} the created widget object */ - addWidget(type, name, value, callback, options) { + addWidget(type: string, name: string, value: any, callback: IWidget["callback"], options?: any): IWidget { if (!this.widgets) { this.widgets = []; } @@ -1427,7 +1589,7 @@ export class LGraphNode { return w; } - addCustomWidget(custom_widget) { + addCustomWidget(custom_widget: IWidget): IWidget { if (!this.widgets) { this.widgets = []; } @@ -1442,7 +1604,7 @@ export class LGraphNode { * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height] */ - getBounding(out, compute_outer) { + getBounding(out?: Float32Array, compute_outer?: boolean): Float32Array { out = out || new Float32Array(4); const nodePos = this.pos; const isCollapsed = this.flags.collapsed; @@ -1487,7 +1649,7 @@ export class LGraphNode { * @param {number} y * @return {boolean} */ - isPointInside(x, y, margin, skip_title) { + isPointInside(x: number, y: number, margin?: number, skip_title?: boolean): boolean { margin = margin || 0; var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT; @@ -1523,7 +1685,7 @@ export class LGraphNode { * @param {number} y * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } */ - getSlotInPosition(x, y) { + getSlotInPosition(x: number, y: number): IFoundSlot | null { //search for inputs var link_pos = new Float32Array(2); if (this.inputs) { @@ -1570,7 +1732,18 @@ export class LGraphNode { * @param {boolean} returnObj if the obj itself wanted * @return {number_or_object} the slot (-1 if not found) */ - findInputSlot(name, returnObj) { + findInputSlot(name: string, returnObj?: TReturn): number + findInputSlot(name: string, returnObj?: TReturn): INodeInputSlot + findInputSlot(name: string, returnObj: boolean = false) { + if (!this.inputs) { + return -1 + } + for (let i = 0, l = this.inputs.length; i < l; ++i) { + if (name == this.inputs[i].name) { + return !returnObj ? i : this.inputs[i] + } + } + return -1 if (!this.inputs) { return -1; } @@ -1589,7 +1762,9 @@ export class LGraphNode { * @param {boolean} returnObj if the obj itself wanted * @return {number_or_object} the slot (-1 if not found) */ - findOutputSlot(name, returnObj) { + findOutputSlot(name: string, returnObj?: TReturn): number + findOutputSlot(name: string, returnObj?: TReturn): INodeOutputSlot + findOutputSlot(name: string, returnObj: boolean = false) { returnObj = returnObj || false; if (!this.outputs) { return -1; @@ -1609,7 +1784,9 @@ export class LGraphNode { * @param {object} options * @return {number_or_object} the slot (-1 if not found) */ - findInputSlotFree(optsIn) { + findInputSlotFree(optsIn: { typesNotAccepted: number[], returnObj?: TReturn }): number + findInputSlotFree(optsIn: { typesNotAccepted: number[], returnObj?: TReturn }): INodeInputSlot + findInputSlotFree(optsIn: { typesNotAccepted: number[], returnObj?: boolean }) { var optsIn = optsIn || {}; var optsDef = { returnObj: false, @@ -1637,7 +1814,9 @@ export class LGraphNode { * @param {object} options * @return {number_or_object} the slot (-1 if not found) */ - findOutputSlotFree(optsIn) { + findOutputSlotFree(optsIn: { typesNotAccepted: number[], returnObj?: TReturn }): number + findOutputSlotFree(optsIn: { typesNotAccepted: number[], returnObj?: TReturn }): INodeOutputSlot + findOutputSlotFree(optsIn: { typesNotAccepted: number[], returnObj?: boolean }) { var optsIn = optsIn || {}; var optsDef = { returnObj: false, @@ -1740,7 +1919,7 @@ export class LGraphNode { * @param {string} target_type the input slot type of the target node * @return {Object} the link_info is created, otherwise null */ - connectByType(slot, target_node, target_slotType, optsIn) { + connectByType(slot: number, target_node: LGraphNode, target_slotType: ISlotType, optsIn?: { reroutes?: RerouteId[] }): LLink | null { var optsIn = optsIn || {}; var optsDef = { createEventInCase: true, @@ -1793,7 +1972,7 @@ export class LGraphNode { * @param {string} target_type the output slot type of the target node * @return {Object} the link_info is created, otherwise null */ - connectByTypeOutput(slot, source_node, source_slotType, optsIn) { + connectByTypeOutput(slot: number, source_node: LGraphNode, source_slotType: ISlotType, optsIn?: unknown): any { var optsIn = optsIn || {}; var optsDef = { createEventInCase: true, @@ -1848,7 +2027,7 @@ export class LGraphNode { * @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 */ - connect(slot, target_node, target_slot) { + connect(slot: number, target_node: LGraphNode, target_slot: ISlotType, reroutes?: RerouteId[]): LLink | null { target_slot = target_slot || 0; if (!this.graph) { @@ -2057,7 +2236,7 @@ export class LGraphNode { * @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 */ - disconnectOutput(slot, target_node) { + disconnectOutput(slot: string | number, target_node?: LGraphNode): boolean { if (slot.constructor === String) { slot = this.findOutputSlot(slot); if (slot == -1) { @@ -2213,7 +2392,7 @@ export class LGraphNode { * @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 */ - disconnectInput(slot) { + disconnectInput(slot: number | string): boolean { //seek for the output slot if (slot.constructor === String) { slot = this.findInputSlot(slot); @@ -2307,9 +2486,7 @@ export class LGraphNode { * @param {vec2} out [optional] a place to store the output, to free garbage * @return {[x,y]} the position **/ - getConnectionPos(is_input, - slot_number, - out) { + getConnectionPos(is_input: boolean, slot_number: number, out?: Point): Point { out = out || new Float32Array(2); var num_slots = 0; if (is_input && this.inputs) { @@ -2389,7 +2566,7 @@ export class LGraphNode { } /* Force align to grid */ - alignToGrid() { + alignToGrid(): void { this.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); @@ -2399,7 +2576,7 @@ export class LGraphNode { } /* Console output */ - trace(msg) { + trace(msg?: string) { if (!this.console) { this.console = []; } @@ -2414,8 +2591,7 @@ export class LGraphNode { } /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ - setDirtyCanvas(dirty_foreground, - dirty_background) { + setDirtyCanvas(dirty_foreground: boolean, dirty_background?: boolean) { if (!this.graph) { return; } @@ -2425,7 +2601,7 @@ export class LGraphNode { ]); } - loadImage(url) { + loadImage(url: string): any { var img = new Image(); img.src = LiteGraph.node_images_path + url; img.ready = false; @@ -2477,7 +2653,7 @@ export class LGraphNode { } */ /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ - captureInput(v) { + captureInput(v: boolean): void { if (!this.graph || !this.graph.list_of_graphcanvas) { return; } @@ -2508,7 +2684,7 @@ export class LGraphNode { * Collapse the node to make it smaller on the canvas * @method collapse **/ - collapse(force) { + collapse(force?: boolean): void { this.graph._version++; if (!this.collapsible && !force) { return; @@ -2544,10 +2720,10 @@ export class LGraphNode { } } - localToScreen(x, y, graphcanvas) { + localToScreen(x: number, y: number, dragAndScale: DragAndScale): Point { return [ - (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], - (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1] + (x + this.pos[0]) * dragAndScale.scale + dragAndScale.offset[0], + (y + this.pos[1]) * dragAndScale.scale + dragAndScale.offset[1] ]; } diff --git a/src/LLink.ts b/src/LLink.ts index adb1ba40e..a822cb487 100644 --- a/src/LLink.ts +++ b/src/LLink.ts @@ -1,7 +1,23 @@ -// @ts-nocheck -//this is the class in charge of storing link information +import type { ISlotType } from "./interfaces" +import type { NodeId } from "./LGraphNode" +export type LinkId = number | string + +export type SerialisedLLinkArray = [LinkId, NodeId, number, NodeId, number, ISlotType] + +//this is the class in charge of storing link information export class LLink { + id?: LinkId + type?: ISlotType + origin_id?: NodeId + origin_slot?: number + target_id?: NodeId + target_slot?: number + data?: number | string | boolean | { toToolTip?(): string } + _data?: unknown + _pos: Float32Array + _last_time?: number + constructor(id, type, origin_id, origin_slot, target_id, target_slot) { this.id = id; this.type = type; @@ -13,6 +29,8 @@ export class LLink { this._data = null; this._pos = new Float32Array(2); //center } + + // configure(o: LLink | SerialisedLLinkArray) { configure(o) { if (o.constructor === Array) { this.id = o[0]; @@ -30,7 +48,8 @@ export class LLink { this.target_slot = o.target_slot; } } - serialize() { + + serialize(): SerialisedLLinkArray { return [ this.id, this.origin_id, diff --git a/src/LiteGraphGlobal.ts b/src/LiteGraphGlobal.ts index dd33780c7..538c3722c 100644 --- a/src/LiteGraphGlobal.ts +++ b/src/LiteGraphGlobal.ts @@ -1,11 +1,14 @@ -import { LiteGraph } from "./litegraph"; -import { LGraphNode } from "./LGraphNode"; -import { drawSlot, SlotShape, SlotDirection, SlotType, LabelPosition } from "./draw"; -import { LGraph, LLink, LGraphGroup, DragAndScale, LGraphCanvas, ContextMenu, CurveEditor } from './litegraph' - -// ************************************************************* -// LiteGraph CLASS ******* -// ************************************************************* +import type { LGraph } from "./LGraph" +import type { LLink } from "./LLink" +import type { LGraphGroup } from "./LGraphGroup" +import type { DragAndScale } from "./DragAndScale" +import type { LGraphCanvas } from "./LGraphCanvas" +import type { ContextMenu } from "./ContextMenu" +import type { CurveEditor } from "./CurveEditor" +import { LiteGraph } from "./litegraph" +import { LGraphNode } from "./LGraphNode" +import { drawSlot, SlotShape, SlotDirection, SlotType, LabelPosition } from "./draw" +import type { Dictionary, ISlotType, Rect } from "./interfaces" /** * The Global Scope. It contains all the registered node classes. @@ -44,6 +47,7 @@ export class LiteGraphGlobal { NODE_BOX_OUTLINE_COLOR = "#FFF"; DEFAULT_SHADOW_COLOR = "rgba(0,0,0,0.5)"; DEFAULT_GROUP_FONT = 24; + DEFAULT_GROUP_FONT_SIZE?: any WIDGET_BGCOLOR = "#222"; WIDGET_OUTLINE_COLOR = "#666"; @@ -139,8 +143,10 @@ export class LiteGraphGlobal { 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 + // @ts-expect-error + slot_types_default_in: Record = [] // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search + // @ts-expect-error + slot_types_default_out: Record = [] // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search alt_drag_do_clone_nodes = false; // [true!] very handy, ALT click to clone and drag the new node @@ -167,30 +173,30 @@ export class LiteGraphGlobal { // Whether to highlight the bounding box of selected groups highlight_selected_group = false - LGraph = LGraph - LLink = LLink - LGraphNode = LGraphNode - LGraphGroup = LGraphGroup - DragAndScale = DragAndScale - LGraphCanvas = LGraphCanvas - ContextMenu = ContextMenu - CurveEditor = CurveEditor + LGraph: typeof LGraph + LLink: typeof LLink + LGraphNode: typeof LGraphNode + LGraphGroup: typeof LGraphGroup + DragAndScale: typeof DragAndScale + LGraphCanvas: typeof LGraphCanvas + ContextMenu: typeof ContextMenu + CurveEditor: typeof CurveEditor constructor() { //timer that works everywhere if (typeof performance != "undefined") { - this.getTime = performance.now.bind(performance); + this.getTime = performance.now.bind(performance) } else if (typeof Date != "undefined" && Date.now) { - this.getTime = Date.now.bind(Date); + this.getTime = Date.now.bind(Date) } else if (typeof process != "undefined") { - this.getTime = function() { - var t = process.hrtime(); - return t[0] * 0.001 + t[1] * 1e-6; - }; + this.getTime = function () { + var t = process.hrtime() + return t[0] * 0.001 + t[1] * 1e-6 + } } else { - this.getTime = function() { - return new Date().getTime(); - }; + this.getTime = function () { + return new Date().getTime() + } } } @@ -200,87 +206,87 @@ export class LiteGraphGlobal { * @param {String} type name of the node and path * @param {Class} base_class class containing the structure of a node */ - registerNodeType(type, base_class) { + registerNodeType(type: string, base_class: typeof LGraphNode): void { if (!base_class.prototype) { - throw "Cannot register a simple object, it must be a class with a prototype"; + throw "Cannot register a simple object, it must be a class with a prototype" } - base_class.type = type; + base_class.type = type if (LiteGraph.debug) { - console.log("Node registered: " + type); + console.log("Node registered: " + type) } - const classname = base_class.name; + const classname = base_class.name - const pos = type.lastIndexOf("/"); - base_class.category = type.substring(0, pos); + const pos = type.lastIndexOf("/") + base_class.category = type.substring(0, pos) if (!base_class.title) { - base_class.title = classname; + base_class.title = classname } //extend class for (var i in LGraphNode.prototype) { if (!base_class.prototype[i]) { - base_class.prototype[i] = LGraphNode.prototype[i]; + base_class.prototype[i] = LGraphNode.prototype[i] } } - const prev = this.registered_node_types[type]; + const prev = this.registered_node_types[type] if (prev) { - console.log("replacing node type: " + type); + 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; + delete this._shape + break case "box": - this._shape = LiteGraph.BOX_SHAPE; - break; + this._shape = LiteGraph.BOX_SHAPE + break case "round": - this._shape = LiteGraph.ROUND_SHAPE; - break; + this._shape = LiteGraph.ROUND_SHAPE + break case "circle": - this._shape = LiteGraph.CIRCLE_SHAPE; - break; + this._shape = LiteGraph.CIRCLE_SHAPE + break case "card": - this._shape = LiteGraph.CARD_SHAPE; - break; + this._shape = LiteGraph.CARD_SHAPE + break default: - this._shape = v; + this._shape = v } }, get: function () { - return this._shape; + 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]; + const ext = base_class.supported_extensions[i] if (ext && ext.constructor === String) { - this.node_types_by_file_extension[ext.toLowerCase()] = base_class; + this.node_types_by_file_extension[ext.toLowerCase()] = base_class } } } } - this.registered_node_types[type] = base_class; + this.registered_node_types[type] = base_class if (base_class.constructor.name) { - this.Nodes[classname] = base_class; + this.Nodes[classname] = base_class } if (this.onNodeTypeRegistered) { - this.onNodeTypeRegistered(type, base_class); + this.onNodeTypeRegistered(type, base_class) } if (prev && this.onNodeTypeReplaced) { - this.onNodeTypeReplaced(type, base_class, prev); + this.onNodeTypeReplaced(type, base_class, prev) } //warnings @@ -289,34 +295,33 @@ export class LiteGraphGlobal { "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"); + new base_class(base_class.title || "tmpnode") } }; - onNodeTypeRegistered(type, base_class) {} - onNodeTypeReplaced(type, base_class, prev) {} + onNodeTypeRegistered?(type: string, base_class: typeof LGraphNode): void + onNodeTypeReplaced?(type: string, base_class: typeof LGraphNode, prev: unknown): void /** * removes a node type from the system * @method unregisterNodeType * @param {String|Object} type name of the node or the node constructor itself */ - unregisterNodeType(type) { + unregisterNodeType(type: string | typeof LGraphNode): void { const base_class = type.constructor === String - // @ts-ignore ? this.registered_node_types[type] - : type; + : type if (!base_class) { - throw "node type not found: " + type; + throw "node type not found: " + type } - delete this.registered_node_types[base_class.type]; + delete this.registered_node_types[base_class.type] if (base_class.constructor.name) { - delete this.Nodes[base_class.constructor.name]; + delete this.Nodes[base_class.constructor.name] } }; @@ -326,51 +331,51 @@ export class LiteGraphGlobal { * @param {String|Object} type name of the node or the node constructor itself * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, .. */ - registerNodeAndSlotType(type, slot_type, out) { - out = out || false; + registerNodeAndSlotType(type: ISlotType | LGraphNode, slot_type: ISlotType, out?: boolean): void { + out = out || false const base_class = type.constructor === String && // @ts-ignore this.registered_node_types[type] !== "anonymous" // @ts-ignore ? this.registered_node_types[type] - : type; + : type - const class_type = base_class.constructor.type; + const class_type = base_class.constructor.type - let allTypes = []; + let allTypes = [] if (typeof slot_type === "string") { - allTypes = slot_type.split(","); + allTypes = slot_type.split(",") } else if (slot_type == this.EVENT || slot_type == this.ACTION) { - allTypes = ["_event_"]; + allTypes = ["_event_"] } else { - allTypes = ["*"]; + allTypes = ["*"] } for (let i = 0; i < allTypes.length; ++i) { - let slotType = allTypes[i]; + let slotType = allTypes[i] if (slotType === "") { - slotType = "*"; + slotType = "*" } const registerTo = out ? "registered_slot_out_types" - : "registered_slot_in_types"; + : "registered_slot_in_types" if (this[registerTo][slotType] === undefined) { - this[registerTo][slotType] = { nodes: [] }; + this[registerTo][slotType] = { nodes: [] } } if (!this[registerTo][slotType].nodes.includes(class_type)) { - this[registerTo][slotType].nodes.push(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(); + 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(); + this.slot_types_out.push(slotType.toLowerCase()) + this.slot_types_out.sort() } } } @@ -387,15 +392,16 @@ export class LiteGraphGlobal { * @param {Object} properties [optional] properties to be configurable */ wrapFunctionAsNode( - name, - func, - param_types, - return_type, - properties + name: string, + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + func: Function, + param_types: string[], + return_type: string, + properties: unknown ) { - var params = Array(func.length); - var code = ""; - var names = this.getParameterNames(func); + var params = Array(func.length) + var code = "" + var names = this.getParameterNames(func) for (var i = 0; i < names.length; ++i) { code += "this.addInput('" + @@ -404,39 +410,40 @@ export class LiteGraphGlobal { (param_types && param_types[i] ? "'" + param_types[i] + "'" : "0") + - ");\n"; + ");\n" } code += "this.addOutput('out'," + (return_type ? "'" + return_type + "'" : 0) + - ");\n"; + ");\n" if (properties) { code += - "this.properties = " + JSON.stringify(properties) + ";\n"; + "this.properties = " + JSON.stringify(properties) + ";\n" } - var classobj = Function(code); + var classobj = Function(code) // @ts-ignore - classobj.title = name.split("/").pop(); + classobj.title = name.split("/").pop() // @ts-ignore - classobj.desc = "Generated from " + func.name; + classobj.desc = "Generated from " + func.name classobj.prototype.onExecute = function onExecute() { for (var i = 0; i < params.length; ++i) { - params[i] = this.getInputData(i); + params[i] = this.getInputData(i) } - var r = func.apply(this, params); - this.setOutputData(0, r); - }; - this.registerNodeType(name, classobj); + var r = func.apply(this, params) + this.setOutputData(0, r) + } + // @ts-expect-error Required to make this kludge work + this.registerNodeType(name, classobj) }; /** * Removes all previously registered node's types */ - clearRegisteredTypes() { - this.registered_node_types = {}; - this.node_types_by_file_extension = {}; - this.Nodes = {}; - this.searchbox_extras = {}; + clearRegisteredTypes(): void { + this.registered_node_types = {} + this.node_types_by_file_extension = {} + this.Nodes = {} + this.searchbox_extras = {} }; /** @@ -445,14 +452,15 @@ export class LiteGraphGlobal { * @method addNodeMethod * @param {Function} func */ - addNodeMethod(name, func) { - LGraphNode.prototype[name] = func; + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + addNodeMethod(name: string, func: Function): void { + LGraphNode.prototype[name] = func for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; + var type = this.registered_node_types[i] if (type.prototype[name]) { - type.prototype["_" + name] = type.prototype[name]; + type.prototype["_" + name] = type.prototype[name] } //keep old in case of replacing - type.prototype[name] = func; + type.prototype[name] = func } }; @@ -463,72 +471,72 @@ export class LiteGraphGlobal { * @param {String} name a name to distinguish from other nodes * @param {Object} options to set options */ - createNode(type, title, options) { - var base_class = this.registered_node_types[type]; + createNode(type: string, title?: string, options?: Dictionary): LGraphNode { + var base_class = this.registered_node_types[type] if (!base_class) { if (this.debug) { console.log( 'GraphNode type "' + type + '" not registered.' - ); + ) } - return null; + return null } - var prototype = base_class.prototype || base_class; + var prototype = base_class.prototype || base_class - title = title || base_class.title || type; + title = title || base_class.title || type - var node = null; + var node = null if (this.catch_exceptions) { try { - node = new base_class(title); + node = new base_class(title) } catch (err) { - console.error(err); - return null; + console.error(err) + return null } } else { - node = new base_class(title); + node = new base_class(title) } - node.type = type; + node.type = type if (!node.title && title) { - node.title = title; + node.title = title } if (!node.properties) { - node.properties = {}; + node.properties = {} } if (!node.properties_info) { - node.properties_info = []; + node.properties_info = [] } if (!node.flags) { - node.flags = {}; + node.flags = {} } if (!node.size) { - node.size = node.computeSize(); + node.size = node.computeSize() //call onresize? } if (!node.pos) { - node.pos = this.DEFAULT_POSITION.concat(); + node.pos = this.DEFAULT_POSITION.concat() } if (!node.mode) { - node.mode = this.ALWAYS; + node.mode = this.ALWAYS } //extra options if (options) { for (var i in options) { - node[i] = options[i]; + node[i] = options[i] } } // callback if (node.onNodeCreated) { - node.onNodeCreated(); + node.onNodeCreated() } - return node; + return node }; /** @@ -537,8 +545,8 @@ export class LiteGraphGlobal { * @param {String} type full name of the node class. p.e. "math/sin" * @return {Class} the node class */ - getNodeType(type) { - return this.registered_node_types[type]; + getNodeType(type: string): typeof LGraphNode { + return this.registered_node_types[type] }; /** @@ -547,28 +555,28 @@ export class LiteGraphGlobal { * @param {String} category category name * @return {Array} array with all the node classes */ - getNodeTypesInCategory(category, filter) { - var r = []; + getNodeTypesInCategory(category: string, filter: any) { + var r = [] for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; + var type = this.registered_node_types[i] if (type.filter != filter) { - continue; + continue } if (category == "") { if (type.category == null) { - r.push(type); + r.push(type) } } else if (type.category == category) { - r.push(type); + r.push(type) } } if (this.auto_sort_node_types) { - r.sort(function (a, b) { return a.title.localeCompare(b.title); }); + r.sort(function (a, b) { return a.title.localeCompare(b.title) }) } - return r; + return r }; /** @@ -577,88 +585,88 @@ export class LiteGraphGlobal { * @param {String} filter only nodes with ctor.filter equal can be shown * @return {Array} array with all the names of the categories */ - getNodeTypesCategories(filter) { - var categories = { "": 1 }; + getNodeTypesCategories(filter: string): string[] { + var categories = { "": 1 } for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; + var type = this.registered_node_types[i] if (type.category && !type.skip_list) { if (type.filter != filter) - continue; - categories[type.category] = 1; + continue + categories[type.category] = 1 } } - var result = []; + var result = [] for (var i in categories) { - result.push(i); + result.push(i) } - return this.auto_sort_node_types ? result.sort() : result; + return this.auto_sort_node_types ? result.sort() : result }; //debug purposes: reloads all the js scripts that matches a wildcard - reloadNodes(folder_wildcard) { - var tmp = document.getElementsByTagName("script"); + reloadNodes(folder_wildcard: string): void { + var tmp = document.getElementsByTagName("script") //weird, this array changes by its own, so we use a copy - var script_files = []; + var script_files = [] for (var i = 0; i < tmp.length; i++) { - script_files.push(tmp[i]); + script_files.push(tmp[i]) } - var docHeadObj = document.getElementsByTagName("head")[0]; - folder_wildcard = document.location.href + folder_wildcard; + 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; + var src = script_files[i].src if (!src || src.substr(0, folder_wildcard.length) != folder_wildcard) { - continue; + continue } try { if (this.debug) { - console.log("Reloading: " + src); + console.log("Reloading: " + src) } - var dynamicScript = document.createElement("script"); - dynamicScript.type = "text/javascript"; - dynamicScript.src = src; - docHeadObj.appendChild(dynamicScript); - docHeadObj.removeChild(script_files[i]); + var dynamicScript = document.createElement("script") + dynamicScript.type = "text/javascript" + dynamicScript.src = src + docHeadObj.appendChild(dynamicScript) + docHeadObj.removeChild(script_files[i]) } catch (err) { if (this.throw_errors) { - throw err; + throw err } if (this.debug) { - console.log("Error while reloading " + src); + console.log("Error while reloading " + src) } } } if (this.debug) { - console.log("Nodes reloaded"); + console.log("Nodes reloaded") } }; //separated just to improve if it doesn't work - cloneObject(obj, target) { + cloneObject(obj: T, target?: T): T { if (obj == null) { - return null; + return null } - var r = JSON.parse(JSON.stringify(obj)); + var r = JSON.parse(JSON.stringify(obj)) if (!target) { - return r; + return r } for (var i in r) { - target[i] = r[i]; + target[i] = r[i] } - return target; + return target }; /* * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670 */ - uuidv4() { + uuidv4(): string { // @ts-ignore - return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, a => (a ^ Math.random() * 16 >> a / 4).toString(16)); + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, a => (a ^ Math.random() * 16 >> a / 4).toString(16)) }; /** @@ -668,40 +676,40 @@ export class LiteGraphGlobal { * @param {String} type_b * @return {Boolean} true if they can be connected */ - isValidConnection(type_a, type_b) { - if (type_a == "" || type_a === "*") type_a = 0; - if (type_b == "" || type_b === "*") type_b = 0; + isValidConnection(type_a: ISlotType, type_b: ISlotType): boolean { + if (type_a == "" || type_a === "*") type_a = 0 + if (type_b == "" || type_b === "*") type_b = 0 if (!type_a //generic output || !type_b // generic input || type_a == type_b //same type (is valid for triggers) || (type_a == this.EVENT && type_b == this.ACTION)) { - return true; + 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(); + 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; + 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(","); + 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 true } } } - return false; + return false }; /** @@ -712,12 +720,12 @@ export class LiteGraphGlobal { * @param {Object} data it could contain info of how the node should be configured * @return {Boolean} true if they can be connected */ - registerSearchboxExtra(node_type, description, data) { + registerSearchboxExtra(node_type: any, description: string, data: any): void { this.searchbox_extras[description.toLowerCase()] = { type: node_type, desc: description, data: data - }; + } }; /** @@ -729,61 +737,61 @@ export class LiteGraphGlobal { * @param {Function} on_error in case of an error * @return {FileReader|Promise} returns the object used to */ - fetchFile(url, type, on_complete, on_error) { - var that = this; + fetchFile(url: string | URL | Request | Blob, type: string, on_complete: (data: string | ArrayBuffer) => void, on_error: (error: unknown) => void): void | Promise { + var that = this if (!url) - return null; + return null - type = type || "text"; + type = type || "text" if (url.constructor === String) { if (url.substr(0, 4) == "http" && this.proxy) { - url = this.proxy + url.substr(url.indexOf(":") + 3); + url = this.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 + throw new Error("File not found") //it will be catch below if (type == "arraybuffer") - return response.arrayBuffer(); + return response.arrayBuffer() else if (type == "text" || type == "string") - return response.text(); + return response.text() else if (type == "json") - return response.json(); + return response.json() else if (type == "blob") - return response.blob(); + return response.blob() }) .then(function (data) { if (on_complete) - on_complete(data); + on_complete(data) }) .catch(function (error) { - console.error("error fetching file:", url); + console.error("error fetching file:", url) if (on_error) - on_error(error); - }); + on_error(error) + }) } else if (url.constructor === File || url.constructor === Blob) { - var reader = new FileReader(); + var reader = new FileReader() reader.onload = function (e) { - var v = e.target.result; + var v = e.target.result if (type == "json") // @ts-ignore - v = JSON.parse(v); + v = JSON.parse(v) if (on_complete) - on_complete(v); - }; + on_complete(v) + } if (type == "arraybuffer") - return reader.readAsArrayBuffer(url); + return reader.readAsArrayBuffer(url) else if (type == "text" || type == "json") - return reader.readAsText(url); + return reader.readAsText(url) else if (type == "blob") - return reader.readAsBinaryString(url); + return reader.readAsBinaryString(url) } - return null; + return null }; //used to create nodes from wrapping functions - getParameterNames(func) { + getParameterNames(func: Function): string[] { return (func + "") .replace(/[/][/].*$/gm, "") // strip single-line comments .replace(/\s+/g, "") // strip white space @@ -792,119 +800,119 @@ export class LiteGraphGlobal { .replace(/^[^(]*[(]/, "") // extract the parameters .replace(/=[^,]+/g, "") // strip any ES6 defaults .split(",") - .filter(Boolean); // split & filter [""] + .filter(Boolean) // split & filter [""] }; /* helper for interaction: pointer, touch, mouse Listeners used by LGraphCanvas DragAndScale ContextMenu*/ - pointerListenerAdd(oDOM, sEvIn, fCall, capture=false) { - if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!=="function"){ + pointerListenerAdd(oDOM: Node, sEvIn: string, fCall: (e: Event) => boolean | void, capture = false): void { + if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall !== "function") { //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- + return // -- break -- } - - var sMethod = LiteGraph.pointerevents_method; - var sEvent = sEvIn; - + + 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; + 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"; + case "move": { + sMethod = "touch" //sEvent = "move"; - break; + break } - case "up":{ - sMethod = "touch"; - sEvent = "end"; - break; + case "up": { + sMethod = "touch" + sEvent = "end" + break } - case "cancel":{ - sMethod = "touch"; + case "cancel": { + sMethod = "touch" //sEvent = "cancel"; - break; + break } - case "enter":{ - console.log("debug: Should I send a move event?"); // ??? - 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"); + default: { + console.warn("PointerEvent not available in this browser ? The event " + sEvent + " would not be called") } } } - switch(sEvent){ + switch (sEvent) { // @ts-expect-error //both pointer and move events case "down": case "up": case "move": case "over": case "out": case "enter": - { - oDOM.addEventListener(sMethod+sEvent, fCall, capture); - } + { + oDOM.addEventListener(sMethod + sEvent, fCall, capture) + } // @ts-expect-error // only pointerevents case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (sMethod!="mouse"){ - return oDOM.addEventListener(sMethod+sEvent, fCall, capture); + { + if (sMethod != "mouse") { + return oDOM.addEventListener(sMethod + sEvent, fCall, capture) + } } - } // not "pointer" || "mouse" default: - return oDOM.addEventListener(sEvent, fCall, capture); + return oDOM.addEventListener(sEvent, fCall, capture) } } - pointerListenerRemove(oDOM, sEvent, fCall, capture=false) { - if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!=="function"){ + pointerListenerRemove(oDOM: Node, sEvent: string, fCall: (e: Event) => boolean | void, capture = false): void { + if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall !== "function") { //console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- + return // -- break -- } - switch(sEvent){ + switch (sEvent) { // @ts-expect-error //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); + { + if (LiteGraph.pointerevents_method == "pointer" || LiteGraph.pointerevents_method == "mouse") { + oDOM.removeEventListener(LiteGraph.pointerevents_method + sEvent, fCall, capture) + } } - } // @ts-expect-error // only pointerevents case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (LiteGraph.pointerevents_method=="pointer"){ - return oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); + { + if (LiteGraph.pointerevents_method == "pointer") { + return oDOM.removeEventListener(LiteGraph.pointerevents_method + sEvent, fCall, capture) + } } - } // not "pointer" || "mouse" default: - return oDOM.removeEventListener(sEvent, fCall, capture); + return oDOM.removeEventListener(sEvent, fCall, capture) } } - getTime() {} + getTime: () => number - compareObjects(a, b) { + compareObjects(a: object, b: object): boolean { for (var i in a) { if (a[i] != b[i]) { - return false; + return false } } - return true; + return true } distance = distance - colorToString(c) { + colorToString(c: [number, number, number, number]): string { return ( "rgba(" + Math.round(c[0] * 255).toFixed() + @@ -915,107 +923,107 @@ export class LiteGraphGlobal { "," + (c.length == 4 ? c[3].toFixed(2) : "1.0") + ")" - ); + ) } isInsideRectangle = isInsideRectangle //[minx,miny,maxx,maxy] - growBounding(bounding, x, y) { + growBounding(bounding: Rect, x: number, y: number): void { if (x < bounding[0]) { - bounding[0] = x; + bounding[0] = x } else if (x > bounding[2]) { - bounding[2] = x; + bounding[2] = x } if (y < bounding[1]) { - bounding[1] = y; + bounding[1] = y } else if (y > bounding[3]) { - bounding[3] = y; + bounding[3] = y } } overlapBounding = overlapBounding //point inside bounding box - isInsideBounding(p, bb) { + isInsideBounding(p: number[], bb: number[][]): boolean { if ( p[0] < bb[0][0] || p[1] < bb[0][1] || p[0] > bb[1][0] || p[1] > bb[1][1] ) { - return false; + return false } - return true; + return true } //Convert a hex value to its decimal value - the inputted hex must be in the // format of a hex triplet - the kind we use for HTML colours. The function // will return an array with three values. - hex2num(hex) { + hex2num(hex: string): number[] { if (hex.charAt(0) == "#") { - hex = hex.slice(1); + 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; + 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++; + int1 = hex_alphabets.indexOf(hex.charAt(i)) + int2 = hex_alphabets.indexOf(hex.charAt(i + 1)) + value[k] = int1 * 16 + int2 + k++ } - return value; + return value } //Give a array with three values as the argument and the function will return // the corresponding hex triplet. - num2hex(triplet) { - var hex_alphabets = "0123456789ABCDEF"; - var hex = "#"; - var int1, int2; + num2hex(triplet: number[]): string { + var hex_alphabets = "0123456789ABCDEF" + var hex = "#" + var int1, int2 for (var i = 0; i < 3; i++) { - int1 = triplet[i] / 16; - int2 = triplet[i] % 16; + int1 = triplet[i] / 16 + int2 = triplet[i] % 16 - hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); + hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2) } - return hex; + return hex } - closeAllContextMenus(ref_window) { - ref_window = ref_window || window; + closeAllContextMenus(ref_window: Window): void { + ref_window = ref_window || window - var elements = ref_window.document.querySelectorAll(".litecontextmenu"); + var elements = ref_window.document.querySelectorAll(".litecontextmenu") if (!elements.length) { - return; + return } - var result = []; + var result = [] for (var i = 0; i < elements.length; i++) { - result.push(elements[i]); + result.push(elements[i]) } - for (var i=0; i < result.length; i++) { + for (var i = 0; i < result.length; i++) { if (result[i].close) { - result[i].close(); + result[i].close() } else if (result[i].parentNode) { - result[i].parentNode.removeChild(result[i]); + result[i].parentNode.removeChild(result[i]) } } } - extendClass(target, origin) { + extendClass(target: any, origin: any): void { for (var i in origin) { //copy class properties if (target.hasOwnProperty(i)) { - continue; + continue } - target[i] = origin[i]; + target[i] = origin[i] } if (origin.prototype) { @@ -1023,12 +1031,12 @@ export class LiteGraphGlobal { for (var i in origin.prototype) { //only enumerable if (!origin.prototype.hasOwnProperty(i)) { - continue; + continue } if (target.prototype.hasOwnProperty(i)) { //avoid overwriting existing ones - continue; + continue } //copy getters @@ -1036,9 +1044,9 @@ export class LiteGraphGlobal { target.prototype.__defineGetter__( i, origin.prototype.__lookupGetter__(i) - ); + ) } else { - target.prototype[i] = origin.prototype[i]; + target.prototype[i] = origin.prototype[i] } //and setters @@ -1046,7 +1054,7 @@ export class LiteGraphGlobal { target.prototype.__defineSetter__( i, origin.prototype.__lookupSetter__(i) - ); + ) } } } @@ -1056,22 +1064,22 @@ export class LiteGraphGlobal { export function distance(a, b) { return Math.sqrt( (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) - ); + ) } export function isInsideRectangle(x, y, left, top, width, height) { if (left < x && left + width > x && top < y && top + height > y) { - return true; + return true } - return false; + return false } //bounding overlap, format: [ startx, starty, width, height ] export 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]; + 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 || @@ -1079,7 +1087,7 @@ export function overlapBounding(a, b) { A_end_x < b[0] || A_end_y < b[1] ) { - return false; + return false } - return true; + return true } diff --git a/src/draw.ts b/src/draw.ts index 45548fd61..559ebc834 100644 --- a/src/draw.ts +++ b/src/draw.ts @@ -1,4 +1,5 @@ -import type { Vector2, INodeSlot } from "../public/litegraph"; +import type { Vector2 } from "./litegraph"; +import type { INodeSlot } from "./interfaces" export enum SlotType { Array = "array", diff --git a/src/interfaces.ts b/src/interfaces.ts new file mode 100644 index 000000000..49644f229 --- /dev/null +++ b/src/interfaces.ts @@ -0,0 +1,138 @@ +import type { ContextMenu } from "./ContextMenu" +import type { LGraphNode } from "./LGraphNode" +import type { LinkDirection, RenderShape } from "./types/globalEnums" +import type { LinkId } from "./LLink" +import type { SlotDirection } from "./draw" + +export type Dictionary = { [key: string]: T } + +export type CanvasColour = string | CanvasGradient | CanvasPattern + +export interface IInputOrOutput { + // If an input, this will be defined + input?: INodeInputSlot + // If an output, this will be defined + output?: INodeOutputSlot +} + +export interface IFoundSlot extends IInputOrOutput { + // Slot index + slot: number + // Centre point of the rendered slot connection + link_pos: Point +} + +/** A point on the canvas: x, y */ +export type Point = [x: number, y: number] | Float32Array | Float64Array + +/** A size: width, height */ +export type Size = [width: number, height: number] | Float32Array | Float64Array + +/** A very firm array */ +type ArRect = [x: number, y: number, width: number, height: number] + +/** A rectangle starting at top-left coordinates: x, y, width, height */ +export type Rect = ArRect | Float32Array | Float64Array + +// TODO: Need regular arrays also? +export type Rect32 = Float32Array + +/** Union of property names that are of type Match */ +export type KeysOfType = { [P in keyof T]: T[P] extends Match ? P : never }[keyof T] + +/** A new type that contains only the properties of T that are of type Match */ +export type PickByType = { [P in keyof T]: Extract } + +/** The names of all methods and functions in T */ +export type MethodNames = KeysOfType any> + +export interface IBoundaryNodes { + top: LGraphNode + right: LGraphNode + bottom: LGraphNode + left: LGraphNode +} + +export type Direction = "top" | "bottom" | "left" | "right" + +// TODO: Rename IOptionalSlotsData +export interface IOptionalInputsData { + content: string + value? + className?: string +} + +export type ISlotType = number | string + +export interface INodeSlot { + name: string + type: ISlotType + dir?: LinkDirection & SlotDirection + removable?: boolean + shape?: RenderShape + not_subgraph_input?: boolean + color_off?: CanvasColour + color_on?: CanvasColour + label?: string + locked?: boolean + nameLocked?: boolean + pos?: Point + widget?: unknown +} + +export interface INodeFlags { + skip_repeated_outputs?: boolean + allow_interaction?: boolean + pinned?: boolean + collapsed?: boolean +} + +export interface INodeInputSlot extends INodeSlot { + link?: LinkId + not_subgraph_input?: boolean +} + +export interface INodeOutputSlot extends INodeSlot { + links?: LinkId[] + _data?: unknown + slot_index?: number + not_subgraph_output?: boolean +} + +/** Links */ +export interface ConnectingLink extends IInputOrOutput { + node: LGraphNode + slot: number + pos: Point + direction?: LinkDirection +} + +/** ContextMenu */ +export interface IContextMenuOptions { + ignore_item_callbacks?: boolean + title?: string + parentMenu?: ContextMenu + className?: string + event?: MouseEvent + extra?: unknown + scroll_speed?: number + left?: number + top?: number + scale?: string + node?: LGraphNode + autoopen?: boolean + callback?(value?: unknown, options?: unknown, event?: MouseEvent, previous_menu?: ContextMenu, node?: LGraphNode): void +} + +export interface IContextMenuValue { + title?: string + content: string + has_submenu?: boolean + disabled?: boolean + className?: any + submenu?: unknown + property?: string + type?: string + slot?: IFoundSlot + callback?: IContextMenuOptions["callback"] +} diff --git a/src/litegraph.js b/src/litegraph.js deleted file mode 100755 index bb3025e56..000000000 --- a/src/litegraph.js +++ /dev/null @@ -1,23 +0,0 @@ -import { LiteGraphGlobal } from "./LiteGraphGlobal"; -import { LGraph } from "./LGraph" -import { LLink } from "./LLink" -import { LGraphNode } from "./LGraphNode"; -import { LGraphGroup } from "./LGraphGroup"; -import { DragAndScale } from "./DragAndScale"; -import { LGraphCanvas } from "./LGraphCanvas"; -import { ContextMenu } from "./ContextMenu"; -import { CurveEditor } from "./CurveEditor"; -import { loadPolyfills } from "./polyfills"; - -export { LGraph, LLink, LGraphNode, LGraphGroup, DragAndScale, LGraphCanvas, ContextMenu, CurveEditor } - -export const LiteGraph = new LiteGraphGlobal() - -export function clamp(v, a, b) { - return a > v ? a : b < v ? b : v; -}; - -// Load legacy polyfills -loadPolyfills(); - -export { LGraphBadge, BadgePosition } from "./LGraphBadge" diff --git a/src/litegraph.ts b/src/litegraph.ts new file mode 100644 index 000000000..ad105332d --- /dev/null +++ b/src/litegraph.ts @@ -0,0 +1,106 @@ +import type { Point, ConnectingLink } from "./interfaces" +import type { INodeSlot, INodeInputSlot, INodeOutputSlot, CanvasColour, Direction, IBoundaryNodes, IContextMenuOptions, IContextMenuValue, IFoundSlot, IInputOrOutput, INodeFlags, IOptionalInputsData, ISlotType, KeysOfType, MethodNames, PickByType, Rect, Rect32, Size } from "./interfaces" +import type { SlotShape, LabelPosition, SlotDirection, SlotType } from "./draw" +import type { IWidget } from "./types/widgets" +import type { TitleMode } from "./types/globalEnums" +import { LiteGraphGlobal } from "./LiteGraphGlobal" +import { loadPolyfills } from "./polyfills" + +import { LGraph } from "./LGraph" +import { LGraphCanvas } from "./LGraphCanvas" +import { DragAndScale } from "./DragAndScale" +import { LGraphNode } from "./LGraphNode" +import { LGraphGroup } from "./LGraphGroup" +import { LLink } from "./LLink" +import { ContextMenu } from "./ContextMenu" +import { CurveEditor } from "./CurveEditor" +import { LGraphBadge, BadgePosition } from "./LGraphBadge" + +export const LiteGraph = new LiteGraphGlobal() +export { LGraph, LGraphCanvas, DragAndScale, LGraphNode, LGraphGroup, LLink, ContextMenu, CurveEditor } +export { INodeSlot, INodeInputSlot, INodeOutputSlot, ConnectingLink, CanvasColour, Direction, IBoundaryNodes, IContextMenuOptions, IContextMenuValue, IFoundSlot, IInputOrOutput, INodeFlags, IOptionalInputsData, ISlotType, KeysOfType, MethodNames, PickByType, Rect, Rect32, Size } +export { IWidget } +export { LGraphBadge, BadgePosition } +export { SlotShape, LabelPosition, SlotDirection, SlotType } + +// TODO: Remove legacy accessors +LiteGraph.LGraph = LGraph +LiteGraph.LLink = LLink +LiteGraph.LGraphNode = LGraphNode +LiteGraph.LGraphGroup = LGraphGroup +LiteGraph.DragAndScale = DragAndScale +LiteGraph.LGraphCanvas = LGraphCanvas +LiteGraph.ContextMenu = ContextMenu +LiteGraph.CurveEditor = CurveEditor + +export function clamp(v: number, a: number, b: number): number { + return a > v ? a : b < v ? b : v +}; + +// Load legacy polyfills +loadPolyfills() + +// Backwards compat + +// Type definitions for litegraph.js 0.7.0 +// Project: litegraph.js +// Definitions by: NateScarlet +export type Vector2 = Point +export type Vector4 = [number, number, number, number] + +export interface IContextMenuItem { + content: string + callback?: ContextMenuEventListener + /** Used as innerHTML for extra child element */ + title?: string + disabled?: boolean + has_submenu?: boolean + submenu?: { + options: IContextMenuItem[] + } & IContextMenuOptions + className?: string +} + +export type ContextMenuEventListener = ( + value: IContextMenuItem, + options: IContextMenuOptions, + event: MouseEvent, + parentMenu: ContextMenu | undefined, + node: LGraphNode +) => boolean | void + +export interface LinkReleaseContext { + node_to?: LGraphNode + node_from?: LGraphNode + slot_from: INodeSlot + type_filter_in?: string + type_filter_out?: string +} + +export interface LinkReleaseContextExtended { + links: ConnectingLink[] +} + +export type LiteGraphCanvasEventType = "empty-release" | "empty-double-click" | "group-double-click" + +export interface LiteGraphCanvasEvent extends CustomEvent<{ + subType: string + originalEvent: Event + linkReleaseContext?: LinkReleaseContextExtended + group?: LGraphGroup +}> { } + +export interface LiteGraphCanvasGroupEvent extends CustomEvent<{ + subType: "group-double-click" + originalEvent: MouseEvent + group: LGraphGroup +}> { } + +/** https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#lgraphnode */ + +export interface LGraphNodeConstructor { + nodeData: any + new(): T +} + +// End backwards compat diff --git a/src/types/events.ts b/src/types/events.ts new file mode 100644 index 000000000..b6464328c --- /dev/null +++ b/src/types/events.ts @@ -0,0 +1,40 @@ +/** + * Event interfaces for event extension + */ + +/** For Canvas*Event - adds graph space co-ordinates (property names are shipped) */ +export interface ICanvasPosition { + /** X co-ordinate of the event, in graph space (NOT canvas space) */ + canvasX?: number + /** Y co-ordinate of the event, in graph space (NOT canvas space) */ + canvasY?: number +} + +/** For Canvas*Event */ +export interface IDeltaPosition { + deltaX?: number + deltaY?: number +} + +/** PointerEvent with canvasX/Y and deltaX/Y properties */ +export interface CanvasPointerEvent extends PointerEvent, CanvasMouseEvent { } + +/** MouseEvent with canvasX/Y and deltaX/Y properties */ +export interface CanvasMouseEvent extends MouseEvent, ICanvasPosition, IDeltaPosition { + dragging?: boolean + click_time?: number + dataTransfer?: unknown +} + +/** WheelEvent with canvasX/Y properties */ +export interface CanvasWheelEvent extends WheelEvent, ICanvasPosition { + dragging?: boolean + click_time?: number + dataTransfer?: unknown +} + +/** DragEvent with canvasX/Y and deltaX/Y properties */ +export interface CanvasDragEvent extends DragEvent, ICanvasPosition, IDeltaPosition { } + +/** TouchEvent with canvasX/Y and deltaX/Y properties */ +export interface CanvasTouchEvent extends TouchEvent, ICanvasPosition, IDeltaPosition { } diff --git a/src/types/globalEnums.ts b/src/types/globalEnums.ts new file mode 100644 index 000000000..d71a1d9fa --- /dev/null +++ b/src/types/globalEnums.ts @@ -0,0 +1,51 @@ +/** Node slot type - input or output */ +export enum NodeSlotType { + INPUT = 1, + OUTPUT = 2, +} + +/** Shape that an object will render as - used by nodes and slots */ +export enum RenderShape { + BOX = 1, + ROUND = 2, + CIRCLE = 3, + CARD = 4, + ARROW = 5, + /** intended for slot arrays */ + GRID = 6, +} + +/** The direction that a link point will flow towards - e.g. horizontal outputs are right by default */ +export enum LinkDirection { + NONE = 0, + UP = 1, + DOWN = 2, + LEFT = 3, + RIGHT = 4, + CENTER = 5, +} + +/** The path calculation that links follow */ +export enum LinkRenderType { + /** Juts out from the input & output a little @see LinkDirection, then a straight line between them */ + STRAIGHT_LINK = 0, + /** 90° angles, clean and box-like */ + LINEAR_LINK = 1, + /** Smooth curved links - default */ + SPLINE_LINK = 2, +} + +export enum TitleMode { + NORMAL_TITLE = 0, + NO_TITLE = 1, + TRANSPARENT_TITLE = 2, + AUTOHIDE_TITLE = 3, +} + +export enum LGraphEventMode { + ALWAYS = 0, + ON_EVENT = 1, + NEVER = 2, + ON_TRIGGER = 3, + BYPASS = 4, +} diff --git a/src/types/serialisation.ts b/src/types/serialisation.ts new file mode 100644 index 000000000..cd63228b6 --- /dev/null +++ b/src/types/serialisation.ts @@ -0,0 +1,61 @@ +import type { CanvasColour, Dictionary, INodeFlags, INodeInputSlot, INodeOutputSlot, Point, Rect, Size } from "@/interfaces" +import type { LGraph } from "@/LGraph" +import type { IGraphGroupFlags, LGraphGroup } from "@/LGraphGroup" +import type { LGraphNode, NodeId } from "@/LGraphNode" +import type { LiteGraph } from "@/litegraph" +import type { LinkId, LLink } from "@/LLink" +import type { TWidgetValue } from "@/types/widgets" + +/** Serialised LGraphNode */ +export interface ISerialisedNode { + title?: string + id?: NodeId + type?: string + pos?: Point + size?: Size + flags?: INodeFlags + order?: number + mode?: number + outputs?: INodeOutputSlot[] + inputs?: INodeInputSlot[] + properties?: Dictionary + shape?: Rect + boxcolor?: CanvasColour + color?: CanvasColour + bgcolor?: string + widgets_values?: TWidgetValue[] +} + +/** Contains serialised graph elements */ +export type ISerialisedGraph< + TNode = ReturnType, + TLink = ReturnType, + TGroup = ReturnType +> = { + last_node_id: LGraph["last_node_id"] + last_link_id: LGraph["last_link_id"] + last_reroute_id: LGraph["last_reroute_id"] + nodes: TNode[] + links: TLink[] | LLink[] + groups: TGroup[] + config: LGraph["config"] + version: typeof LiteGraph.VERSION + extra?: unknown +} + +/** Serialised LGraphGroup */ +export interface ISerialisedGroup { + title: string + bounding: number[] + color: string + font_size: number + flags?: IGraphGroupFlags +} + +export type TClipboardLink = [targetRelativeIndex: number, originSlot: number, nodeRelativeIndex: number, targetSlot: number, targetNodeId: NodeId] + +/** */ +export interface IClipboardContents { + nodes?: ISerialisedNode[] + links?: TClipboardLink[] +} diff --git a/src/types/widgets.ts b/src/types/widgets.ts new file mode 100644 index 000000000..4cc9db7e6 --- /dev/null +++ b/src/types/widgets.ts @@ -0,0 +1,124 @@ +import { CanvasColour, Point, Size } from "@/interfaces" +import type { LGraphCanvas, LGraphNode } from "@/litegraph" +import type { CanvasMouseEvent } from "./events" + +export interface IWidgetOptions extends Record { + on?: string + off?: string + max?: number + min?: number + slider_color?: CanvasColour + marker_color?: CanvasColour + precision?: number + read_only?: boolean + step?: number + y?: number + multiline?: boolean + // TODO: Confirm this + property?: string + + hasOwnProperty?(arg0: string): any + // values?(widget?: IWidget, node?: LGraphNode): any + values?: TValue[] + callback?: IWidget["callback"] + + onHide?(widget: IWidget): void +} + +/** + * A widget for a node. + * @typescript + * All types are based on IBaseWidget - additions can be made there or directly on individual types. + * + * Implemented as a discriminative union of widget types, so this type itself cannot be extended. + * Recommend declaration merging any properties that use IWidget (e.g. {@link LGraphNode.widgets}) with a new type alias. + * @see ICustomWidget + */ +export type IWidget = IBooleanWidget | INumericWidget | IStringWidget | IMultilineStringWidget | IComboWidget | ICustomWidget + +export interface IBooleanWidget extends IBaseWidget { + type?: "toggle" + value: boolean +} + +/** Any widget that uses a numeric backing */ +export interface INumericWidget extends IBaseWidget { + type?: "slider" | "number" + value: number +} + +/** A combo-box widget (dropdown, select, etc) */ +export interface IComboWidget extends IBaseWidget { + type?: "combo" + value: string | number + options: IWidgetOptions +} + +export type IStringWidgetType = IStringWidget["type"] | IMultilineStringWidget["type"] + +/** A widget with a string value */ +export interface IStringWidget extends IBaseWidget { + type?: "string" | "text" | "button" + value: string +} + +/** A widget with a string value and a multiline text input */ +export interface IMultilineStringWidget extends IBaseWidget { + type?: "multiline" + value: string + + /** HTML textarea element */ + element?: TElement +} + +/** A custom widget - accepts any value and has no built-in special handling */ +export interface ICustomWidget extends IBaseWidget { + type?: "custom" + value: string | object + + element?: TElement +} + + +/** + * Valid widget types. TS cannot provide easily extensible type safety for this at present. + * Override linkedWidgets[] + * Values not in this list will not result in litegraph errors, however they will be treated the same as "custom". + */ +export type TWidgetType = IWidget["type"] +export type TWidgetValue = IWidget["value"] + +/** + * The base type for all widgets. Should not be implemented directly. + * @see IWidget + */ +export interface IBaseWidget { + linkedWidgets?: IWidget[] + + options: IWidgetOptions + marker?: number + label?: string + clicked?: boolean + name?: string + /** Widget type (see {@link TWidgetType}) */ + type?: TWidgetType + value?: TWidgetValue + y?: number + last_y?: number + width?: number + disabled?: boolean + + tooltip?: string + + /** HTML widget element */ + element?: TElement + + // TODO: Confirm this format + callback?(value: any, canvas?: LGraphCanvas, node?: LGraphNode, pos?: Point, e?: CanvasMouseEvent): void + onRemove?(): void + beforeQueued?(): void + + mouse?(event: CanvasMouseEvent, arg1: number[], node: LGraphNode): boolean + draw?(ctx: CanvasRenderingContext2D, node: LGraphNode, widget_width: number, y: number, H: number): void + computeSize?(width: number): Size +} diff --git a/vite.config.mts b/vite.config.mts index 7cf946518..4988ca631 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -5,7 +5,7 @@ import dts from 'vite-plugin-dts' export default defineConfig({ build: { lib: { - entry: path.resolve(__dirname, 'src/litegraph.js'), + entry: path.resolve(__dirname, 'src/litegraph'), name: 'litegraph.js', fileName: (format) => `litegraph.${format}.js`, formats: ['es', 'umd'] @@ -21,4 +21,9 @@ export default defineConfig({ outDir: 'dist', }), ], + resolve: { + alias: { + '@': '/src' + } + } }) \ No newline at end of file