diff --git a/src/lib/litegraph/src/LGraph.ts b/src/lib/litegraph/src/LGraph.ts index 561836d1eb8..04f2a4a4fe8 100644 --- a/src/lib/litegraph/src/LGraph.ts +++ b/src/lib/litegraph/src/LGraph.ts @@ -842,8 +842,13 @@ export class LGraph if (!list_of_graphcanvas) return for (const c of list_of_graphcanvas) { - // eslint-disable-next-line prefer-spread - c[action]?.apply(c, params) + const method = c[action] + + if (typeof method === 'function') { + const args = + params == null ? [] : Array.isArray(params) ? params : [params] + ;(method as (...args: unknown[]) => unknown).apply(c, args) + } } } diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index 3e09ba5fdaa..7dee812fca2 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -52,6 +52,11 @@ import type { LinkSegment, NewNodePosition, NullableProperties, + Panel, + PanelButton, + PanelWidget, + PanelWidgetCallback, + PanelWidgetOptions, Point, Positionable, ReadOnlyRect, @@ -94,7 +99,7 @@ import type { SubgraphIO } from './types/serialisation' import type { NeverNever, PickNevers } from './types/utility' -import type { IBaseWidget } from './types/widgets' +import type { IBaseWidget, TWidgetValue } from './types/widgets' import { alignNodes, distributeNodes, getBoundaryNodes } from './utils/arrange' import { findFirstNode, getAllNestedItems } from './utils/collections' import { resolveConnectingLinkColor } from './utils/linkColors' @@ -232,6 +237,15 @@ interface ICreatePanelOptions { height?: number | string } +interface SlotTypeDefaultNodeOpts { + node?: string + title?: string + properties?: Record + inputs?: [string, string][] + outputs?: [string, string][] + json?: Parameters[0] +} + const cursors = { NE: 'nesw-resize', SE: 'nwse-resize', @@ -693,9 +707,9 @@ export class LGraphCanvas implements CustomEventDispatcher _highlight_input?: INodeInputSlot // TODO: Check if panels are used /** @deprecated Panels */ - node_panel?: any + node_panel?: Panel /** @deprecated Panels */ - options_panel?: any + options_panel?: Panel _bg_img?: HTMLImageElement _pattern?: CanvasPattern _pattern_img?: HTMLImageElement @@ -1249,11 +1263,13 @@ export class LGraphCanvas implements CustomEventDispatcher function inner_clicked( this: ContextMenuDivElement, - v: IContextMenuValue, - e: any, - prev: any + v?: string | IContextMenuValue, + _options?: unknown, + e?: MouseEvent, + prev?: ContextMenu ) { if (!node) return + if (!v || typeof v === 'string') return // TODO: This is a static method, so the below "that" appears broken. if (v.callback) void v.callback.call(this, node, v, e, prev) @@ -6354,8 +6370,7 @@ export class LGraphCanvas implements CustomEventDispatcher ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in if (slotTypesDefault?.[fromSlotType]) { - // TODO: Remove "any" kludge - let nodeNewType: any = false + let nodeNewType: string | Record | false = false if (typeof slotTypesDefault[fromSlotType] == 'object') { for (const typeX in slotTypesDefault[fromSlotType]) { if ( @@ -6373,11 +6388,13 @@ export class LGraphCanvas implements CustomEventDispatcher nodeNewType = slotTypesDefault[fromSlotType] } if (nodeNewType) { - // TODO: Remove "any" kludge - let nodeNewOpts: any = false - if (typeof nodeNewType == 'object' && nodeNewType.node) { - nodeNewOpts = nodeNewType - nodeNewType = nodeNewType.node + let nodeNewOpts: SlotTypeDefaultNodeOpts | undefined + let nodeTypeStr: string + if (typeof nodeNewType == 'object') { + nodeNewOpts = nodeNewType as SlotTypeDefaultNodeOpts + nodeTypeStr = nodeNewOpts.node ?? '' + } else { + nodeTypeStr = nodeNewType } // that.graph.beforeChange(); @@ -6386,7 +6403,7 @@ export class LGraphCanvas implements CustomEventDispatcher const nodeX = opts.position[0] + opts.posAdd[0] + xSizeFix const nodeY = opts.position[1] + opts.posAdd[1] + ySizeFix const pos = [nodeX, nodeY] - const newNode = LiteGraph.createNode(nodeNewType, nodeNewOpts.title, { + const newNode = LiteGraph.createNode(nodeTypeStr, nodeNewOpts?.title, { pos }) if (newNode) { @@ -6399,20 +6416,14 @@ export class LGraphCanvas implements CustomEventDispatcher } if (nodeNewOpts.inputs) { newNode.inputs = [] - for (const i in nodeNewOpts.inputs) { - newNode.addOutput( - nodeNewOpts.inputs[i][0], - nodeNewOpts.inputs[i][1] - ) + for (const input of nodeNewOpts.inputs) { + newNode.addInput(input[0], input[1]) } } if (nodeNewOpts.outputs) { newNode.outputs = [] - for (const i in nodeNewOpts.outputs) { - newNode.addOutput( - nodeNewOpts.outputs[i][0], - nodeNewOpts.outputs[i][1] - ) + for (const output of nodeNewOpts.outputs) { + newNode.addOutput(output[0], output[1]) } } if (nodeNewOpts.json) { @@ -6909,8 +6920,7 @@ export class LGraphCanvas implements CustomEventDispatcher // hide on mouse leave if (options.hide_on_mouse_leave) { - // FIXME: Remove "any" kludge - let prevent_timeout: any = false + let prevent_timeout = 0 let timeout_close: ReturnType | null = null LiteGraph.pointerListenerAdd(dialog, 'enter', function () { if (timeout_close) { @@ -7104,8 +7114,7 @@ export class LGraphCanvas implements CustomEventDispatcher // join node after inserting if (options.node_from) { - // FIXME: any - let iS: any = false + let iS: number | false = false switch (typeof options.slot_from) { case 'string': iS = options.node_from.findOutputSlot(options.slot_from) @@ -7131,8 +7140,8 @@ export class LGraphCanvas implements CustomEventDispatcher // try with first if no name set iS = 0 } - if (options.node_from.outputs[iS] !== undefined) { - if (iS !== false && iS > -1) { + if (iS !== false && options.node_from.outputs[iS] !== undefined) { + if (iS > -1) { if (node == null) throw new TypeError( 'options.slot_from was null when showing search box' @@ -7149,8 +7158,7 @@ export class LGraphCanvas implements CustomEventDispatcher } } if (options.node_to) { - // FIXME: any - let iS: any = false + let iS: number | false = false switch (typeof options.slot_from) { case 'string': iS = options.node_to.findInputSlot(options.slot_from) @@ -7176,8 +7184,8 @@ export class LGraphCanvas implements CustomEventDispatcher // try with first if no name set iS = 0 } - if (options.node_to.inputs[iS] !== undefined) { - if (iS !== false && iS > -1) { + if (iS !== false && options.node_to.inputs[iS] !== undefined) { + if (iS > -1) { if (node == null) throw new TypeError( 'options.slot_from was null when showing search box' @@ -7240,13 +7248,16 @@ export class LGraphCanvas implements CustomEventDispatcher const filter = graphcanvas.filter || graphcanvas.graph.filter - // FIXME: any // filter by type preprocess - let sIn: any = false - let sOut: any = false + let sIn: HTMLSelectElement | null = null + let sOut: HTMLSelectElement | null = null if (options.do_type_filter && that.search_box) { - sIn = that.search_box.querySelector('.slot_in_type_filter') - sOut = that.search_box.querySelector('.slot_out_type_filter') + sIn = that.search_box.querySelector( + '.slot_in_type_filter' + ) + sOut = that.search_box.querySelector( + '.slot_out_type_filter' + ) } const keys = Object.keys(LiteGraph.registered_node_types) @@ -7264,7 +7275,7 @@ export class LGraphCanvas implements CustomEventDispatcher // add general type if filtering if ( options.show_general_after_typefiltered && - (sIn.value || sOut.value) + (sIn?.value || sOut?.value) ) { const filtered_extra: string[] = [] for (const i in LiteGraph.registered_node_types) { @@ -7289,7 +7300,7 @@ export class LGraphCanvas implements CustomEventDispatcher // check il filtering gave no results if ( - (sIn.value || sOut.value) && + (sIn?.value || sOut?.value) && helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter ) { @@ -7337,8 +7348,10 @@ export class LGraphCanvas implements CustomEventDispatcher if (options.do_type_filter && !opts.skipFilter) { const sType = type - let sV = - opts.inTypeOverride !== false ? opts.inTypeOverride : sIn.value + let sV: string | undefined = + typeof opts.inTypeOverride === 'string' + ? opts.inTypeOverride + : sIn?.value // type is stored if (sIn && sV && LiteGraph.registered_slot_in_types[sV]?.nodes) { const doesInc = @@ -7346,8 +7359,9 @@ export class LGraphCanvas implements CustomEventDispatcher if (doesInc === false) return false } - sV = sOut.value - if (opts.outTypeOverride !== false) sV = opts.outTypeOverride + sV = sOut?.value + if (typeof opts.outTypeOverride === 'string') + sV = opts.outTypeOverride // type is stored if (sOut && sV && LiteGraph.registered_slot_out_types[sV]?.nodes) { const doesInc = @@ -7628,15 +7642,14 @@ export class LGraphCanvas implements CustomEventDispatcher return dialog } - createPanel(title: string, options: ICreatePanelOptions) { + createPanel(title: string, options: ICreatePanelOptions): Panel { options = options || {} - // TODO: any kludge - const root: any = document.createElement('div') + const root = document.createElement('div') as Panel root.className = 'litegraph dialog' root.innerHTML = "
" - root.header = root.querySelector('.dialog-header') + root.header = root.querySelector('.dialog-header')! if (options.width) root.style.width = @@ -7653,11 +7666,11 @@ export class LGraphCanvas implements CustomEventDispatcher }) root.header.append(close) } - root.title_element = root.querySelector('.dialog-title') + root.title_element = root.querySelector('.dialog-title')! root.title_element.textContent = title - root.content = root.querySelector('.dialog-content') - root.alt_content = root.querySelector('.dialog-alt-content') - root.footer = root.querySelector('.dialog-footer') + root.content = root.querySelector('.dialog-content')! + root.alt_content = root.querySelector('.dialog-alt-content')! + root.footer = root.querySelector('.dialog-footer')! root.footer.style.marginTop = '-96px' root.close = function () { @@ -7667,7 +7680,7 @@ export class LGraphCanvas implements CustomEventDispatcher } // function to swap panel content - root.toggleAltContent = function (force: unknown) { + root.toggleAltContent = function (force?: boolean) { let vTo: string let vAlt: string if (force !== undefined) { @@ -7681,7 +7694,7 @@ export class LGraphCanvas implements CustomEventDispatcher root.content.style.display = vAlt } - root.toggleFooterVisibility = function (force: unknown) { + root.toggleFooterVisibility = function (force?: boolean) { let vTo: string if (force !== undefined) { vTo = force ? 'block' : 'none' @@ -7695,7 +7708,11 @@ export class LGraphCanvas implements CustomEventDispatcher this.content.innerHTML = '' } - root.addHTML = function (code: string, classname: string, on_footer: any) { + root.addHTML = function ( + code: string, + classname?: string, + on_footer?: boolean + ) { const elem = document.createElement('div') if (classname) elem.className = classname elem.innerHTML = code @@ -7704,9 +7721,12 @@ export class LGraphCanvas implements CustomEventDispatcher return elem } - root.addButton = function (name: any, callback: any, options: any) { - // TODO: any kludge - const elem: any = document.createElement('button') + root.addButton = function ( + name: string, + callback: () => void, + options?: unknown + ): PanelButton { + const elem = document.createElement('button') as PanelButton elem.textContent = name elem.options = options elem.classList.add('btn') @@ -7723,20 +7743,18 @@ export class LGraphCanvas implements CustomEventDispatcher root.addWidget = function ( type: string, - name: any, - value: unknown, - options: { label?: any; type?: any; values?: any; callback?: any }, - callback: (arg0: any, arg1: any, arg2: any) => void - ) { + name: string, + value: TWidgetValue, + options?: PanelWidgetOptions, + callback?: PanelWidgetCallback + ): PanelWidget { options = options || {} let str_value = String(value) type = type.toLowerCase() if (type == 'number' && typeof value === 'number') str_value = value.toFixed(3) - // FIXME: any kludge - const elem: HTMLDivElement & { options?: unknown; value?: unknown } = - document.createElement('div') + const elem: PanelWidget = document.createElement('div') as PanelWidget elem.className = 'property' elem.innerHTML = "" @@ -7744,7 +7762,6 @@ export class LGraphCanvas implements CustomEventDispatcher if (!nameSpan) throw new TypeError('Property name element was null.') nameSpan.textContent = options.label || name - // TODO: any kludge const value_element: HTMLSpanElement | null = elem.querySelector('.property_value') if (!value_element) throw new TypeError('Property name element was null.') @@ -7756,7 +7773,8 @@ export class LGraphCanvas implements CustomEventDispatcher if (type == 'code') { elem.addEventListener('click', function () { - root.inner_showCodePad(this.dataset['property']) + const property = this.dataset['property'] + if (property) root.inner_showCodePad?.(property) }) } else if (type == 'boolean') { elem.classList.add('boolean') @@ -7799,19 +7817,16 @@ export class LGraphCanvas implements CustomEventDispatcher value_element.textContent = str_value ?? '' value_element.addEventListener('click', function (event) { - const values = options.values || [] + const values = options?.values || [] const propname = this.parentElement?.dataset['property'] - const inner_clicked = (v: string | null) => { - // node.setProperty(propname,v); - // graphcanvas.dirty_canvas = true; - this.textContent = v + const inner_clicked = (v?: string) => { + this.textContent = v ?? null innerChange(propname, v) return false } new LiteGraph.ContextMenu(values, { event, className: 'dark', - // @ts-expect-error fixme ts strict error - callback signature mismatch callback: inner_clicked }) }) @@ -7819,9 +7834,10 @@ export class LGraphCanvas implements CustomEventDispatcher root.content.append(elem) - function innerChange(name: string | undefined, value: unknown) { - options.callback?.(name, value, options) - callback?.(name, value, options) + function innerChange(name: string | undefined, value: TWidgetValue) { + const opts = options || {} + opts.callback?.(name, value, opts) + callback?.(name, value, opts) } return elem @@ -7850,7 +7866,7 @@ export class LGraphCanvas implements CustomEventDispatcher }, onClose: () => { this.NODEPANEL_IS_OPEN = false - this.node_panel = null + this.node_panel = undefined } }) this.node_panel = panel @@ -7871,11 +7887,9 @@ export class LGraphCanvas implements CustomEventDispatcher panel.addHTML('

Properties

') - const fUpdate = ( - name: string, - value: string | number | boolean | object | undefined - ) => { + const fUpdate: PanelWidgetCallback = (name, value) => { if (!this.graph) throw new NullGraphError() + if (!name) return this.graph.beforeChange(node) switch (name) { case 'Title': @@ -7979,7 +7993,7 @@ export class LGraphCanvas implements CustomEventDispatcher panel.alt_content.innerHTML = "" const textarea: HTMLTextAreaElement = - panel.alt_content.querySelector('textarea') + panel.alt_content.querySelector('textarea')! const fDoneWith = function () { panel.toggleAltContent(false) panel.toggleFooterVisibility(true) diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index 0ca41a7320f..9b8d4d15363 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -41,6 +41,7 @@ import type { INodeSlotContextItem, IPinnable, ISlotType, + Panel, Point, Positionable, ReadOnlyRect, @@ -96,9 +97,12 @@ export type NodeId = number | string export type NodeProperty = string | number | boolean | object interface INodePropertyInfo { - name: string + name?: string type?: string - default_value: NodeProperty | undefined + default_value?: NodeProperty + widget?: string + label?: string + values?: TWidgetValue[] } interface IMouseOverData { @@ -606,8 +610,8 @@ export class 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 + onShowCustomPanelInfo?(this: LGraphNode, panel: Panel): void + onAddPropertyToPanel?(this: LGraphNode, pName: string, panel: Panel): boolean onWidgetChanged?( this: LGraphNode, name: string, @@ -667,8 +671,7 @@ export class LGraphNode index: number, e: CanvasPointerEvent ): void - // TODO: Return type - onGetPropertyInfo?(this: LGraphNode, property: string): any + onGetPropertyInfo?(this: LGraphNode, property: string): INodePropertyInfo onNodeOutputAdd?(this: LGraphNode, value: unknown): void onNodeInputAdd?(this: LGraphNode, value: unknown): void onMenuNodeInputs?( diff --git a/src/lib/litegraph/src/contextMenuCompat.ts b/src/lib/litegraph/src/contextMenuCompat.ts index 38865bf206d..c1f039cfa42 100644 --- a/src/lib/litegraph/src/contextMenuCompat.ts +++ b/src/lib/litegraph/src/contextMenuCompat.ts @@ -81,6 +81,7 @@ class LegacyMenuCompat { return currentImpl }, set: (newImpl: LGraphCanvas[K]) => { + if (!newImpl) return const fnKey = `${methodName as string}:${newImpl.toString().slice(0, 100)}` if (!this.hasWarned.has(fnKey) && this.currentExtension) { this.hasWarned.add(fnKey) diff --git a/src/lib/litegraph/src/interfaces.ts b/src/lib/litegraph/src/interfaces.ts index 0983770a11a..aad3dff3fae 100644 --- a/src/lib/litegraph/src/interfaces.ts +++ b/src/lib/litegraph/src/interfaces.ts @@ -1,5 +1,6 @@ import type { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle' import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' +import type { TWidgetValue } from '@/lib/litegraph/src/types/widgets' import type { ContextMenu } from './ContextMenu' import type { LGraphNode, NodeId } from './LGraphNode' @@ -493,3 +494,68 @@ export interface Hoverable extends HasBoundingRect { onPointerEnter?(e?: CanvasPointerEvent): void onPointerLeave?(e?: CanvasPointerEvent): void } + +/** + * Callback for panel widget value changes. + */ +export type PanelWidgetCallback = ( + name: string | undefined, + value: TWidgetValue, + options: PanelWidgetOptions +) => void + +/** + * Options for panel widgets. + */ +export interface PanelWidgetOptions { + label?: string + type?: string + widget?: string + values?: Array | null> + callback?: PanelWidgetCallback +} + +/** + * A button element with optional options property. + */ +export interface PanelButton extends HTMLButtonElement { + options?: unknown +} + +/** + * A widget element with options and value properties. + */ +export interface PanelWidget extends HTMLDivElement { + options?: PanelWidgetOptions + value?: TWidgetValue +} + +/** + * A dialog panel created by LGraphCanvas.createPanel(). + * Extends HTMLDivElement with additional properties and methods for panel management. + */ +export interface Panel extends HTMLDivElement { + header: HTMLElement + title_element: HTMLSpanElement + content: HTMLDivElement + alt_content: HTMLDivElement + footer: HTMLDivElement + node?: LGraphNode + onOpen?: () => void + onClose?: () => void + close(): void + toggleAltContent(force?: boolean): void + toggleFooterVisibility(force?: boolean): void + clear(): void + addHTML(code: string, classname?: string, on_footer?: boolean): HTMLDivElement + addButton(name: string, callback: () => void, options?: unknown): PanelButton + addSeparator(): void + addWidget( + type: string, + name: string, + value: TWidgetValue, + options?: PanelWidgetOptions, + callback?: PanelWidgetCallback + ): PanelWidget + inner_showCodePad?(property: string): void +} diff --git a/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts b/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts index 045cb2de16b..a50633848f1 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts @@ -195,7 +195,7 @@ export abstract class SubgraphIONodeBase< if (!(options.length > 0)) return new LiteGraph.ContextMenu(options, { - event: event as any, + event, title: slot.name || 'Subgraph Output', callback: (item: IContextMenuValue) => { this.#onSlotMenuAction(item, slot, event)