From 067d80c4eddeeda0ff15f0bd262ee297bc308374 Mon Sep 17 00:00:00 2001 From: Alexander Brown Date: Thu, 29 Jan 2026 18:18:58 -0800 Subject: [PATCH] refactor: migrate ES private fields to TypeScript private for Vue Proxy compatibility (#8440) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Migrates ECMAScript private fields (`#`) to TypeScript private (`private`) across LiteGraph to fix Vue Proxy reactivity incompatibility. ## Problem ES private fields (`#field`) are incompatible with Vue's Proxy-based reactivity system - accessing `#field` through a Proxy throws `TypeError: Cannot read private member from an object whose class did not declare it`. ## Solution - Converted all `#field` to `private _field` across 10 phases - Added `toJSON()` methods to `LGraph`, `NodeSlot`, `NodeInputSlot`, and `NodeOutputSlot` to prevent circular reference errors during serialization (TypeScript private fields are visible to `JSON.stringify` unlike true ES private fields) - Made `DragAndScale.element.data` non-enumerable to break canvas circular reference chain ## Testing - All 4027 unit tests pass - Added 9 new serialization tests to catch future circular reference issues - Browser tests (undo/redo, save workflows) verified working ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8440-refactor-migrate-ES-private-fields-to-TypeScript-private-for-Vue-Proxy-compatibility-2f76d73d365081a3bd82d429a3e0fcb7) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp --- src/extensions/core/groupNode.ts | 8 +- src/extensions/core/widgetInputs.ts | 30 +- src/lib/litegraph/src/CanvasPointer.ts | 90 +++--- src/lib/litegraph/src/DragAndScale.ts | 4 +- src/lib/litegraph/src/LGraph.ts | 22 +- src/lib/litegraph/src/LGraphCanvas.ts | 277 +++++++++------- src/lib/litegraph/src/LGraphNode.ts | 106 +++--- src/lib/litegraph/src/LGraphNodeProperties.ts | 40 +-- .../src/__snapshots__/LGraph.test.ts.snap | 304 ++---------------- .../litegraph/src/canvas/InputIndicators.ts | 16 +- src/lib/litegraph/src/canvas/LinkConnector.ts | 42 +-- .../src/infrastructure/ConstrainedSize.ts | 26 +- .../litegraph/src/infrastructure/Rectangle.ts | 12 +- src/lib/litegraph/src/node/NodeInputSlot.ts | 14 +- src/lib/litegraph/src/node/NodeOutputSlot.ts | 13 +- src/lib/litegraph/src/node/NodeSlot.ts | 29 +- src/lib/litegraph/src/serialization.test.ts | 131 ++++++++ .../src/subgraph/ExecutableNodeDTO.ts | 14 +- .../src/subgraph/SubgraphIONodeBase.ts | 20 +- .../litegraph/src/subgraph/SubgraphNode.ts | 20 +- src/lib/litegraph/src/widgets/BaseWidget.ts | 12 +- src/scripts/api.ts | 18 +- src/scripts/ui.ts | 24 +- src/scripts/ui/components/asyncDialog.ts | 8 +- src/scripts/ui/components/button.ts | 14 +- src/scripts/ui/components/popup.ts | 20 +- src/scripts/ui/dialog.ts | 6 +- src/utils/migration/migrateReroute.ts | 38 +-- 28 files changed, 653 insertions(+), 705 deletions(-) create mode 100644 src/lib/litegraph/src/serialization.test.ts diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index db543c30a..ed7dcbb20 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -292,10 +292,10 @@ export class GroupNodeConfig { this.processNode(node, seenInputs, seenOutputs) } - for (const p of this.#convertedToProcess) { + for (const p of this._convertedToProcess) { p() } - this.#convertedToProcess = [] + this._convertedToProcess = [] if (!this.nodeDef) return await app.registerNodeDef(`${PREFIX}${SEPARATOR}` + this.name, this.nodeDef) useNodeDefStore().addNodeDef(this.nodeDef) @@ -773,7 +773,7 @@ export class GroupNodeConfig { } } - #convertedToProcess: (() => void)[] = [] + private _convertedToProcess: (() => void)[] = [] processNodeInputs( node: GroupNodeData, seenInputs: Record, @@ -804,7 +804,7 @@ export class GroupNodeConfig { ) // Converted inputs have to be processed after all other nodes as they'll be at the end of the list - this.#convertedToProcess.push(() => + this._convertedToProcess.push(() => this.processConvertedWidgets( inputs, node, diff --git a/src/extensions/core/widgetInputs.ts b/src/extensions/core/widgetInputs.ts index 630f2b092..40aef8a56 100644 --- a/src/extensions/core/widgetInputs.ts +++ b/src/extensions/core/widgetInputs.ts @@ -103,7 +103,7 @@ export class PrimitiveNode extends LGraphNode { override onAfterGraphConfigured() { if (this.outputs[0].links?.length && !this.widgets?.length) { - this.#onFirstConnection() + this._onFirstConnection() // Populate widget values from config data if (this.widgets && this.widgets_values) { @@ -116,7 +116,7 @@ export class PrimitiveNode extends LGraphNode { } // Merge values if required - this.#mergeWidgetConfig() + this._mergeWidgetConfig() } } @@ -133,11 +133,11 @@ export class PrimitiveNode extends LGraphNode { const links = this.outputs[0].links if (connected) { if (links?.length && !this.widgets?.length) { - this.#onFirstConnection() + this._onFirstConnection() } } else { // We may have removed a link that caused the constraints to change - this.#mergeWidgetConfig() + this._mergeWidgetConfig() if (!links?.length) { this.onLastDisconnect() @@ -159,7 +159,7 @@ export class PrimitiveNode extends LGraphNode { } if (this.outputs[slot].links?.length) { - const valid = this.#isValidConnection(input) + const valid = this._isValidConnection(input) if (valid) { // On connect of additional outputs, copy our value to their widget this.applyToGraph([{ target_id: target_node.id, target_slot } as LLink]) @@ -170,7 +170,7 @@ export class PrimitiveNode extends LGraphNode { return true } - #onFirstConnection(recreating?: boolean) { + private _onFirstConnection(recreating?: boolean) { // First connection can fire before the graph is ready on initial load so random things can be missing if (!this.outputs[0].links || !this.graph) { this.onLastDisconnect() @@ -204,7 +204,7 @@ export class PrimitiveNode extends LGraphNode { this.outputs[0].name = type this.outputs[0].widget = widget - this.#createWidget( + this._createWidget( widget[CONFIG] ?? config, theirNode, widget.name, @@ -213,7 +213,7 @@ export class PrimitiveNode extends LGraphNode { ) } - #createWidget( + private _createWidget( inputData: InputSpec, node: LGraphNode, widgetName: string, @@ -307,8 +307,8 @@ export class PrimitiveNode extends LGraphNode { recreateWidget() { const values = this.widgets?.map((w) => w.value) - this.#removeWidgets() - this.#onFirstConnection(true) + this._removeWidgets() + this._onFirstConnection(true) if (values?.length && this.widgets) { for (let i = 0; i < this.widgets.length; i++) this.widgets[i].value = values[i] @@ -316,7 +316,7 @@ export class PrimitiveNode extends LGraphNode { return this.widgets?.[0] } - #mergeWidgetConfig() { + private _mergeWidgetConfig() { // Merge widget configs if the node has multiple outputs const output = this.outputs[0] const links = output.links ?? [] @@ -348,11 +348,11 @@ export class PrimitiveNode extends LGraphNode { const theirInput = theirNode.inputs[link.target_slot] // Call is valid connection so it can merge the configs when validating - this.#isValidConnection(theirInput, hasConfig) + this._isValidConnection(theirInput, hasConfig) } } - #isValidConnection(input: INodeInputSlot, forceUpdate?: boolean) { + private _isValidConnection(input: INodeInputSlot, forceUpdate?: boolean) { // Only allow connections where the configs match const output = this.outputs?.[0] const config2 = (input.widget?.[GET_CONFIG] as () => InputSpec)?.() @@ -367,7 +367,7 @@ export class PrimitiveNode extends LGraphNode { ) } - #removeWidgets() { + private _removeWidgets() { if (this.widgets) { // Allow widgets to cleanup for (const w of this.widgets) { @@ -398,7 +398,7 @@ export class PrimitiveNode extends LGraphNode { this.outputs[0].name = 'connect to widget input' delete this.outputs[0].widget - this.#removeWidgets() + this._removeWidgets() } } diff --git a/src/lib/litegraph/src/CanvasPointer.ts b/src/lib/litegraph/src/CanvasPointer.ts index b4175e953..3bd101cf5 100644 --- a/src/lib/litegraph/src/CanvasPointer.ts +++ b/src/lib/litegraph/src/CanvasPointer.ts @@ -31,17 +31,17 @@ export class CanvasPointer { /** Maximum offset from click location */ static get maxClickDrift() { - return this.#maxClickDrift + return this._maxClickDrift } static set maxClickDrift(value) { - this.#maxClickDrift = value - this.#maxClickDrift2 = value * value + this._maxClickDrift = value + this._maxClickDrift2 = value * value } - static #maxClickDrift = 6 + private static _maxClickDrift = 6 /** {@link maxClickDrift} squared. Used to calculate click drift without `sqrt`. */ - static #maxClickDrift2 = this.#maxClickDrift ** 2 + private static _maxClickDrift2 = this._maxClickDrift ** 2 /** Assume that "wheel" events with both deltaX and deltaY less than this value are trackpad gestures. */ static trackpadThreshold = 60 @@ -153,18 +153,18 @@ export class CanvasPointer { * Therefore, simply setting this value twice will execute the first callback. */ get finally() { - return this.#finally + return this._finally } set finally(value) { try { - this.#finally?.() + this._finally?.() } finally { - this.#finally = value + this._finally = value } } - #finally?: () => unknown + private _finally?: () => unknown constructor(element: Element) { this.element = element @@ -197,7 +197,7 @@ export class CanvasPointer { // Primary button released - treat as pointerup. if (!(e.buttons & eDown.buttons)) { - this.#completeClick(e) + this._completeClick(e) this.reset() return } @@ -209,8 +209,8 @@ export class CanvasPointer { const longerThanBufferTime = e.timeStamp - eDown.timeStamp > CanvasPointer.bufferTime - if (longerThanBufferTime || !this.#hasSamePosition(e, eDown)) { - this.#setDragStarted(e) + if (longerThanBufferTime || !this._hasSamePosition(e, eDown)) { + this._setDragStarted(e) } } @@ -221,13 +221,13 @@ export class CanvasPointer { up(e: CanvasPointerEvent): boolean { if (e.button !== this.eDown?.button) return false - this.#completeClick(e) + this._completeClick(e) const { dragStarted } = this this.reset() return !dragStarted } - #completeClick(e: CanvasPointerEvent): void { + private _completeClick(e: CanvasPointerEvent): void { const { eDown } = this if (!eDown) return @@ -236,11 +236,11 @@ export class CanvasPointer { if (this.dragStarted) { // A move event already started drag this.onDragEnd?.(e) - } else if (!this.#hasSamePosition(e, eDown)) { + } else if (!this._hasSamePosition(e, eDown)) { // Teleport without a move event (e.g. tab out, move, tab back) - this.#setDragStarted() + this._setDragStarted() this.onDragEnd?.(e) - } else if (this.onDoubleClick && this.#isDoubleClick()) { + } else if (this.onDoubleClick && this._isDoubleClick()) { // Double-click event this.onDoubleClick(e) this.eLastDown = undefined @@ -258,10 +258,10 @@ export class CanvasPointer { * @param tolerance2 The maximum distance (squared) before the positions are considered different * @returns `true` if the two events were no more than {@link maxClickDrift} apart, otherwise `false` */ - #hasSamePosition( + private _hasSamePosition( a: PointerEvent, b: PointerEvent, - tolerance2 = CanvasPointer.#maxClickDrift2 + tolerance2 = CanvasPointer._maxClickDrift2 ): boolean { const drift = dist2(a.clientX, a.clientY, b.clientX, b.clientY) return drift <= tolerance2 @@ -271,21 +271,21 @@ export class CanvasPointer { * Checks whether the pointer is currently past the max click drift threshold. * @returns `true` if the latest pointer event is past the the click drift threshold */ - #isDoubleClick(): boolean { + private _isDoubleClick(): boolean { const { eDown, eLastDown } = this if (!eDown || !eLastDown) return false // Use thrice the drift distance for double-click gap - const tolerance2 = (3 * CanvasPointer.#maxClickDrift) ** 2 + const tolerance2 = (3 * CanvasPointer._maxClickDrift) ** 2 const diff = eDown.timeStamp - eLastDown.timeStamp return ( diff > 0 && diff < CanvasPointer.doubleClickTime && - this.#hasSamePosition(eDown, eLastDown, tolerance2) + this._hasSamePosition(eDown, eLastDown, tolerance2) ) } - #setDragStarted(eMove?: CanvasPointerEvent): void { + private _setDragStarted(eMove?: CanvasPointerEvent): void { this.dragStarted = true this.onDragStart?.(this, eMove) delete this.onDragStart @@ -303,14 +303,14 @@ export class CanvasPointer { const timeSinceLastEvent = Math.max(0, now - this.lastWheelEventTime) this.lastWheelEventTime = now - if (this.#isHighResWheelEvent(e, now)) { + if (this._isHighResWheelEvent(e, now)) { this.detectedDevice = 'mouse' - } else if (this.#isWithinCooldown(timeSinceLastEvent)) { - if (this.#shouldBufferLinuxEvent(e)) { - this.#bufferLinuxEvent(e, now) + } else if (this._isWithinCooldown(timeSinceLastEvent)) { + if (this._shouldBufferLinuxEvent(e)) { + this._bufferLinuxEvent(e, now) } } else { - this.#updateDeviceMode(e, now) + this._updateDeviceMode(e, now) this.hasReceivedWheelEvent = true } @@ -321,7 +321,7 @@ export class CanvasPointer { * Validates buffered high res wheel events and switches to mouse mode if pattern matches. * @returns `true` if switched to mouse mode */ - #isHighResWheelEvent(event: WheelEvent, now: number): boolean { + private _isHighResWheelEvent(event: WheelEvent, now: number): boolean { if (!this.bufferedLinuxEvent || this.bufferedLinuxEventTime <= 0) { return false } @@ -329,15 +329,15 @@ export class CanvasPointer { const timeSinceBuffer = now - this.bufferedLinuxEventTime if (timeSinceBuffer > CanvasPointer.maxHighResBufferTime) { - this.#clearLinuxBuffer() + this._clearLinuxBuffer() return false } if ( event.deltaX === 0 && - this.#isLinuxWheelPattern(this.bufferedLinuxEvent.deltaY, event.deltaY) + this._isLinuxWheelPattern(this.bufferedLinuxEvent.deltaY, event.deltaY) ) { - this.#clearLinuxBuffer() + this._clearLinuxBuffer() return true } @@ -347,7 +347,7 @@ export class CanvasPointer { /** * Checks if we're within the cooldown period where mode switching is disabled. */ - #isWithinCooldown(timeSinceLastEvent: number): boolean { + private _isWithinCooldown(timeSinceLastEvent: number): boolean { const isFirstEvent = !this.hasReceivedWheelEvent const cooldownExpired = timeSinceLastEvent >= CanvasPointer.trackpadMaxGap return !isFirstEvent && !cooldownExpired @@ -356,23 +356,23 @@ export class CanvasPointer { /** * Updates the device mode based on event patterns. */ - #updateDeviceMode(event: WheelEvent, now: number): void { - if (this.#isTrackpadPattern(event)) { + private _updateDeviceMode(event: WheelEvent, now: number): void { + if (this._isTrackpadPattern(event)) { this.detectedDevice = 'trackpad' - } else if (this.#isMousePattern(event)) { + } else if (this._isMousePattern(event)) { this.detectedDevice = 'mouse' } else if ( this.detectedDevice === 'trackpad' && - this.#shouldBufferLinuxEvent(event) + this._shouldBufferLinuxEvent(event) ) { - this.#bufferLinuxEvent(event, now) + this._bufferLinuxEvent(event, now) } } /** * Clears the buffered Linux wheel event and associated timer. */ - #clearLinuxBuffer(): void { + private _clearLinuxBuffer(): void { this.bufferedLinuxEvent = undefined this.bufferedLinuxEventTime = 0 if (this.linuxBufferTimeoutId !== undefined) { @@ -385,7 +385,7 @@ export class CanvasPointer { * Checks if the event matches trackpad input patterns. * @param event The wheel event to check */ - #isTrackpadPattern(event: WheelEvent): boolean { + private _isTrackpadPattern(event: WheelEvent): boolean { // Two-finger panning: non-zero deltaX AND deltaY if (event.deltaX !== 0 && event.deltaY !== 0) return true @@ -399,7 +399,7 @@ export class CanvasPointer { * Checks if the event matches mouse wheel input patterns. * @param event The wheel event to check */ - #isMousePattern(event: WheelEvent): boolean { + private _isMousePattern(event: WheelEvent): boolean { const absoluteDeltaY = Math.abs(event.deltaY) // Primary threshold for switching from trackpad to mouse @@ -417,7 +417,7 @@ export class CanvasPointer { * Checks if the event should be buffered as a potential Linux wheel event. * @param event The wheel event to check */ - #shouldBufferLinuxEvent(event: WheelEvent): boolean { + private _shouldBufferLinuxEvent(event: WheelEvent): boolean { const absoluteDeltaY = Math.abs(event.deltaY) const isInLinuxRange = absoluteDeltaY >= 10 && absoluteDeltaY < 60 const isVerticalOnly = event.deltaX === 0 @@ -436,7 +436,7 @@ export class CanvasPointer { * @param event The event to buffer * @param now The current timestamp */ - #bufferLinuxEvent(event: WheelEvent, now: number): void { + private _bufferLinuxEvent(event: WheelEvent, now: number): void { if (this.linuxBufferTimeoutId !== undefined) { clearTimeout(this.linuxBufferTimeoutId) } @@ -446,7 +446,7 @@ export class CanvasPointer { // Set timeout to clear buffer after 10ms this.linuxBufferTimeoutId = setTimeout(() => { - this.#clearLinuxBuffer() + this._clearLinuxBuffer() }, CanvasPointer.maxHighResBufferTime) } @@ -455,7 +455,7 @@ export class CanvasPointer { * @param deltaY1 The first deltaY value * @param deltaY2 The second deltaY value */ - #isLinuxWheelPattern(deltaY1: number, deltaY2: number): boolean { + private _isLinuxWheelPattern(deltaY1: number, deltaY2: number): boolean { const absolute1 = Math.abs(deltaY1) const absolute2 = Math.abs(deltaY2) diff --git a/src/lib/litegraph/src/DragAndScale.ts b/src/lib/litegraph/src/DragAndScale.ts index d2e35252d..e193bb1ea 100644 --- a/src/lib/litegraph/src/DragAndScale.ts +++ b/src/lib/litegraph/src/DragAndScale.ts @@ -81,7 +81,7 @@ export class DragAndScale { * Returns `true` if the current state has changed from the previous state. * @returns `true` if the current state has changed from the previous state, otherwise `false`. */ - #stateHasChanged(): boolean { + private _stateHasChanged(): boolean { const current = this.state const previous = this.lastState @@ -95,7 +95,7 @@ export class DragAndScale { computeVisibleArea(viewport: Rect | undefined): void { const { scale, offset, visible_area } = this - if (this.#stateHasChanged()) { + if (this._stateHasChanged()) { this.onChanged?.(scale, offset) copyState(this.state, this.lastState) } diff --git a/src/lib/litegraph/src/LGraph.ts b/src/lib/litegraph/src/LGraph.ts index b060afaec..fd7b7c000 100644 --- a/src/lib/litegraph/src/LGraph.ts +++ b/src/lib/litegraph/src/LGraph.ts @@ -244,7 +244,7 @@ export class LGraph } /** Internal only. Not required for serialisation; calculated on deserialise. */ - #lastFloatingLinkId: number = 0 + private _lastFloatingLinkId: number = 0 private readonly floatingLinksInternal: Map = new Map() get floatingLinks(): ReadonlyMap { @@ -365,7 +365,7 @@ export class LGraph this.reroutes.clear() this.floatingLinksInternal.clear() - this.#lastFloatingLinkId = 0 + this._lastFloatingLinkId = 0 // other scene stuff this._groups = [] @@ -1304,7 +1304,7 @@ export class LGraph addFloatingLink(link: LLink): LLink { if (link.id === -1) { - link.id = ++this.#lastFloatingLinkId + link.id = ++this._lastFloatingLinkId } this.floatingLinksInternal.set(link.id, link) @@ -2175,8 +2175,16 @@ export class LGraph } } + /** + * Custom JSON serialization to prevent circular reference errors. + * Called automatically by JSON.stringify(). + */ + toJSON(): ISerialisedGraph { + return this.serialize() + } + /** @returns The drag and scale state of the first attached canvas, otherwise `undefined`. */ - #getDragAndScale(): DragAndScaleState | undefined { + private _getDragAndScale(): DragAndScaleState | undefined { const ds = this.list_of_graphcanvas?.at(0)?.ds if (ds) return { scale: ds.scale, offset: ds.offset } } @@ -2216,7 +2224,7 @@ export class LGraph // Save scale and offset const extra = { ...this.extra } - if (LiteGraph.saveViewportWithGraph) extra.ds = this.#getDragAndScale() + if (LiteGraph.saveViewportWithGraph) extra.ds = this._getDragAndScale() if (!extra.ds) delete extra.ds const data: ReturnType = { @@ -2406,8 +2414,8 @@ export class LGraph const floatingLink = LLink.create(linkData) this.addFloatingLink(floatingLink) - if (floatingLink.id > this.#lastFloatingLinkId) - this.#lastFloatingLinkId = floatingLink.id + if (floatingLink.id > this._lastFloatingLinkId) + this._lastFloatingLinkId = floatingLink.id } } diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index 541165102..ebd4d2c69 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -316,17 +316,17 @@ export class LGraphCanvas implements CustomEventDispatcher selectionChanged: false } - #subgraph?: Subgraph + private _subgraph?: Subgraph get subgraph(): Subgraph | undefined { - return this.#subgraph + return this._subgraph } set subgraph(value: Subgraph | undefined) { - if (value !== this.#subgraph) { - this.#subgraph = value + if (value !== this._subgraph) { + this._subgraph = value if (value) this.dispatch('litegraph:set-graph', { - oldGraph: this.#subgraph, + oldGraph: this._subgraph, newGraph: value }) } @@ -361,7 +361,7 @@ export class LGraphCanvas implements CustomEventDispatcher this.canvas.dispatchEvent(new CustomEvent(type, { detail })) } - #updateCursorStyle() { + private _updateCursorStyle() { if (!this.state.shouldSetCursor) return const crosshairItems = @@ -398,7 +398,7 @@ export class LGraphCanvas implements CustomEventDispatcher set read_only(value: boolean) { this.state.readOnly = value - this.#updateCursorStyle() + this._updateCursorStyle() } get isDragging(): boolean { @@ -415,7 +415,7 @@ export class LGraphCanvas implements CustomEventDispatcher set hoveringOver(value: CanvasItem) { this.state.hoveringOver = value - this.#updateCursorStyle() + this._updateCursorStyle() } /** @deprecated Replace all references with {@link pointer}.{@link CanvasPointer.isDown isDown}. */ @@ -435,7 +435,7 @@ export class LGraphCanvas implements CustomEventDispatcher set dragging_canvas(value: boolean) { this.state.draggingCanvas = value - this.#updateCursorStyle() + this._updateCursorStyle() } /** @@ -450,16 +450,16 @@ export class LGraphCanvas implements CustomEventDispatcher return `normal ${LiteGraph.NODE_SUBTEXT_SIZE}px ${LiteGraph.NODE_FONT}` } - #maximumFrameGap = 0 + private _maximumFrameGap = 0 /** Maximum frames per second to render. 0: unlimited. Default: 0 */ public get maximumFps() { - return this.#maximumFrameGap > Number.EPSILON - ? this.#maximumFrameGap / 1000 + return this._maximumFrameGap > Number.EPSILON + ? this._maximumFrameGap / 1000 : 0 } public set maximumFps(value) { - this.#maximumFrameGap = value > Number.EPSILON ? 1000 / value : 0 + this._maximumFrameGap = value > Number.EPSILON ? 1000 / value : 0 } /** @@ -660,12 +660,12 @@ export class LGraphCanvas implements CustomEventDispatcher * The IDs of the nodes that are currently visible on the canvas. More * performant than {@link visible_nodes} for visibility checks. */ - #visible_node_ids: Set = new Set() + private _visible_node_ids: Set = new Set() node_over?: LGraphNode node_capturing_input?: LGraphNode | null highlighted_links: Dictionary = {} - #visibleReroutes: Set = new Set() + private _visibleReroutes: Set = new Set() dirty_canvas: boolean = true dirty_bgcanvas: boolean = true @@ -725,9 +725,9 @@ export class LGraphCanvas implements CustomEventDispatcher NODEPANEL_IS_OPEN?: boolean /** Once per frame check of snap to grid value. @todo Update on change. */ - #snapToGrid?: number + private _snapToGrid?: number /** Set on keydown, keyup. @todo */ - #shiftDown: boolean = false + private _shiftDown: boolean = false /** Link rendering adapter for litegraph-to-canvas integration */ linkRenderer: LitegraphLinkAdapter | null = null @@ -735,7 +735,11 @@ export class LGraphCanvas implements CustomEventDispatcher /** If true, enable drag zoom. Ctrl+Shift+Drag Up/Down: zoom canvas. */ dragZoomEnabled: boolean = false /** The start position of the drag zoom and original read-only state. */ - #dragZoomStart: { pos: Point; scale: number; readOnly: boolean } | null = null + private _dragZoomStart: { + pos: Point + scale: number + readOnly: boolean + } | null = null /** If true, enable live selection during drag. Nodes are selected/deselected in real-time. */ liveSelection: boolean = false @@ -810,7 +814,7 @@ export class LGraphCanvas implements CustomEventDispatcher } this.linkConnector.events.addEventListener('link-created', () => - this.#dirty() + this._dirty() ) // @deprecated Workaround: Keep until connecting_links is removed. @@ -1808,7 +1812,7 @@ export class LGraphCanvas implements CustomEventDispatcher this.dragging_canvas = false - this.#dirty() + this._dirty() this.dirty_area = null this.node_in_panel = null @@ -1836,7 +1840,7 @@ export class LGraphCanvas implements CustomEventDispatcher this.linkRenderer = new LitegraphLinkAdapter(false) this.dispatch('litegraph:set-graph', { newGraph, oldGraph: graph }) - this.#dirty() + this._dirty() } openSubgraph(subgraph: Subgraph, fromNode: SubgraphNode): void { @@ -1873,7 +1877,7 @@ export class LGraphCanvas implements CustomEventDispatcher * @returns The canvas element * @throws If {@link canvas} is an element ID that does not belong to a valid HTML canvas element */ - #validateCanvas( + private _validateCanvas( canvas: string | HTMLCanvasElement ): HTMLCanvasElement & { data?: LGraphCanvas } { if (typeof canvas === 'string') { @@ -1892,7 +1896,7 @@ export class LGraphCanvas implements CustomEventDispatcher * @param skip_events If true, events on the previous canvas will not be removed. Has no effect on the first invocation. */ setCanvas(canvas: string | HTMLCanvasElement, skip_events?: boolean) { - const element = this.#validateCanvas(canvas) + const element = this._validateCanvas(canvas) if (element === this.canvas) return // maybe detach events from old_canvas if (!element && this.canvas && !skip_events) this.unbindEvents() @@ -1905,7 +1909,12 @@ export class LGraphCanvas implements CustomEventDispatcher // TODO: classList.add element.className += ' lgraphcanvas' - element.data = this + Object.defineProperty(element, 'data', { + value: this, + writable: true, + configurable: true, + enumerable: false + }) // Background canvas: To render objects behind nodes (background, links, groups) this.bgcanvas = document.createElement('canvas') @@ -2026,12 +2035,12 @@ export class LGraphCanvas implements CustomEventDispatcher } /** Marks the entire canvas as dirty. */ - #dirty(): void { + private _dirty(): void { this.dirty_canvas = true this.dirty_bgcanvas = true } - #linkConnectorDrop(): void { + private _linkConnectorDrop(): void { const { graph, linkConnector, pointer } = this if (!graph) throw new NullGraphError() @@ -2070,10 +2079,10 @@ export class LGraphCanvas implements CustomEventDispatcher const window = this.getCanvasWindow() if (this.is_rendering) { - if (this.#maximumFrameGap > 0) { + if (this._maximumFrameGap > 0) { // Manual FPS limit const gap = - this.#maximumFrameGap - (LiteGraph.getTime() - this.last_draw_time) + this._maximumFrameGap - (LiteGraph.getTime() - this.last_draw_time) setTimeout(renderFrame.bind(this), Math.max(1, gap)) } else { // FPS limited by refresh rate @@ -2161,7 +2170,7 @@ export class LGraphCanvas implements CustomEventDispatcher !e.altKey && e.buttons ) { - this.#dragZoomStart = { + this._dragZoomStart = { pos: [e.x, e.y], scale: this.ds.scale, readOnly: this.read_only @@ -2208,9 +2217,9 @@ export class LGraphCanvas implements CustomEventDispatcher // left button mouse / single finger if (e.button === 0 && !pointer.isDouble) { - this.#processPrimaryButton(e, node) + this._processPrimaryButton(e, node) } else if (e.button === 1) { - this.#processMiddleButton(e, node) + this._processMiddleButton(e, node) } else if ( (e.button === 2 || pointer.isDouble) && this.allow_interaction && @@ -2246,7 +2255,7 @@ export class LGraphCanvas implements CustomEventDispatcher reroute = graph.getRerouteOnPos( e.canvasX, e.canvasY, - this.#visibleReroutes + this._visibleReroutes ) } if (reroute) { @@ -2302,18 +2311,24 @@ export class LGraphCanvas implements CustomEventDispatcher * @param y The y coordinate in canvas space * @returns The positionable item or undefined */ - #getPositionableOnPos(x: number, y: number): Positionable | undefined { + private _getPositionableOnPos( + x: number, + y: number + ): Positionable | undefined { const ioNode = this.subgraph?.getIoNodeOnPos(x, y) if (ioNode) return ioNode - for (const reroute of this.#visibleReroutes) { + for (const reroute of this._visibleReroutes) { if (reroute.containsPoint([x, y])) return reroute } return this.graph?.getGroupTitlebarOnPos(x, y) } - #processPrimaryButton(e: CanvasPointerEvent, node: LGraphNode | undefined) { + private _processPrimaryButton( + e: CanvasPointerEvent, + node: LGraphNode | undefined + ) { const { pointer, graph, linkConnector, subgraph } = this if (!graph) throw new NullGraphError() @@ -2329,7 +2344,7 @@ export class LGraphCanvas implements CustomEventDispatcher !e.altKey && LiteGraph.leftMouseClickBehavior === 'panning' ) { - this.#setupNodeSelectionDrag(e, pointer, node) + this._setupNodeSelectionDrag(e, pointer, node) return } @@ -2360,16 +2375,16 @@ export class LGraphCanvas implements CustomEventDispatcher if (this.allow_dragnodes) { pointer.onDragStart = (pointer) => { - this.#startDraggingItems(cloned, pointer) + this._startDraggingItems(cloned, pointer) } - pointer.onDragEnd = (e) => this.#processDraggedItems(e) + pointer.onDragEnd = (e) => this._processDraggedItems(e) } return } // Node clicked if (node && (this.allow_interaction || node.flags.allow_interaction)) { - this.#processNodeClick(e, ctrlOrMeta, node) + this._processNodeClick(e, ctrlOrMeta, node) } else { // Subgraph IO nodes if (subgraph) { @@ -2387,8 +2402,8 @@ export class LGraphCanvas implements CustomEventDispatcher ioNode.onPointerDown(e, pointer, linkConnector) pointer.onClick ??= () => canvas.processSelect(ioNode, e) pointer.onDragStart ??= () => - canvas.#startDraggingItems(ioNode, pointer, true) - pointer.onDragEnd ??= (eUp) => canvas.#processDraggedItems(eUp) + canvas._startDraggingItems(ioNode, pointer, true) + pointer.onDragEnd ??= (eUp) => canvas._processDraggedItems(eUp) return true } } @@ -2404,7 +2419,7 @@ export class LGraphCanvas implements CustomEventDispatcher } // Fallback to checking visible reroutes directly - for (const reroute of this.#visibleReroutes) { + for (const reroute of this._visibleReroutes) { const overReroute = foundReroute === reroute || reroute.containsPoint([x, y]) if (!reroute.isSlotHovered && !overReroute) continue @@ -2413,19 +2428,19 @@ export class LGraphCanvas implements CustomEventDispatcher pointer.onClick = () => this.processSelect(reroute, e) if (!e.shiftKey) { pointer.onDragStart = (pointer) => - this.#startDraggingItems(reroute, pointer, true) - pointer.onDragEnd = (e) => this.#processDraggedItems(e) + this._startDraggingItems(reroute, pointer, true) + pointer.onDragEnd = (e) => this._processDraggedItems(e) } } if (reroute.isOutputHovered || (overReroute && e.shiftKey)) { linkConnector.dragFromReroute(graph, reroute) - this.#linkConnectorDrop() + this._linkConnectorDrop() } if (reroute.isInputHovered) { linkConnector.dragFromRerouteToOutput(graph, reroute) - this.#linkConnectorDrop() + this._linkConnectorDrop() } reroute.hideSlots() @@ -2470,14 +2485,14 @@ export class LGraphCanvas implements CustomEventDispatcher if (e.shiftKey && !e.altKey) { linkConnector.dragFromLinkSegment(graph, linkSegment) - this.#linkConnectorDrop() + this._linkConnectorDrop() return } else if (e.altKey && !e.shiftKey) { const newReroute = graph.createReroute([x, y], linkSegment) pointer.onDragStart = (pointer) => - this.#startDraggingItems(newReroute, pointer) - pointer.onDragEnd = (e) => this.#processDraggedItems(e) + this._startDraggingItems(newReroute, pointer) + pointer.onDragEnd = (e) => this._processDraggedItems(e) return } } else if ( @@ -2519,7 +2534,7 @@ export class LGraphCanvas implements CustomEventDispatcher eMove.canvasY - group.pos[1] - offsetY ] // Unless snapping. - if (this.#snapToGrid) snapPoint(pos, this.#snapToGrid) + if (this._snapToGrid) snapPoint(pos, this._snapToGrid) const resized = group.resize(pos[0], pos[1]) if (resized) this.dirty_bgcanvas = true @@ -2542,9 +2557,9 @@ export class LGraphCanvas implements CustomEventDispatcher pointer.onClick = () => this.processSelect(group, e) pointer.onDragStart = (pointer) => { group.recomputeInsideNodes() - this.#startDraggingItems(group, pointer, true) + this._startDraggingItems(group, pointer, true) } - pointer.onDragEnd = (e) => this.#processDraggedItems(e) + pointer.onDragEnd = (e) => this._processDraggedItems(e) } } @@ -2582,12 +2597,12 @@ export class LGraphCanvas implements CustomEventDispatcher pointer.finally = () => (this.dragging_canvas = false) this.dragging_canvas = true } else { - this.#setupNodeSelectionDrag(e, pointer) + this._setupNodeSelectionDrag(e, pointer) } } } - #setupNodeSelectionDrag( + private _setupNodeSelectionDrag( e: CanvasPointerEvent, pointer: CanvasPointer, node?: LGraphNode | undefined @@ -2602,7 +2617,7 @@ export class LGraphCanvas implements CustomEventDispatcher pointer.onClick = (eUp) => { // Click, not drag const clickedItem = - node ?? this.#getPositionableOnPos(eUp.canvasX, eUp.canvasY) + node ?? this._getPositionableOnPos(eUp.canvasX, eUp.canvasY) this.processSelect(clickedItem, eUp) } pointer.onDragStart = () => (this.dragging_rectangle = dragRect) @@ -2617,7 +2632,7 @@ export class LGraphCanvas implements CustomEventDispatcher } else { // Classic mode: select only when drag ends pointer.onDragEnd = (upEvent) => - this.#handleMultiSelect(upEvent, dragRect) + this._handleMultiSelect(upEvent, dragRect) } pointer.finally = () => (this.dragging_rectangle = null) @@ -2629,7 +2644,7 @@ export class LGraphCanvas implements CustomEventDispatcher * @param ctrlOrMeta Ctrl or meta key is pressed * @param node The node to process a click event for */ - #processNodeClick( + private _processNodeClick( e: CanvasPointerEvent, ctrlOrMeta: boolean, node: LGraphNode @@ -2685,13 +2700,13 @@ export class LGraphCanvas implements CustomEventDispatcher // Drag multiple output links if (e.shiftKey && hasRelevantOutputLinks(output, graph)) { linkConnector.moveOutputLink(graph, output) - this.#linkConnectorDrop() + this._linkConnectorDrop() return } // New output link linkConnector.dragNewFromOutput(graph, node, output) - this.#linkConnectorDrop() + this._linkConnectorDrop() if (LiteGraph.shift_click_do_break_link_from) { if (e.shiftKey) { @@ -2744,7 +2759,7 @@ export class LGraphCanvas implements CustomEventDispatcher linkConnector.dragNewFromInput(graph, node, input) } - this.#linkConnectorDrop() + this._linkConnectorDrop() this.dirty_bgcanvas = true return @@ -2874,7 +2889,7 @@ export class LGraphCanvas implements CustomEventDispatcher } // Apply snapping to position changes - if (this.#snapToGrid) { + if (this._snapToGrid) { if ( resizeDirection.includes('N') || resizeDirection.includes('W') @@ -2882,7 +2897,7 @@ export class LGraphCanvas implements CustomEventDispatcher const originalX = newBounds.x const originalY = newBounds.y - snapPoint(newBounds.pos, this.#snapToGrid) + snapPoint(newBounds.pos, this._snapToGrid) // Adjust size to compensate for snapped position if (resizeDirection.includes('N')) { @@ -2893,7 +2908,7 @@ export class LGraphCanvas implements CustomEventDispatcher } } - snapPoint(newBounds.size, this.#snapToGrid) + snapPoint(newBounds.size, this._snapToGrid) } // Apply snapping to size changes @@ -2918,11 +2933,11 @@ export class LGraphCanvas implements CustomEventDispatcher node.pos = newBounds.pos node.setSize(newBounds.size) - this.#dirty() + this._dirty() } pointer.onDragEnd = () => { - this.#dirty() + this._dirty() graph.afterChange(node) } pointer.finally = () => { @@ -2938,8 +2953,8 @@ export class LGraphCanvas implements CustomEventDispatcher // Drag node pointer.onDragStart = (pointer) => - this.#startDraggingItems(node, pointer, true) - pointer.onDragEnd = (e) => this.#processDraggedItems(e) + this._startDraggingItems(node, pointer, true) + pointer.onDragEnd = (e) => this._processDraggedItems(e) } this.dirty_canvas = true @@ -3008,7 +3023,10 @@ export class LGraphCanvas implements CustomEventDispatcher * @param e The pointerdown event * @param node The node to process a click event for */ - #processMiddleButton(e: CanvasPointerEvent, node: LGraphNode | undefined) { + private _processMiddleButton( + e: CanvasPointerEvent, + node: LGraphNode | undefined + ) { const { pointer } = this if ( @@ -3105,14 +3123,14 @@ export class LGraphCanvas implements CustomEventDispatcher } } - #processDragZoom(e: PointerEvent): void { + private _processDragZoom(e: PointerEvent): void { // stop canvas zoom action if (!e.buttons) { - this.#finishDragZoom() + this._finishDragZoom() return } - const start = this.#dragZoomStart + const start = this._dragZoomStart if (!start) throw new TypeError('Drag-zoom state object was null') if (!this.graph) throw new NullGraphError() @@ -3126,10 +3144,10 @@ export class LGraphCanvas implements CustomEventDispatcher this.graph.change() } - #finishDragZoom(): void { - const start = this.#dragZoomStart + private _finishDragZoom(): void { + const start = this._dragZoomStart if (!start) return - this.#dragZoomStart = null + this._dragZoomStart = null this.read_only = start.readOnly } @@ -3141,9 +3159,9 @@ export class LGraphCanvas implements CustomEventDispatcher this.dragZoomEnabled && e.ctrlKey && e.shiftKey && - this.#dragZoomStart + this._dragZoomStart ) { - this.#processDragZoom(e) + this._processDragZoom(e) return } @@ -3210,7 +3228,7 @@ export class LGraphCanvas implements CustomEventDispatcher } else if (this.dragging_canvas) { this.ds.offset[0] += delta[0] / this.ds.scale this.ds.offset[1] += delta[1] / this.ds.scale - this.#dirty() + this._dirty() } else if ( (this.allow_interaction || node?.flags.allow_interaction) && !this.read_only @@ -3258,7 +3276,7 @@ export class LGraphCanvas implements CustomEventDispatcher this.node_over = node this.dirty_canvas = true - for (const reroute of this.#visibleReroutes) { + for (const reroute of this._visibleReroutes) { reroute.hideSlots() this.dirty_bgcanvas = true } @@ -3382,10 +3400,10 @@ export class LGraphCanvas implements CustomEventDispatcher } } else { // Reroutes - underPointer = this.#updateReroutes(underPointer) + underPointer = this._updateReroutes(underPointer) // Not over a node - const segment = this.#getLinkCentreOnPos(e) + const segment = this._getLinkCentreOnPos(e) if (this.over_link_center !== segment) { underPointer |= CanvasItem.Link this.over_link_center = segment @@ -3435,7 +3453,7 @@ export class LGraphCanvas implements CustomEventDispatcher } } - this.#dirty() + this._dirty() } } @@ -3449,14 +3467,14 @@ export class LGraphCanvas implements CustomEventDispatcher * Updates the hover / snap state of all visible reroutes. * @returns The original value of {@link underPointer}, with any found reroute items added. */ - #updateReroutes(underPointer: CanvasItem): CanvasItem { + private _updateReroutes(underPointer: CanvasItem): CanvasItem { const { graph, pointer, linkConnector } = this if (!graph) throw new NullGraphError() // Update reroute hover state if (!pointer.isDown) { let anyChanges = false - for (const reroute of this.#visibleReroutes) { + for (const reroute of this._visibleReroutes) { anyChanges ||= reroute.updateVisibility(this.graph_mouse) if (reroute.isSlotHovered) underPointer |= CanvasItem.RerouteSlot @@ -3464,7 +3482,7 @@ export class LGraphCanvas implements CustomEventDispatcher if (anyChanges) this.dirty_bgcanvas = true } else if (linkConnector.isConnecting) { // Highlight the reroute that the mouse is over - for (const reroute of this.#visibleReroutes) { + for (const reroute of this._visibleReroutes) { if (reroute.containsPoint(this.graph_mouse)) { if (linkConnector.isRerouteValidDrop(reroute)) { linkConnector.overReroute = reroute @@ -3489,7 +3507,7 @@ export class LGraphCanvas implements CustomEventDispatcher * @param pointer The pointer event that initiated the drag, e.g. pointerdown * @param sticky If `true`, the item is added to the selection - see {@link processSelect} */ - #startDraggingItems( + private _startDraggingItems( item: Positionable, pointer: CanvasPointer, sticky = false @@ -3511,7 +3529,7 @@ export class LGraphCanvas implements CustomEventDispatcher * Handles shared clean up and placement after items have been dragged. * @param e The event that completed the drag, e.g. pointerup, pointermove */ - #processDraggedItems(e: CanvasPointerEvent): void { + private _processDraggedItems(e: CanvasPointerEvent): void { const { graph } = this if (e.shiftKey || LiteGraph.alwaysSnapToGrid) graph?.snapToGrid(this.selectedItems) @@ -3533,7 +3551,7 @@ export class LGraphCanvas implements CustomEventDispatcher const { graph, pointer } = this if (!graph) return - this.#finishDragZoom() + this._finishDragZoom() LGraphCanvas.active_canvas = this @@ -3677,7 +3695,7 @@ export class LGraphCanvas implements CustomEventDispatcher return } - #noItemsSelected(): void { + private _noItemsSelected(): void { const event = new CustomEvent('litegraph:no-items-selected', { bubbles: true }) @@ -3688,7 +3706,7 @@ export class LGraphCanvas implements CustomEventDispatcher * process a key event */ processKey(e: KeyboardEvent): void { - this.#shiftDown = e.shiftKey + this._shiftDown = e.shiftKey const { graph } = this if (!graph) return @@ -3735,7 +3753,7 @@ export class LGraphCanvas implements CustomEventDispatcher // @ts-expect-error EventTarget.localName is not in standard types if (e.target.localName != 'input' && e.target.localName != 'textarea') { if (this.selectedItems.size === 0) { - this.#noItemsSelected() + this._noItemsSelected() return } @@ -4097,7 +4115,7 @@ export class LGraphCanvas implements CustomEventDispatcher * @param dragRect The drag rectangle to normalize (modified in place) * @returns The normalized rectangle */ - #normalizeDragRect(dragRect: Rect): Rect { + private _normalizeDragRect(dragRect: Rect): Rect { const w = Math.abs(dragRect[2]) const h = Math.abs(dragRect[3]) if (dragRect[2] < 0) dragRect[0] -= w @@ -4112,7 +4130,7 @@ export class LGraphCanvas implements CustomEventDispatcher * @param rect The rectangle to check against * @returns Set of positionable items that overlap with the rectangle */ - #getItemsInRect(rect: Rect): Set { + private _getItemsInRect(rect: Rect): Set { const { graph, subgraph } = this if (!graph) throw new NullGraphError() @@ -4166,9 +4184,9 @@ export class LGraphCanvas implements CustomEventDispatcher dragRect[2], dragRect[3] ] - this.#normalizeDragRect(normalizedRect) + this._normalizeDragRect(normalizedRect) - const itemsInRect = this.#getItemsInRect(normalizedRect) + const itemsInRect = this._getItemsInRect(normalizedRect) const desired = new Set() if (e.shiftKey && !e.altKey) { @@ -4215,16 +4233,16 @@ export class LGraphCanvas implements CustomEventDispatcher * @param e The pointer up event * @param dragRect The drag rectangle */ - #handleMultiSelect(e: CanvasPointerEvent, dragRect: Rect): void { + private _handleMultiSelect(e: CanvasPointerEvent, dragRect: Rect): void { const normalizedRect: Rect = [ dragRect[0], dragRect[1], dragRect[2], dragRect[3] ] - this.#normalizeDragRect(normalizedRect) + this._normalizeDragRect(normalizedRect) - const itemsInRect = this.#getItemsInRect(normalizedRect) + const itemsInRect = this._getItemsInRect(normalizedRect) const { selectedItems } = this if (e.shiftKey) { @@ -4588,7 +4606,7 @@ export class LGraphCanvas implements CustomEventDispatcher */ setZoom(value: number, zooming_center: Point) { this.ds.changeScale(value, zooming_center) - this.#dirty() + this._dirty() } /** @@ -4671,7 +4689,7 @@ export class LGraphCanvas implements CustomEventDispatcher * @returns `true` if the node is visible, otherwise `false` */ isNodeVisible(node: LGraphNode): boolean { - return this.#visible_node_ids.has(node.id) + return this._visible_node_ids.has(node.id) } /** @@ -4692,7 +4710,7 @@ export class LGraphCanvas implements CustomEventDispatcher if (this.dirty_canvas || force_canvas) { this.computeVisibleNodes(undefined, this.visible_nodes) // Update visible node IDs - this.#visible_node_ids = new Set( + this._visible_node_ids = new Set( this.visible_nodes.map((node) => node.id) ) @@ -4746,8 +4764,8 @@ export class LGraphCanvas implements CustomEventDispatcher } // TODO: Set snapping value when changed instead of once per frame - this.#snapToGrid = - this.#shiftDown || LiteGraph.alwaysSnapToGrid + this._snapToGrid = + this._shiftDown || LiteGraph.alwaysSnapToGrid ? this.graph?.getSnapToGridSize() : undefined @@ -4789,7 +4807,7 @@ export class LGraphCanvas implements CustomEventDispatcher // draw nodes const { visible_nodes } = this const drawSnapGuides = - this.#snapToGrid && + this._snapToGrid && (this.isDragging || layoutStore.isDraggingVueNodes.value) for (const node of visible_nodes) { @@ -4829,7 +4847,7 @@ export class LGraphCanvas implements CustomEventDispatcher if (linkConnector.isConnecting) { // current connection (the one being dragged by the mouse) const { renderLinks } = linkConnector - const highlightPos = this.#getHighlightPosition() + const highlightPos = this._getHighlightPosition() ctx.lineWidth = this.connections_width for (const renderLink of renderLinks) { @@ -4883,7 +4901,7 @@ export class LGraphCanvas implements CustomEventDispatcher } // Gradient half-border over target node - this.#renderSnapHighlight(ctx, highlightPos) + this._renderSnapHighlight(ctx, highlightPos) } // on top of link center @@ -4909,7 +4927,7 @@ export class LGraphCanvas implements CustomEventDispatcher } /** @returns If the pointer is over a link centre marker, the link segment it belongs to. Otherwise, `undefined`. */ - #getLinkCentreOnPos(e: CanvasPointerEvent): LinkSegment | undefined { + private _getLinkCentreOnPos(e: CanvasPointerEvent): LinkSegment | undefined { // Skip hit detection if center markers are disabled if (this.linkMarkerShape === LinkMarkerShape.None) { return undefined @@ -4928,7 +4946,7 @@ export class LGraphCanvas implements CustomEventDispatcher } /** Get the target snap / highlight point in graph space */ - #getHighlightPosition(): Readonly { + private _getHighlightPosition(): Readonly { return LiteGraph.snaps_for_comfy ? (this.linkConnector.state.snapLinksPos ?? this._highlight_pos ?? @@ -4941,7 +4959,7 @@ export class LGraphCanvas implements CustomEventDispatcher * Partial border over target node and a highlight over the slot itself. * @param ctx Canvas 2D context */ - #renderSnapHighlight( + private _renderSnapHighlight( ctx: CanvasRenderingContext2D, highlightPos: Readonly ): void { @@ -5592,7 +5610,7 @@ export class LGraphCanvas implements CustomEventDispatcher // Normalise boundingRect to pos to snap snapGuide[0] += offsetX snapGuide[1] += offsetY - if (this.#snapToGrid) snapPoint(snapGuide, this.#snapToGrid) + if (this._snapToGrid) snapPoint(snapGuide, this._snapToGrid) snapGuide[0] -= offsetX snapGuide[1] -= offsetY @@ -5672,7 +5690,7 @@ export class LGraphCanvas implements CustomEventDispatcher const output = start_node.outputs[outputId] if (!output) continue - this.#renderAllLinkSegments( + this._renderAllLinkSegments( ctx, link, startPos, @@ -5701,7 +5719,7 @@ export class LGraphCanvas implements CustomEventDispatcher ? getSlotPosition(inputNode, link.target_slot, true) : inputNode.getInputPos(link.target_slot) - this.#renderAllLinkSegments( + this._renderAllLinkSegments( ctx, link, output.pos, @@ -5728,7 +5746,7 @@ export class LGraphCanvas implements CustomEventDispatcher ? getSlotPosition(outputNode, link.origin_slot, false) : outputNode.getOutputPos(link.origin_slot) - this.#renderAllLinkSegments( + this._renderAllLinkSegments( ctx, link, startPos, @@ -5742,10 +5760,10 @@ export class LGraphCanvas implements CustomEventDispatcher } if (graph.floatingLinks.size > 0) { - this.#renderFloatingLinks(ctx, graph, visibleReroutes, now) + this._renderFloatingLinks(ctx, graph, visibleReroutes, now) } - const rerouteSet = this.#visibleReroutes + const rerouteSet = this._visibleReroutes rerouteSet.clear() // Render reroutes, ordered by number of non-floating links @@ -5754,7 +5772,7 @@ export class LGraphCanvas implements CustomEventDispatcher rerouteSet.add(reroute) if ( - this.#snapToGrid && + this._snapToGrid && this.isDragging && this.selectedItems.has(reroute) ) { @@ -5776,7 +5794,7 @@ export class LGraphCanvas implements CustomEventDispatcher : this.editor_alpha } - #renderFloatingLinks( + private _renderFloatingLinks( ctx: CanvasRenderingContext2D, graph: LGraph, visibleReroutes: Reroute[], @@ -5805,7 +5823,7 @@ export class LGraphCanvas implements CustomEventDispatcher const endDirection = node.inputs[link.target_slot]?.dir firstReroute._dragging = true - this.#renderAllLinkSegments( + this._renderAllLinkSegments( ctx, link, startPos, @@ -5827,7 +5845,7 @@ export class LGraphCanvas implements CustomEventDispatcher const startDirection = node.outputs[link.origin_slot]?.dir link._dragging = true - this.#renderAllLinkSegments( + this._renderAllLinkSegments( ctx, link, startPos, @@ -5843,7 +5861,7 @@ export class LGraphCanvas implements CustomEventDispatcher ctx.globalAlpha = globalAlpha } - #renderAllLinkSegments( + private _renderAllLinkSegments( ctx: CanvasRenderingContext2D, link: LLink, startPos: Point, @@ -6146,7 +6164,7 @@ export class LGraphCanvas implements CustomEventDispatcher ctx.save() ctx.globalAlpha = 0.5 * this.editor_alpha const drawSnapGuides = - this.#snapToGrid && + this._snapToGrid && (this.isDragging || layoutStore.isDraggingVueNodes.value) for (const group of groups) { @@ -6513,7 +6531,7 @@ export class LGraphCanvas implements CustomEventDispatcher }, optPass || {} ) - const dirty = () => this.#dirty() + const dirty = () => this._dirty() const that = this const { graph } = this @@ -7522,7 +7540,7 @@ export class LGraphCanvas implements CustomEventDispatcher function inner() { setValue(input?.value) } - const dirty = () => this.#dirty() + const dirty = () => this._dirty() function setValue(value: string | number | undefined) { if ( @@ -8356,7 +8374,7 @@ export class LGraphCanvas implements CustomEventDispatcher reroute = this.graph.getRerouteOnPos( event.canvasX, event.canvasY, - this.#visibleReroutes + this._visibleReroutes ) } if (reroute) { @@ -8646,4 +8664,17 @@ export class LGraphCanvas implements CustomEventDispatcher const mutations = this.initLayoutMutations() this.applyNodePositionUpdates(nodesToReposition, mutations) } + + /** + * Custom JSON serialization to prevent circular reference errors. + * LGraphCanvas should not be serialized directly - serialize the graph instead. + */ + toJSON(): { ds: { scale: number; offset: [number, number] } } { + return { + ds: { + scale: this.ds.scale, + offset: [...this.ds.offset] as [number, number] + } + } + } } diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index 1bdbbf867..ec29111b0 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -273,8 +273,8 @@ export class LGraphNode inputs: INodeInputSlot[] = [] outputs: INodeOutputSlot[] = [] - #concreteInputs: NodeInputSlot[] = [] - #concreteOutputs: NodeOutputSlot[] = [] + private _concreteInputs: NodeInputSlot[] = [] + private _concreteOutputs: NodeOutputSlot[] = [] properties: Dictionary = {} properties_info: INodePropertyInfo[] = [] @@ -438,24 +438,24 @@ export class LGraphNode } /** @inheritdoc {@link renderArea} */ - #renderArea = new Rectangle() + private _renderArea = new Rectangle() /** * Rect describing the node area, including shadows and any protrusions. * Determines if the node is visible. Calculated once at the start of every frame. */ get renderArea(): ReadOnlyRect { - return this.#renderArea + return this._renderArea } /** @inheritdoc {@link boundingRect} */ - #boundingRect: Rectangle = new Rectangle() + private _boundingRect: Rectangle = new Rectangle() /** * Cached node position & area as `x, y, width, height`. Includes changes made by {@link onBounding}, if present. * * Determines the node hitbox and other rendering effects. Calculated once at the start of every frame. */ get boundingRect(): ReadOnlyRectangle { - return this.#boundingRect + return this._boundingRect } /** The offset from {@link pos} to the top-left of {@link boundingRect}. */ @@ -753,7 +753,9 @@ export class LGraphNode onPropertyChange?(this: LGraphNode): void updateOutputData?(this: LGraphNode, origin_slot: number): void - #getErrorStrokeStyle(this: LGraphNode): IDrawBoundingOptions | undefined { + private _getErrorStrokeStyle( + this: LGraphNode + ): IDrawBoundingOptions | undefined { if (this.has_errors) { return { padding: 12, @@ -763,7 +765,9 @@ export class LGraphNode } } - #getSelectedStrokeStyle(this: LGraphNode): IDrawBoundingOptions | undefined { + private _getSelectedStrokeStyle( + this: LGraphNode + ): IDrawBoundingOptions | undefined { if (this.selected) { return { padding: this.has_errors ? 20 : undefined @@ -778,8 +782,8 @@ export class LGraphNode this.size = [LiteGraph.NODE_WIDTH, 60] this.pos = [10, 10] this.strokeStyles = { - error: this.#getErrorStrokeStyle, - selected: this.#getSelectedStrokeStyle + error: this._getErrorStrokeStyle, + selected: this._getSelectedStrokeStyle } // Initialize property manager with tracked properties this.changeTracker = new LGraphNodeProperties(this) @@ -2067,11 +2071,11 @@ export class LGraphNode * Called automatically at the start of every frame. */ updateArea(ctx?: CanvasRenderingContext2D): void { - const bounds = this.#boundingRect + const bounds = this._boundingRect this.measure(bounds, ctx) this.onBounding?.(bounds) - const renderArea = this.#renderArea + const renderArea = this._renderArea renderArea.set(bounds) // 4 offset for collapsed node connection points renderArea[0] -= 4 @@ -2293,7 +2297,7 @@ export class LGraphNode optsIn?: FindFreeSlotOptions & { returnObj?: TReturn } ): INodeInputSlot | -1 findInputSlotFree(optsIn?: FindFreeSlotOptions) { - return this.#findFreeSlot(this.inputs, optsIn) + return this._findFreeSlot(this.inputs, optsIn) } /** @@ -2308,14 +2312,14 @@ export class LGraphNode optsIn?: FindFreeSlotOptions & { returnObj?: TReturn } ): INodeOutputSlot | -1 findOutputSlotFree(optsIn?: FindFreeSlotOptions) { - return this.#findFreeSlot(this.outputs, optsIn) + return this._findFreeSlot(this.outputs, optsIn) } /** * Finds the next free slot * @param slots The slots to search, i.e. this.inputs or this.outputs */ - #findFreeSlot( + private _findFreeSlot( slots: TSlot[], options?: FindFreeSlotOptions ): TSlot | number { @@ -2357,7 +2361,7 @@ export class LGraphNode preferFreeSlot?: boolean, doNotUseOccupied?: boolean ) { - return this.#findSlotByType( + return this._findSlotByType( this.inputs, type, returnObj, @@ -2387,7 +2391,7 @@ export class LGraphNode preferFreeSlot?: boolean, doNotUseOccupied?: boolean ) { - return this.#findSlotByType( + return this._findSlotByType( this.outputs, type, returnObj, @@ -2433,14 +2437,14 @@ export class LGraphNode doNotUseOccupied?: boolean ): number | INodeOutputSlot | INodeInputSlot { return input - ? this.#findSlotByType( + ? this._findSlotByType( this.inputs, type, returnObj, preferFreeSlot, doNotUseOccupied ) - : this.#findSlotByType( + : this._findSlotByType( this.outputs, type, returnObj, @@ -2461,7 +2465,7 @@ export class LGraphNode * @see {findInputSlotByType} * @returns If a match is found, the slot if returnObj is true, otherwise the index. If no matches are found, -1 */ - #findSlotByType( + private _findSlotByType( slots: TSlot[], type: ISlotType, returnObj?: boolean, @@ -3310,8 +3314,8 @@ export class LGraphNode // default vertical slots const offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5 const slotIndex = is_input - ? this.#defaultVerticalInputs.indexOf(this.inputs[slot_number]) - : this.#defaultVerticalOutputs.indexOf(this.outputs[slot_number]) + ? this._defaultVerticalInputs.indexOf(this.inputs[slot_number]) + : this._defaultVerticalOutputs.indexOf(this.outputs[slot_number]) out[0] = is_input ? nodeX + offset : nodeX + this.size[0] + 1 - offset out[1] = @@ -3324,7 +3328,7 @@ export class LGraphNode /** * @internal The inputs that are not positioned with absolute coordinates. */ - get #defaultVerticalInputs() { + private get _defaultVerticalInputs() { return this.inputs.filter( (slot) => !slot.pos && !(this.widgets?.length && isWidgetInputSlot(slot)) ) @@ -3333,7 +3337,7 @@ export class LGraphNode /** * @internal The outputs that are not positioned with absolute coordinates. */ - get #defaultVerticalOutputs() { + private get _defaultVerticalOutputs() { return this.outputs.filter((slot: INodeOutputSlot) => !slot.pos) } @@ -3341,7 +3345,7 @@ export class LGraphNode * Get the context needed for slot position calculations * @internal */ - #getSlotPositionContext(): SlotPositionContext { + private _getSlotPositionContext(): SlotPositionContext { return { nodeX: this.pos[0], nodeY: this.pos[1], @@ -3373,7 +3377,7 @@ export class LGraphNode * @returns Position of the centre of the input slot in graph co-ordinates. */ getInputSlotPos(input: INodeInputSlot): Point { - return calculateInputSlotPosFromSlot(this.#getSlotPositionContext(), input) + return calculateInputSlotPosFromSlot(this._getSlotPositionContext(), input) } /** @@ -3916,13 +3920,13 @@ export class LGraphNode */ drawCollapsedSlots(ctx: CanvasRenderingContext2D): void { // Render the first connected slot only. - for (const slot of this.#concreteInputs) { + for (const slot of this._concreteInputs) { if (slot.link != null) { slot.drawCollapsed(ctx) break } } - for (const slot of this.#concreteOutputs) { + for (const slot of this._concreteOutputs) { if (slot.links?.length) { slot.drawCollapsed(ctx) break @@ -3934,7 +3938,7 @@ export class LGraphNode return [...this.inputs, ...this.outputs] } - #measureSlot( + private _measureSlot( slot: NodeInputSlot | NodeOutputSlot, slotIndex: number, isInput: boolean @@ -3951,27 +3955,27 @@ export class LGraphNode slot.boundingRect[3] = LiteGraph.NODE_SLOT_HEIGHT } - #measureSlots(): ReadOnlyRect | null { + private _measureSlots(): ReadOnlyRect | null { const slots: (NodeInputSlot | NodeOutputSlot)[] = [] - for (const [slotIndex, slot] of this.#concreteInputs.entries()) { + for (const [slotIndex, slot] of this._concreteInputs.entries()) { // Unrecognized nodes (Nodes with error) has inputs but no widgets. Treat // converted inputs as normal inputs. /** Widget input slots are handled in {@link layoutWidgetInputSlots} */ if (this.widgets?.length && isWidgetInputSlot(slot)) continue - this.#measureSlot(slot, slotIndex, true) + this._measureSlot(slot, slotIndex, true) slots.push(slot) } - for (const [slotIndex, slot] of this.#concreteOutputs.entries()) { - this.#measureSlot(slot, slotIndex, false) + for (const [slotIndex, slot] of this._concreteOutputs.entries()) { + this._measureSlot(slot, slotIndex, false) slots.push(slot) } return slots.length ? createBounds(slots, 0) : null } - #getMouseOverSlot(slot: INodeSlot): INodeSlot | null { + private _getMouseOverSlot(slot: INodeSlot): INodeSlot | null { const isInput = isINodeInputSlot(slot) const mouseOverId = this.mouseOver?.[isInput ? 'inputId' : 'outputId'] ?? -1 if (mouseOverId === -1) { @@ -3980,11 +3984,11 @@ export class LGraphNode return isInput ? this.inputs[mouseOverId] : this.outputs[mouseOverId] } - #isMouseOverSlot(slot: INodeSlot): boolean { - return this.#getMouseOverSlot(slot) === slot + private _isMouseOverSlot(slot: INodeSlot): boolean { + return this._getMouseOverSlot(slot) === slot } - #isMouseOverWidget(widget: IBaseWidget | undefined): boolean { + private _isMouseOverWidget(widget: IBaseWidget | undefined): boolean { if (!widget) return false return this.mouseOver?.overWidget === widget } @@ -4016,9 +4020,9 @@ export class LGraphNode ctx: CanvasRenderingContext2D, { fromSlot, colorContext, editorAlpha, lowQuality }: DrawSlotsOptions ) { - for (const slot of [...this.#concreteInputs, ...this.#concreteOutputs]) { + for (const slot of [...this._concreteInputs, ...this._concreteOutputs]) { const isValidTarget = fromSlot && slot.isValidTarget(fromSlot) - const isMouseOverSlot = this.#isMouseOverSlot(slot) + const isMouseOverSlot = this._isMouseOverSlot(slot) // change opacity of incompatible slots when dragging a connection const isValid = !fromSlot || isValidTarget @@ -4033,7 +4037,7 @@ export class LGraphNode isMouseOverSlot || isValidTarget || !slot.isWidgetInputSlot || - this.#isMouseOverWidget(this.getWidgetFromSlot(slot)) || + this._isMouseOverWidget(this.getWidgetFromSlot(slot)) || slot.isConnected || slot.alwaysVisible ) { @@ -4054,7 +4058,7 @@ export class LGraphNode * - {@link IBaseWidget.y} * @param widgetStartY The y-coordinate of the first widget */ - #arrangeWidgets(widgetStartY: number): void { + private _arrangeWidgets(widgetStartY: number): void { if (!this.widgets || !this.widgets.length) return const bodyHeight = this.bodyHeight @@ -4132,7 +4136,7 @@ export class LGraphNode /** * Arranges the layout of the node's widget input slots. */ - #arrangeWidgetInputSlots(): void { + private _arrangeWidgetInputSlots(): void { if (!this.widgets) return const slotByWidgetName = new Map< @@ -4154,10 +4158,10 @@ export class LGraphNode const slot = slotByWidgetName.get(widget.name) if (!slot) continue - const actualSlot = this.#concreteInputs[slot.index] + const actualSlot = this._concreteInputs[slot.index] const offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5 actualSlot.pos = [offset, widget.y + offset] - this.#measureSlot(actualSlot, slot.index, true) + this._measureSlot(actualSlot, slot.index, true) } } else { // For Vue positioning, just measure the slots without setting pos @@ -4165,7 +4169,7 @@ export class LGraphNode const slot = slotByWidgetName.get(widget.name) if (!slot) continue - this.#measureSlot(this.#concreteInputs[slot.index], slot.index, true) + this._measureSlot(this._concreteInputs[slot.index], slot.index, true) } } } @@ -4178,10 +4182,10 @@ export class LGraphNode * have been removed from the ecosystem. */ _setConcreteSlots(): void { - this.#concreteInputs = this.inputs.map((slot) => + this._concreteInputs = this.inputs.map((slot) => toClass(NodeInputSlot, slot, this) ) - this.#concreteOutputs = this.outputs.map((slot) => + this._concreteOutputs = this.outputs.map((slot) => toClass(NodeOutputSlot, slot, this) ) } @@ -4190,12 +4194,12 @@ export class LGraphNode * Arranges node elements in preparation for rendering (slots & widgets). */ arrange(): void { - const slotsBounds = this.#measureSlots() + const slotsBounds = this._measureSlots() const widgetStartY = slotsBounds ? slotsBounds[1] + slotsBounds[3] - this.pos[1] : 0 - this.#arrangeWidgets(widgetStartY) - this.#arrangeWidgetInputSlots() + this._arrangeWidgets(widgetStartY) + this._arrangeWidgetInputSlots() } /** diff --git a/src/lib/litegraph/src/LGraphNodeProperties.ts b/src/lib/litegraph/src/LGraphNodeProperties.ts index 9df938298..b3aa18d41 100644 --- a/src/lib/litegraph/src/LGraphNodeProperties.ts +++ b/src/lib/litegraph/src/LGraphNodeProperties.ts @@ -25,24 +25,24 @@ export class LGraphNodeProperties { node: LGraphNode /** Set of property paths that have been instrumented */ - #instrumentedPaths = new Set() + private _instrumentedPaths = new Set() constructor(node: LGraphNode) { this.node = node - this.#setupInstrumentation() + this._setupInstrumentation() } /** * Sets up property instrumentation for all tracked properties */ - #setupInstrumentation(): void { + private _setupInstrumentation(): void { for (const path of DEFAULT_TRACKED_PROPERTIES) { - this.#instrumentProperty(path) + this._instrumentProperty(path) } } - #resolveTargetObject(parts: string[]): { + private _resolveTargetObject(parts: string[]): { targetObject: Record propertyName: string } { @@ -73,14 +73,14 @@ export class LGraphNodeProperties { /** * Instruments a single property to track changes */ - #instrumentProperty(path: string): void { + private _instrumentProperty(path: string): void { const parts = path.split('.') if (parts.length > 1) { - this.#ensureNestedPath(path) + this._ensureNestedPath(path) } - const { targetObject, propertyName } = this.#resolveTargetObject(parts) + const { targetObject, propertyName } = this._resolveTargetObject(parts) const hasProperty = Object.prototype.hasOwnProperty.call( targetObject, @@ -96,7 +96,7 @@ export class LGraphNodeProperties { set: (newValue: unknown) => { const oldValue = value value = newValue - this.#emitPropertyChange(path, oldValue, newValue) + this._emitPropertyChange(path, oldValue, newValue) // Update enumerable: true for non-undefined values, false for undefined const shouldBeEnumerable = newValue !== undefined @@ -121,24 +121,24 @@ export class LGraphNodeProperties { Object.defineProperty( targetObject, propertyName, - this.#createInstrumentedDescriptor(path, currentValue) + this._createInstrumentedDescriptor(path, currentValue) ) } - this.#instrumentedPaths.add(path) + this._instrumentedPaths.add(path) } /** * Creates a property descriptor that emits change events */ - #createInstrumentedDescriptor( + private _createInstrumentedDescriptor( propertyPath: string, initialValue: unknown ): PropertyDescriptor { - return this.#createInstrumentedDescriptorTyped(propertyPath, initialValue) + return this._createInstrumentedDescriptorTyped(propertyPath, initialValue) } - #createInstrumentedDescriptorTyped( + private _createInstrumentedDescriptorTyped( propertyPath: string, initialValue: TValue ): PropertyDescriptor { @@ -149,7 +149,7 @@ export class LGraphNodeProperties { set: (newValue: TValue) => { const oldValue = value value = newValue - this.#emitPropertyChange(propertyPath, oldValue, newValue) + this._emitPropertyChange(propertyPath, oldValue, newValue) }, enumerable: true, configurable: true @@ -159,15 +159,15 @@ export class LGraphNodeProperties { /** * Emits a property change event if the node is connected to a graph */ - #emitPropertyChange( + private _emitPropertyChange( propertyPath: string, oldValue: unknown, newValue: unknown ): void { - this.#emitPropertyChangeTyped(propertyPath, oldValue, newValue) + this._emitPropertyChangeTyped(propertyPath, oldValue, newValue) } - #emitPropertyChangeTyped( + private _emitPropertyChangeTyped( propertyPath: string, oldValue: TValue, newValue: TValue @@ -183,7 +183,7 @@ export class LGraphNodeProperties { /** * Ensures parent objects exist for nested properties */ - #ensureNestedPath(path: string): void { + private _ensureNestedPath(path: string): void { const parts = path.split('.') // LGraphNode supports dynamic property access at runtime let current: Record = this.node as unknown as Record< @@ -208,7 +208,7 @@ export class LGraphNodeProperties { * Checks if a property is being tracked */ isTracked(path: string): boolean { - return this.#instrumentedPaths.has(path) + return this._instrumentedPaths.has(path) } /** diff --git a/src/lib/litegraph/src/__snapshots__/LGraph.test.ts.snap b/src/lib/litegraph/src/__snapshots__/LGraph.test.ts.snap index 0841fc96c..a159fd1b9 100644 --- a/src/lib/litegraph/src/__snapshots__/LGraph.test.ts.snap +++ b/src/lib/litegraph/src/__snapshots__/LGraph.test.ts.snap @@ -1,301 +1,43 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`LGraph > supports schema v0.4 graphs > oldSchemaGraph 1`] = ` -LGraph { - "_groups": [ - LGraphGroup { - "_bounding": Rectangle [ +{ + "config": {}, + "definitions": undefined, + "extra": { + "reroutes": undefined, + }, + "floatingLinks": undefined, + "groups": [ + { + "bounding": [ 20, 20, 1, 3, ], - "_children": Set {}, - "_nodes": [], - "_pos": Float64Array [ - 20, - 20, - ], - "_size": Float64Array [ - 1, - 3, - ], "color": "#6029aa", "flags": {}, - "font": undefined, "font_size": 14, - "graph": [Circular], "id": 123, - "selected": undefined, - "setDirtyCanvas": [Function], "title": "A group to test with", }, ], - "_input_nodes": undefined, - "_last_trigger_time": undefined, - "_links": Map {}, - "_nodes": [ - LGraphNode { - "_collapsed_width": undefined, - "_level": undefined, - "_pos": Float64Array [ - 10, - 10, - ], - "_posSize": Rectangle [ - 10, - 10, - 140, - 60, - ], - "_relative_id": undefined, - "_shape": undefined, - "_size": Float64Array [ - 140, - 60, - ], - "action_call": undefined, - "action_triggered": undefined, - "badgePosition": "top-left", - "badges": [], - "bgcolor": undefined, - "block_delete": undefined, - "boxcolor": undefined, - "changeTracker": undefined, - "clip_area": undefined, - "clonable": undefined, - "color": undefined, - "console": undefined, - "exec_version": undefined, - "execute_triggered": undefined, - "flags": {}, - "freeWidgetSpace": undefined, - "gotFocusAt": undefined, - "graph": [Circular], - "has_errors": true, - "id": 1, - "ignore_remove": undefined, - "inputs": [], - "last_serialization": { - "id": 1, - }, - "locked": undefined, - "lostFocusAt": undefined, - "mode": 0, - "mouseOver": undefined, - "order": 0, - "outputs": [], - "progress": undefined, - "properties": {}, - "properties_info": [], - "redraw_on_mouse": undefined, - "removable": undefined, - "resizable": undefined, - "selected": undefined, - "serialize_widgets": undefined, - "showAdvanced": undefined, - "strokeStyles": { - "error": [Function], - "selected": [Function], - }, - "title": undefined, - "title_buttons": [], - "type": "", - "widgets": undefined, - "widgets_start_y": undefined, - "widgets_up": undefined, - }, - ], - "_nodes_by_id": { - "1": LGraphNode { - "_collapsed_width": undefined, - "_level": undefined, - "_pos": Float64Array [ - 10, - 10, - ], - "_posSize": Rectangle [ - 10, - 10, - 140, - 60, - ], - "_relative_id": undefined, - "_shape": undefined, - "_size": Float64Array [ - 140, - 60, - ], - "action_call": undefined, - "action_triggered": undefined, - "badgePosition": "top-left", - "badges": [], - "bgcolor": undefined, - "block_delete": undefined, - "boxcolor": undefined, - "changeTracker": undefined, - "clip_area": undefined, - "clonable": undefined, - "color": undefined, - "console": undefined, - "exec_version": undefined, - "execute_triggered": undefined, - "flags": {}, - "freeWidgetSpace": undefined, - "gotFocusAt": undefined, - "graph": [Circular], - "has_errors": true, - "id": 1, - "ignore_remove": undefined, - "inputs": [], - "last_serialization": { - "id": 1, - }, - "locked": undefined, - "lostFocusAt": undefined, - "mode": 0, - "mouseOver": undefined, - "order": 0, - "outputs": [], - "progress": undefined, - "properties": {}, - "properties_info": [], - "redraw_on_mouse": undefined, - "removable": undefined, - "resizable": undefined, - "selected": undefined, - "serialize_widgets": undefined, - "showAdvanced": undefined, - "strokeStyles": { - "error": [Function], - "selected": [Function], - }, - "title": undefined, - "title_buttons": [], - "type": "", - "widgets": undefined, - "widgets_start_y": undefined, - "widgets_up": undefined, - }, - }, - "_nodes_executable": [], - "_nodes_in_order": [ - LGraphNode { - "_collapsed_width": undefined, - "_level": undefined, - "_pos": Float64Array [ - 10, - 10, - ], - "_posSize": Rectangle [ - 10, - 10, - 140, - 60, - ], - "_relative_id": undefined, - "_shape": undefined, - "_size": Float64Array [ - 140, - 60, - ], - "action_call": undefined, - "action_triggered": undefined, - "badgePosition": "top-left", - "badges": [], - "bgcolor": undefined, - "block_delete": undefined, - "boxcolor": undefined, - "changeTracker": undefined, - "clip_area": undefined, - "clonable": undefined, - "color": undefined, - "console": undefined, - "exec_version": undefined, - "execute_triggered": undefined, - "flags": {}, - "freeWidgetSpace": undefined, - "gotFocusAt": undefined, - "graph": [Circular], - "has_errors": true, - "id": 1, - "ignore_remove": undefined, - "inputs": [], - "last_serialization": { - "id": 1, - }, - "locked": undefined, - "lostFocusAt": undefined, - "mode": 0, - "mouseOver": undefined, - "order": 0, - "outputs": [], - "progress": undefined, - "properties": {}, - "properties_info": [], - "redraw_on_mouse": undefined, - "removable": undefined, - "resizable": undefined, - "selected": undefined, - "serialize_widgets": undefined, - "showAdvanced": undefined, - "strokeStyles": { - "error": [Function], - "selected": [Function], - }, - "title": undefined, - "title_buttons": [], - "type": "", - "widgets": undefined, - "widgets_start_y": undefined, - "widgets_up": undefined, - }, - ], - "_subgraphs": Map {}, - "_version": 3, - "catch_errors": true, - "config": {}, - "elapsed_time": 0.01, - "errors_in_execution": undefined, - "events": CustomEventTarget { - Symbol(listeners): { - "bubbling": Map {}, - "capturing": Map {}, - }, - Symbol(listenerOptions): { - "bubbling": Map {}, - "capturing": Map {}, - }, - }, - "execution_time": undefined, - "execution_timer_id": undefined, - "extra": {}, - "filter": undefined, - "fixedtime": 0, - "fixedtime_lapse": 0.01, - "floatingLinksInternal": Map {}, - "globaltime": 0, "id": "b4e984f1-b421-4d24-b8b4-ff895793af13", - "iteration": 0, - "last_update_time": 0, - "links": Map {}, - "list_of_graphcanvas": null, - "nodes_actioning": [], - "nodes_executedAction": [], - "nodes_executing": [], - "onTrigger": undefined, - "reroutesInternal": Map {}, + "last_link_id": 0, + "last_node_id": 1, + "links": [], + "nodes": [ + { + "id": 1, + "mode": 0, + "pos": [ + 10, + 10, + ], + }, + ], "revision": 0, - "runningtime": 0, - "starttime": 0, - "state": { - "lastGroupId": 123, - "lastLinkId": 0, - "lastNodeId": 1, - "lastRerouteId": 0, - }, - "status": 1, - "vars": {}, "version": 0.4, } `; diff --git a/src/lib/litegraph/src/canvas/InputIndicators.ts b/src/lib/litegraph/src/canvas/InputIndicators.ts index ad98cb533..63579057b 100644 --- a/src/lib/litegraph/src/canvas/InputIndicators.ts +++ b/src/lib/litegraph/src/canvas/InputIndicators.ts @@ -51,11 +51,11 @@ export class InputIndicators implements Disposable { const element = canvas.canvas const options = { capture: true, signal } satisfies AddEventListenerOptions - element.addEventListener('pointerdown', this.#onPointerDownOrMove, options) - element.addEventListener('pointermove', this.#onPointerDownOrMove, options) - element.addEventListener('pointerup', this.#onPointerUp, options) - element.addEventListener('keydown', this.#onKeyDownOrUp, options) - document.addEventListener('keyup', this.#onKeyDownOrUp, options) + element.addEventListener('pointerdown', this._onPointerDownOrMove, options) + element.addEventListener('pointermove', this._onPointerDownOrMove, options) + element.addEventListener('pointerup', this._onPointerUp, options) + element.addEventListener('keydown', this._onKeyDownOrUp, options) + document.addEventListener('keyup', this._onKeyDownOrUp, options) const origDrawFrontCanvas = canvas.drawFrontCanvas.bind(canvas) signal.addEventListener('abort', () => { @@ -68,7 +68,7 @@ export class InputIndicators implements Disposable { } } - #onPointerDownOrMove = this.onPointerDownOrMove.bind(this) + private _onPointerDownOrMove = this.onPointerDownOrMove.bind(this) onPointerDownOrMove(e: MouseEvent): void { this.mouse0Down = (e.buttons & 1) === 1 this.mouse1Down = (e.buttons & 4) === 4 @@ -80,14 +80,14 @@ export class InputIndicators implements Disposable { this.canvas.setDirty(true) } - #onPointerUp = this.onPointerUp.bind(this) + private _onPointerUp = this.onPointerUp.bind(this) onPointerUp(): void { this.mouse0Down = false this.mouse1Down = false this.mouse2Down = false } - #onKeyDownOrUp = this.onKeyDownOrUp.bind(this) + private _onKeyDownOrUp = this.onKeyDownOrUp.bind(this) onKeyDownOrUp(e: KeyboardEvent): void { this.ctrlDown = e.ctrlKey this.altDown = e.altKey diff --git a/src/lib/litegraph/src/canvas/LinkConnector.ts b/src/lib/litegraph/src/canvas/LinkConnector.ts index 1e4068d1b..2610011aa 100644 --- a/src/lib/litegraph/src/canvas/LinkConnector.ts +++ b/src/lib/litegraph/src/canvas/LinkConnector.ts @@ -115,10 +115,10 @@ export class LinkConnector { /** The reroute beneath the pointer, if it is a valid connection target. */ overReroute?: Reroute - readonly #setConnectingLinks: (value: ConnectingLink[]) => void + private readonly _setConnectingLinks: (value: ConnectingLink[]) => void constructor(setConnectingLinks: (value: ConnectingLink[]) => void) { - this.#setConnectingLinks = setConnectingLinks + this._setConnectingLinks = setConnectingLinks } get isConnecting() { @@ -253,7 +253,7 @@ export class LinkConnector { state.connectingTo = 'input' state.draggingExistingLinks = true - this.#setLegacyLinks(false) + this._setLegacyLinks(false) } /** Drag all links from an output to a new output. */ @@ -364,7 +364,7 @@ export class LinkConnector { state.multi = true state.connectingTo = 'output' - this.#setLegacyLinks(true) + this._setLegacyLinks(true) } /** @@ -387,7 +387,7 @@ export class LinkConnector { state.connectingTo = 'input' - this.#setLegacyLinks(false) + this._setLegacyLinks(false) } /** @@ -410,7 +410,7 @@ export class LinkConnector { state.connectingTo = 'output' - this.#setLegacyLinks(true) + this._setLegacyLinks(true) } dragNewFromSubgraphInput( @@ -431,7 +431,7 @@ export class LinkConnector { this.state.connectingTo = 'input' - this.#setLegacyLinks(false) + this._setLegacyLinks(false) } dragNewFromSubgraphOutput( @@ -452,7 +452,7 @@ export class LinkConnector { this.state.connectingTo = 'output' - this.#setLegacyLinks(true) + this._setLegacyLinks(true) } /** @@ -489,7 +489,7 @@ export class LinkConnector { this.state.connectingTo = 'input' - this.#setLegacyLinks(false) + this._setLegacyLinks(false) return } @@ -516,7 +516,7 @@ export class LinkConnector { this.state.connectingTo = 'input' - this.#setLegacyLinks(false) + this._setLegacyLinks(false) } /** @@ -553,7 +553,7 @@ export class LinkConnector { this.state.connectingTo = 'output' - this.#setLegacyLinks(false) + this._setLegacyLinks(false) return } @@ -581,7 +581,7 @@ export class LinkConnector { this.state.connectingTo = 'output' - this.#setLegacyLinks(true) + this._setLegacyLinks(true) } dragFromLinkSegment(network: LinkNetwork, linkSegment: LinkSegment): void { @@ -603,7 +603,7 @@ export class LinkConnector { state.connectingTo = 'input' - this.#setLegacyLinks(false) + this._setLegacyLinks(false) } /** @@ -754,7 +754,7 @@ export class LinkConnector { const output = node.getOutputOnPos([canvasX, canvasY]) if (output) { - this.#dropOnOutput(node, output) + this._dropOnOutput(node, output) } else { this.connectToNode(node, event) } @@ -765,7 +765,7 @@ export class LinkConnector { // Input slot if (inputOrSocket) { - this.#dropOnInput(node, inputOrSocket) + this._dropOnInput(node, inputOrSocket) } else { // Node background / title this.connectToNode(node, event) @@ -911,7 +911,7 @@ export class LinkConnector { return } - this.#dropOnOutput(node, output) + this._dropOnOutput(node, output) } else if (connectingTo === 'input') { // Dropping new input link const input = node.findInputByType(firstLink.fromSlot.type)?.slot @@ -922,11 +922,11 @@ export class LinkConnector { return } - this.#dropOnInput(node, input) + this._dropOnInput(node, input) } } - #dropOnInput(node: LGraphNode, input: INodeInputSlot): void { + private _dropOnInput(node: LGraphNode, input: INodeInputSlot): void { for (const link of this.renderLinks) { if (!link.canConnectToInput(node, input)) continue @@ -934,7 +934,7 @@ export class LinkConnector { } } - #dropOnOutput(node: LGraphNode, output: INodeOutputSlot): void { + private _dropOnOutput(node: LGraphNode, output: INodeOutputSlot): void { for (const link of this.renderLinks) { if (!link.canConnectToOutput(node, output)) { if ( @@ -1014,7 +1014,7 @@ export class LinkConnector { } /** Sets connecting_links, used by some extensions still. */ - #setLegacyLinks(fromSlotIsInput: boolean): void { + private _setLegacyLinks(fromSlotIsInput: boolean): void { const links = this.renderLinks.map((link) => { const input = fromSlotIsInput ? (link.fromSlot as INodeInputSlot) : null const output = fromSlotIsInput ? null : (link.fromSlot as INodeOutputSlot) @@ -1033,7 +1033,7 @@ export class LinkConnector { afterRerouteId } satisfies ConnectingLink }) - this.#setConnectingLinks(links) + this._setConnectingLinks(links) } /** diff --git a/src/lib/litegraph/src/infrastructure/ConstrainedSize.ts b/src/lib/litegraph/src/infrastructure/ConstrainedSize.ts index f0890fab9..2f3a22309 100644 --- a/src/lib/litegraph/src/infrastructure/ConstrainedSize.ts +++ b/src/lib/litegraph/src/infrastructure/ConstrainedSize.ts @@ -10,10 +10,10 @@ import type { ReadOnlyRect, Size } from '@/lib/litegraph/src/interfaces' * - Width and height are then updated, clamped to min/max values */ export class ConstrainedSize { - #width: number = 0 - #height: number = 0 - #desiredWidth: number = 0 - #desiredHeight: number = 0 + private _width: number = 0 + private _height: number = 0 + private _desiredWidth: number = 0 + private _desiredHeight: number = 0 minWidth: number = 0 minHeight: number = 0 @@ -21,29 +21,29 @@ export class ConstrainedSize { maxHeight: number = Infinity get width() { - return this.#width + return this._width } get height() { - return this.#height + return this._height } get desiredWidth() { - return this.#desiredWidth + return this._desiredWidth } set desiredWidth(value: number) { - this.#desiredWidth = value - this.#width = clamp(value, this.minWidth, this.maxWidth) + this._desiredWidth = value + this._width = clamp(value, this.minWidth, this.maxWidth) } get desiredHeight() { - return this.#desiredHeight + return this._desiredHeight } set desiredHeight(value: number) { - this.#desiredHeight = value - this.#height = clamp(value, this.minHeight, this.maxHeight) + this._desiredHeight = value + this._height = clamp(value, this.minHeight, this.maxHeight) } constructor(width: number, height: number) { @@ -70,6 +70,6 @@ export class ConstrainedSize { } toSize(): Size { - return [this.#width, this.#height] + return [this._width, this._height] } } diff --git a/src/lib/litegraph/src/infrastructure/Rectangle.ts b/src/lib/litegraph/src/infrastructure/Rectangle.ts index 3c70369f1..6751b84ae 100644 --- a/src/lib/litegraph/src/infrastructure/Rectangle.ts +++ b/src/lib/litegraph/src/infrastructure/Rectangle.ts @@ -19,8 +19,8 @@ import { isInRectangle } from '@/lib/litegraph/src/measure' * - {@link size}: The size of the rectangle. */ export class Rectangle extends Float64Array { - #pos: Float64Array | undefined - #size: Float64Array | undefined + private _pos: Float64Array | undefined + private _size: Float64Array | undefined constructor( x: number = 0, @@ -78,8 +78,8 @@ export class Rectangle extends Float64Array { * Updating the values of the returned object will update this rectangle. */ get pos(): Point { - this.#pos ??= this.subarray(0, 2) - return this.#pos! as unknown as Point + this._pos ??= this.subarray(0, 2) + return this._pos! as unknown as Point } set pos(value: Readonly) { @@ -93,8 +93,8 @@ export class Rectangle extends Float64Array { * Updating the values of the returned object will update this rectangle. */ get size(): Size { - this.#size ??= this.subarray(2, 4) - return this.#size! as unknown as Size + this._size ??= this.subarray(2, 4) + return this._size! as unknown as Size } set size(value: Readonly) { diff --git a/src/lib/litegraph/src/node/NodeInputSlot.ts b/src/lib/litegraph/src/node/NodeInputSlot.ts index 6f8dc352c..60aa4ebf4 100644 --- a/src/lib/litegraph/src/node/NodeInputSlot.ts +++ b/src/lib/litegraph/src/node/NodeInputSlot.ts @@ -23,15 +23,15 @@ export class NodeInputSlot extends NodeSlot implements INodeInputSlot { return !!this.widget } - #widget: WeakRef | undefined + private _widgetRef: WeakRef | undefined /** Internal use only; API is not finalised and may change at any time. */ get _widget(): IBaseWidget | undefined { - return this.#widget?.deref() + return this._widgetRef?.deref() } set _widget(widget: IBaseWidget | undefined) { - this.#widget = widget ? new WeakRef(widget) : undefined + this._widgetRef = widget ? new WeakRef(widget) : undefined } get collapsedPos(): Readonly { @@ -79,4 +79,12 @@ export class NodeInputSlot extends NodeSlot implements INodeInputSlot { ctx.textAlign = textAlign } + + override toJSON(): INodeInputSlot { + return { + ...super.toJSON(), + link: this.link, + widget: this.widget + } + } } diff --git a/src/lib/litegraph/src/node/NodeOutputSlot.ts b/src/lib/litegraph/src/node/NodeOutputSlot.ts index 68d6c2333..d0775274a 100644 --- a/src/lib/litegraph/src/node/NodeOutputSlot.ts +++ b/src/lib/litegraph/src/node/NodeOutputSlot.ts @@ -15,8 +15,6 @@ import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput import { isSubgraphOutput } from '@/lib/litegraph/src/subgraph/subgraphUtils' export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot { - #node: LGraphNode - links: LinkId[] | null _data?: unknown slot_index?: number @@ -27,7 +25,7 @@ export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot { get collapsedPos(): Readonly { return [ - this.#node._collapsed_width ?? LiteGraph.NODE_COLLAPSED_WIDTH, + this._node._collapsed_width ?? LiteGraph.NODE_COLLAPSED_WIDTH, LiteGraph.NODE_TITLE_HEIGHT * -0.5 ] } @@ -40,7 +38,6 @@ export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot { this.links = slot.links this._data = slot._data this.slot_index = slot.slot_index - this.#node = node } override isValidTarget( @@ -78,4 +75,12 @@ export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot { ctx.textAlign = textAlign ctx.strokeStyle = strokeStyle } + + override toJSON(): INodeOutputSlot { + return { + ...super.toJSON(), + links: this.links, + slot_index: this.slot_index + } + } } diff --git a/src/lib/litegraph/src/node/NodeSlot.ts b/src/lib/litegraph/src/node/NodeSlot.ts index 9fac7816f..86329660a 100644 --- a/src/lib/litegraph/src/node/NodeSlot.ts +++ b/src/lib/litegraph/src/node/NodeSlot.ts @@ -37,7 +37,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { pos?: Point /** The offset from the parent node to the centre point of this slot. */ - get #centreOffset(): Readonly { + private get _centreOffset(): Readonly { const nodePos = this.node.pos const { boundingRect } = this @@ -55,9 +55,9 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { /** The center point of this slot when the node is collapsed. */ abstract get collapsedPos(): Readonly - #node: LGraphNode + protected _node: LGraphNode get node(): LGraphNode { - return this.#node + return this._node } get highlightColor(): CanvasColour { @@ -89,7 +89,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { super(name, type, rectangle) Object.assign(this, rest) - this.#node = node + this._node = node } /** @@ -126,7 +126,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { ? this.highlightColor : LiteGraph.NODE_TEXT_COLOR - const pos = this.#centreOffset + const pos = this._centreOffset const slot_type = this.type const slot_shape = ( slot_type === SlotType.Array ? SlotShape.Grid : this.shape @@ -260,6 +260,25 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot { ctx.lineWidth = originalLineWidth } + /** + * Custom JSON serialization to prevent circular reference errors. + * Returns only serializable slot properties without the node back-reference. + */ + toJSON(): INodeSlot { + return { + name: this.name, + type: this.type, + label: this.label, + color_on: this.color_on, + color_off: this.color_off, + shape: this.shape, + dir: this.dir, + localized_name: this.localized_name, + pos: this.pos, + boundingRect: [...this.boundingRect] as [number, number, number, number] + } + } + drawCollapsed(ctx: CanvasRenderingContext2D) { const [x, y] = this.collapsedPos diff --git a/src/lib/litegraph/src/serialization.test.ts b/src/lib/litegraph/src/serialization.test.ts new file mode 100644 index 000000000..c2f6f23e9 --- /dev/null +++ b/src/lib/litegraph/src/serialization.test.ts @@ -0,0 +1,131 @@ +import { describe, expect, it } from 'vitest' + +import { LGraph, LGraphNode, LiteGraph } from './litegraph' +import type { NodeInputSlot } from './node/NodeInputSlot' +import type { NodeOutputSlot } from './node/NodeOutputSlot' + +class TestNode extends LGraphNode { + static override title = 'TestNode' + constructor() { + super('TestNode') + } +} + +LiteGraph.registerNodeType('test/TestNode', TestNode) + +describe('Serialization - Circular Reference Prevention', () => { + describe('LGraph.toJSON()', () => { + it('should serialize without circular reference errors', () => { + const graph = new LGraph() + + expect(() => JSON.stringify(graph)).not.toThrow() + }) + + it('should return serialize() output from toJSON()', () => { + const graph = new LGraph() + const serialized = graph.serialize() + const jsonOutput = graph.toJSON() + + expect(jsonOutput).toEqual(serialized) + }) + + it('should not include list_of_graphcanvas in JSON output', () => { + const graph = new LGraph() + const json = JSON.stringify(graph) + const parsed = JSON.parse(json) + + expect(parsed.list_of_graphcanvas).toBeUndefined() + }) + }) + + describe('NodeSlot.toJSON()', () => { + it('NodeInputSlot should serialize without circular reference errors', () => { + const graph = new LGraph() + const node = LiteGraph.createNode('test/TestNode')! + graph.add(node) + node.addInput('test_input', 'TEST') + + const inputSlot = node.inputs[0] + + expect(() => JSON.stringify(inputSlot)).not.toThrow() + }) + + it('NodeOutputSlot should serialize without circular reference errors', () => { + const graph = new LGraph() + const node = LiteGraph.createNode('test/TestNode')! + graph.add(node) + node.addOutput('test_output', 'TEST') + + const outputSlot = node.outputs[0] + + expect(() => JSON.stringify(outputSlot)).not.toThrow() + }) + + it('NodeInputSlot.toJSON() should not include _node reference', () => { + const graph = new LGraph() + const node = LiteGraph.createNode('test/TestNode')! + graph.add(node) + node.addInput('test_input', 'TEST') + + const inputSlot = node.inputs[0] as NodeInputSlot + const json = inputSlot.toJSON() + + expect('_node' in json).toBe(false) + expect('node' in json).toBe(false) + expect(json.name).toBe('test_input') + expect(json.type).toBe('TEST') + }) + + it('NodeOutputSlot.toJSON() should not include _node reference', () => { + const graph = new LGraph() + const node = LiteGraph.createNode('test/TestNode')! + graph.add(node) + node.addOutput('test_output', 'TEST') + + const outputSlot = node.outputs[0] as NodeOutputSlot + const json = outputSlot.toJSON() + + expect('_node' in json).toBe(false) + expect('node' in json).toBe(false) + expect(json.name).toBe('test_output') + expect(json.type).toBe('TEST') + }) + }) + + describe('Full graph with nodes - no circular references', () => { + it('should serialize a graph with connected nodes', () => { + const graph = new LGraph() + + const node1 = LiteGraph.createNode('test/TestNode')! + const node2 = LiteGraph.createNode('test/TestNode')! + graph.add(node1) + graph.add(node2) + + node1.addOutput('out', 'TEST') + node2.addInput('in', 'TEST') + + node1.connect(0, node2, 0) + + expect(() => JSON.stringify(graph)).not.toThrow() + }) + + it('should serialize graph.serialize() output without errors', () => { + const graph = new LGraph() + + const node1 = LiteGraph.createNode('test/TestNode')! + const node2 = LiteGraph.createNode('test/TestNode')! + graph.add(node1) + graph.add(node2) + + node1.addOutput('out', 'TEST') + node2.addInput('in', 'TEST') + + node1.connect(0, node2, 0) + + const serialized = graph.serialize() + + expect(() => JSON.stringify(serialized)).not.toThrow() + expect(() => JSON.parse(JSON.stringify(serialized))).not.toThrow() + }) + }) +}) diff --git a/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts b/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts index 366dbb96d..9421ee45f 100644 --- a/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts +++ b/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts @@ -50,7 +50,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode { inputs: { linkId: number | null; name: string; type: ISlotType }[] /** Backing field for {@link id}. */ - #id: ExecutionId + private _id: ExecutionId /** * The path to the actual node through subgraph instances, represented as a list of all subgraph node IDs (instances), @@ -62,7 +62,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode { * - `3` is the node ID of the actual node in the subgraph definition */ get id() { - return this.#id + return this._id } get type() { @@ -106,7 +106,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode { if (!node.graph) throw new NullGraphError() // Set the internal ID of the DTO - this.#id = [...this.subgraphNodePath, this.node.id].join(':') + this._id = [...this.subgraphNodePath, this.node.id].join(':') this.graph = node.graph this.inputs = this.node.inputs.map((x) => ({ linkId: x.link, @@ -265,7 +265,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode { // Upstreamed: Bypass nodes are bypassed using the first input with matching type if (this.mode === LGraphEventMode.BYPASS) { // Bypass nodes by finding first input with matching type - const matchingIndex = this.#getBypassSlotIndex(slot, type) + const matchingIndex = this._getBypassSlotIndex(slot, type) // No input types match - bypass not possible if (matchingIndex === -1) { @@ -281,7 +281,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode { const { node } = this if (node.isSubgraphNode()) - return this.#resolveSubgraphOutput(slot, type, visited) + return this._resolveSubgraphOutput(slot, type, visited) if (node.isVirtualNode) { const virtualLink = this.node.getInputLink(slot) @@ -321,7 +321,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode { * @param type The type of the final target input (so type list matches are accurate) * @returns The index of the input slot on this node, otherwise `-1`. */ - #getBypassSlotIndex(slot: number, type: ISlotType) { + private _getBypassSlotIndex(slot: number, type: ISlotType) { const { inputs } = this const oppositeInput = inputs[slot] const outputType = this.node.outputs[slot].type @@ -358,7 +358,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode { * @param visited A set of unique IDs to guard against infinite recursion. See {@link resolveInput}. * @returns A DTO for the node, and the origin ID / slot index of the output. */ - #resolveSubgraphOutput( + private _resolveSubgraphOutput( slot: number, type: ISlotType, visited: Set diff --git a/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts b/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts index a50633848..9c9690a8f 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts @@ -38,12 +38,12 @@ export abstract class SubgraphIONodeBase< static minWidth = 100 static roundedRadius = 10 - readonly #boundingRect: Rectangle = new Rectangle() + private readonly _boundingRect: Rectangle = new Rectangle() abstract readonly id: NodeId get boundingRect(): Rectangle { - return this.#boundingRect + return this._boundingRect } selected: boolean = false @@ -181,7 +181,7 @@ export abstract class SubgraphIONodeBase< ): void { // Only allow renaming non-empty slots if (slot !== this.emptySlot) { - this.#promptForSlotRename(slot, event) + this._promptForSlotRename(slot, event) } } @@ -191,14 +191,14 @@ export abstract class SubgraphIONodeBase< * @param event The event that triggered the context menu. */ protected showSlotContextMenu(slot: TSlot, event: CanvasPointerEvent): void { - const options: (IContextMenuValue | null)[] = this.#getSlotMenuOptions(slot) + const options: (IContextMenuValue | null)[] = this._getSlotMenuOptions(slot) if (!(options.length > 0)) return new LiteGraph.ContextMenu(options, { event, title: slot.name || 'Subgraph Output', callback: (item: IContextMenuValue) => { - this.#onSlotMenuAction(item, slot, event) + this._onSlotMenuAction(item, slot, event) } }) } @@ -208,7 +208,7 @@ export abstract class SubgraphIONodeBase< * @param slot The slot to get the context menu options for. * @returns The context menu options. */ - #getSlotMenuOptions(slot: TSlot): (IContextMenuValue | null)[] { + private _getSlotMenuOptions(slot: TSlot): (IContextMenuValue | null)[] { const options: (IContextMenuValue | null)[] = [] // Disconnect option if slot has connections @@ -239,7 +239,7 @@ export abstract class SubgraphIONodeBase< * @param slot The slot * @param event The event that triggered the context menu. */ - #onSlotMenuAction( + private _onSlotMenuAction( selectedItem: IContextMenuValue, slot: TSlot, event: CanvasPointerEvent @@ -260,7 +260,7 @@ export abstract class SubgraphIONodeBase< // Rename the slot case 'rename': if (slot !== this.emptySlot) { - this.#promptForSlotRename(slot, event) + this._promptForSlotRename(slot, event) } break } @@ -273,7 +273,7 @@ export abstract class SubgraphIONodeBase< * @param slot The slot to rename. * @param event The event that triggered the rename. */ - #promptForSlotRename(slot: TSlot, event: CanvasPointerEvent): void { + private _promptForSlotRename(slot: TSlot, event: CanvasPointerEvent): void { this.subgraph.canvasAction((c) => c.prompt( 'Slot name', @@ -362,7 +362,7 @@ export abstract class SubgraphIONodeBase< } configure(data: ExportedSubgraphIONode): void { - this.#boundingRect.set(data.bounding) + this._boundingRect.set(data.bounding) this.pinned = data.pinned ?? false } diff --git a/src/lib/litegraph/src/subgraph/SubgraphNode.ts b/src/lib/litegraph/src/subgraph/SubgraphNode.ts index a1fc265d8..fcbeed36a 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphNode.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphNode.ts @@ -63,7 +63,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { override widgets: IBaseWidget[] = [] /** Manages lifecycle of all subgraph event listeners */ - #eventAbortController = new AbortController() + private _eventAbortController = new AbortController() constructor( /** The (sub)graph that contains this subgraph instance. */ @@ -76,7 +76,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { // Update this node when the subgraph input / output slots are changed const subgraphEvents = this.subgraph.events - const { signal } = this.#eventAbortController + const { signal } = this._eventAbortController subgraphEvents.addEventListener( 'input-added', @@ -89,12 +89,12 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { const { inputNode, input } = subgraph.links[linkId].resolve(subgraph) const widget = inputNode?.widgets?.find?.((w) => w.name == name) if (widget) - this.#setWidget(subgraphInput, existingInput, widget, input?.widget) + this._setWidget(subgraphInput, existingInput, widget, input?.widget) return } const input = this.addInput(name, type) - this.#addSubgraphInputListeners(subgraphInput, input) + this._addSubgraphInputListeners(subgraphInput, input) }, { signal } ) @@ -179,7 +179,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { } } - #addSubgraphInputListeners( + private _addSubgraphInputListeners( subgraphInput: SubgraphInput, input: INodeInputSlot & Partial ) { @@ -201,7 +201,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { if (!widget) return const widgetLocator = e.detail.input.widget - this.#setWidget(subgraphInput, input, widget, widgetLocator) + this._setWidget(subgraphInput, input, widget, widgetLocator) }, { signal } ) @@ -288,7 +288,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { continue } - this.#addSubgraphInputListeners(subgraphInput, input) + this._addSubgraphInputListeners(subgraphInput, input) // Find the first widget that this slot is connected to for (const linkId of subgraphInput.linkIds) { @@ -318,13 +318,13 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { const widget = inputNode.getWidgetFromSlot(targetInput) if (!widget) continue - this.#setWidget(subgraphInput, input, widget, targetInput.widget) + this._setWidget(subgraphInput, input, widget, targetInput.widget) break } } } - #setWidget( + private _setWidget( subgraphInput: Readonly, input: INodeInputSlot, widget: Readonly, @@ -553,7 +553,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { override onRemoved(): void { // Clean up all subgraph event listeners - this.#eventAbortController.abort() + this._eventAbortController.abort() // Clean up all promoted widgets for (const widget of this.widgets) { diff --git a/src/lib/litegraph/src/widgets/BaseWidget.ts b/src/lib/litegraph/src/widgets/BaseWidget.ts index 9104c8a5b..689dd205f 100644 --- a/src/lib/litegraph/src/widgets/BaseWidget.ts +++ b/src/lib/litegraph/src/widgets/BaseWidget.ts @@ -57,10 +57,10 @@ export abstract class BaseWidget< maxWidth?: number } - #node: LGraphNode + private _node: LGraphNode /** The node that this widget belongs to. */ get node() { - return this.#node + return this._node } linkedWidgets?: IBaseWidget[] @@ -97,20 +97,20 @@ export abstract class BaseWidget< canvas: LGraphCanvas ): boolean - #value?: TWidget['value'] + private _value?: TWidget['value'] get value(): TWidget['value'] { - return this.#value + return this._value } set value(value: TWidget['value']) { - this.#value = value + this._value = value } constructor(widget: TWidget & { node: LGraphNode }) constructor(widget: TWidget, node: LGraphNode) constructor(widget: TWidget & { node: LGraphNode }, node?: LGraphNode) { // Private fields - this.#node = node ?? widget.node + this._node = node ?? widget.node // The set and get functions for DOM widget values are hacked on to the options object; // attempting to set value before options will throw. diff --git a/src/scripts/api.ts b/src/scripts/api.ts index a0efb2c54..57c0724ec 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -294,7 +294,7 @@ export class PromptExecutionError extends Error { } export class ComfyApi extends EventTarget { - #registered = new Set() + private _registered = new Set() api_host: string api_base: string /** @@ -451,7 +451,7 @@ export class ComfyApi extends EventTarget { ) { // Type assertion: strictFunctionTypes. So long as we emit events in a type-safe fashion, this is safe. super.addEventListener(type, callback as EventListener, options) - this.#registered.add(type) + this._registered.add(type) } override removeEventListener( @@ -492,7 +492,7 @@ export class ComfyApi extends EventTarget { /** * Poll status for colab and other things that don't support websockets. */ - #pollQueue() { + private _pollQueue() { setInterval(async () => { try { const resp = await this.fetchApi('/prompt') @@ -568,7 +568,7 @@ export class ComfyApi extends EventTarget { this.socket.addEventListener('error', () => { if (this.socket) this.socket.close() if (!isReconnect && !opened) { - this.#pollQueue() + this._pollQueue() } }) @@ -691,7 +691,7 @@ export class ComfyApi extends EventTarget { ) break default: - if (this.#registered.has(msg.type)) { + if (this._registered.has(msg.type)) { // Fallback for custom types - calls super direct. super.dispatchEvent( new CustomEvent(msg.type, { detail: msg.data }) @@ -956,7 +956,7 @@ export class ComfyApi extends EventTarget { * @param {*} type The endpoint to post to * @param {*} body Optional POST data */ - async #postItem(type: string, body?: Record) { + private async _postItem(type: string, body?: Record) { try { await this.fetchApi('/' + type, { method: 'POST', @@ -976,7 +976,7 @@ export class ComfyApi extends EventTarget { * @param {number} id The id of the item to delete */ async deleteItem(type: string, id: string) { - await this.#postItem(type, { delete: [id] }) + await this._postItem(type, { delete: [id] }) } /** @@ -984,7 +984,7 @@ export class ComfyApi extends EventTarget { * @param {string} type The type of list to clear, queue or history */ async clearItems(type: string) { - await this.#postItem(type, { clear: true }) + await this._postItem(type, { clear: true }) } /** @@ -993,7 +993,7 @@ export class ComfyApi extends EventTarget { * @param {string | null} [runningPromptId] Optional Running Prompt ID to interrupt */ async interrupt(runningPromptId: string | null) { - await this.#postItem( + await this._postItem( 'interrupt', runningPromptId ? { prompt_id: runningPromptId } : undefined ) diff --git a/src/scripts/ui.ts b/src/scripts/ui.ts index 0d1b383fb..802b098ba 100644 --- a/src/scripts/ui.ts +++ b/src/scripts/ui.ts @@ -241,17 +241,17 @@ function dragElement(dragEl): () => void { } class ComfyList { - #type - #text - #reverse + private _type + private _text + private _reverse element: HTMLDivElement button?: HTMLButtonElement // @ts-expect-error fixme ts strict error constructor(text, type?, reverse?) { - this.#text = text - this.#type = type || text.toLowerCase() - this.#reverse = reverse || false + this._text = text + this._type = type || text.toLowerCase() + this._reverse = reverse || false this.element = $el('div.comfy-list') as HTMLDivElement this.element.style.display = 'none' } @@ -261,7 +261,7 @@ class ComfyList { } async load() { - const items = await api.getItems(this.#type) + const items = await api.getItems(this._type) this.element.replaceChildren( ...Object.keys(items).flatMap((section) => [ $el('h4', { @@ -269,12 +269,12 @@ class ComfyList { }), $el('div.comfy-list-items', [ // @ts-expect-error fixme ts strict error - ...(this.#reverse ? items[section].reverse() : items[section]).map( + ...(this._reverse ? items[section].reverse() : items[section]).map( (item: LegacyQueueItem) => { // Allow items to specify a custom remove action (e.g. for interrupt current prompt) const removeAction = item.remove ?? { name: 'Delete', - cb: () => api.deleteItem(this.#type, item.prompt[1]) + cb: () => api.deleteItem(this._type, item.prompt[1]) } return $el('div', { textContent: item.prompt[0] + ': ' }, [ $el('button', { @@ -311,9 +311,9 @@ class ComfyList { ]), $el('div.comfy-list-actions', [ $el('button', { - textContent: 'Clear ' + this.#text, + textContent: 'Clear ' + this._text, onclick: async () => { - await api.clearItems(this.#type) + await api.clearItems(this._type) await this.load() } }), @@ -339,7 +339,7 @@ class ComfyList { hide() { this.element.style.display = 'none' // @ts-expect-error fixme ts strict error - this.button.textContent = 'View ' + this.#text + this.button.textContent = 'View ' + this._text } toggle() { diff --git a/src/scripts/ui/components/asyncDialog.ts b/src/scripts/ui/components/asyncDialog.ts index d1f45a9de..30518d237 100644 --- a/src/scripts/ui/components/asyncDialog.ts +++ b/src/scripts/ui/components/asyncDialog.ts @@ -6,7 +6,7 @@ type DialogAction = string | { value?: T; text: string } export class ComfyAsyncDialog< T = string | null > extends ComfyDialog { - #resolve: (value: T | null) => void = () => {} + private _resolve: (value: T | null) => void = () => {} constructor(actions?: Array>) { super( @@ -30,7 +30,7 @@ export class ComfyAsyncDialog< super.show(html) return new Promise((resolve) => { - this.#resolve = resolve + this._resolve = resolve }) } @@ -43,12 +43,12 @@ export class ComfyAsyncDialog< this.element.showModal() return new Promise((resolve) => { - this.#resolve = resolve + this._resolve = resolve }) } override close(result: T | null = null) { - this.#resolve(result) + this._resolve(result) this.element.close() super.close() } diff --git a/src/scripts/ui/components/button.ts b/src/scripts/ui/components/button.ts index 7a4aae6b6..7d3bc116c 100644 --- a/src/scripts/ui/components/button.ts +++ b/src/scripts/ui/components/button.ts @@ -21,8 +21,8 @@ type ComfyButtonProps = { } export class ComfyButton implements ComfyComponent { - #over = 0 - #popupOpen = false + private _over = 0 + private _popupOpen = false isOver = false iconElement = $el('i.mdi') contentElement = $el('span') @@ -123,7 +123,7 @@ export class ComfyButton implements ComfyComponent { this.element.addEventListener('click', (e) => { if (this.popup) { // we are either a touch device or triggered by click not hover - if (!this.#over) { + if (!this._over) { this.popup.toggle() } } @@ -157,7 +157,7 @@ export class ComfyButton implements ComfyComponent { internalClasses.push('disabled') } if (this.popup) { - if (this.#popupOpen) { + if (this._popupOpen) { internalClasses.push('popup-open') } else { internalClasses.push('popup-closed') @@ -172,16 +172,16 @@ export class ComfyButton implements ComfyComponent { if (mode === 'hover') { for (const el of [this.element, this.popup.element]) { el.addEventListener('mouseenter', () => { - this.popup.open = !!++this.#over + this.popup.open = !!++this._over }) el.addEventListener('mouseleave', () => { - this.popup.open = !!--this.#over + this.popup.open = !!--this._over }) } } popup.addEventListener('change', () => { - this.#popupOpen = popup.open + this._popupOpen = popup.open this.updateClasses() }) diff --git a/src/scripts/ui/components/popup.ts b/src/scripts/ui/components/popup.ts index 04bb38149..3896a075d 100644 --- a/src/scripts/ui/components/popup.ts +++ b/src/scripts/ui/components/popup.ts @@ -54,9 +54,9 @@ export class ComfyPopup extends EventTarget { this.open = prop(this, 'open', false, (v, o) => { if (v === o) return if (v) { - this.#show() + this._show() } else { - this.#hide() + this._hide() } }) } @@ -65,24 +65,24 @@ export class ComfyPopup extends EventTarget { this.open = !this.open } - #hide() { + private _hide() { this.element.classList.remove('open') window.removeEventListener('resize', this.update) - window.removeEventListener('click', this.#clickHandler, { capture: true }) - window.removeEventListener('keydown', this.#escHandler, { capture: true }) + window.removeEventListener('click', this._clickHandler, { capture: true }) + window.removeEventListener('keydown', this._escHandler, { capture: true }) this.dispatchEvent(new CustomEvent('close')) this.dispatchEvent(new CustomEvent('change')) } - #show() { + private _show() { this.element.classList.add('open') this.update() window.addEventListener('resize', this.update) - window.addEventListener('click', this.#clickHandler, { capture: true }) + window.addEventListener('click', this._clickHandler, { capture: true }) if (this.closeOnEscape) { - window.addEventListener('keydown', this.#escHandler, { capture: true }) + window.addEventListener('keydown', this._escHandler, { capture: true }) } this.dispatchEvent(new CustomEvent('open')) @@ -90,7 +90,7 @@ export class ComfyPopup extends EventTarget { } // @ts-expect-error fixme ts strict error - #escHandler = (e) => { + private _escHandler = (e) => { if (e.key === 'Escape') { this.open = false e.preventDefault() @@ -99,7 +99,7 @@ export class ComfyPopup extends EventTarget { } // @ts-expect-error fixme ts strict error - #clickHandler = (e) => { + private _clickHandler = (e) => { /** @type {any} */ const target = e.target if ( diff --git a/src/scripts/ui/dialog.ts b/src/scripts/ui/dialog.ts index 12ca4a2e5..51070ea85 100644 --- a/src/scripts/ui/dialog.ts +++ b/src/scripts/ui/dialog.ts @@ -5,11 +5,11 @@ export class ComfyDialog< > extends EventTarget { element: T textElement!: HTMLElement - #buttons: HTMLButtonElement[] | null + private _buttons: HTMLButtonElement[] | null constructor(type = 'div', buttons: HTMLButtonElement[] | null = null) { super() - this.#buttons = buttons + this._buttons = buttons this.element = $el(type + '.comfy-modal', { parent: document.body }, [ $el('div.comfy-modal-content', [ $el('p', { $: (p) => (this.textElement = p) }), @@ -20,7 +20,7 @@ export class ComfyDialog< createButtons() { return ( - this.#buttons ?? [ + this._buttons ?? [ $el('button', { type: 'button', textContent: 'Close', diff --git a/src/utils/migration/migrateReroute.ts b/src/utils/migration/migrateReroute.ts index 07ba18dc3..0d60a426d 100644 --- a/src/utils/migration/migrateReroute.ts +++ b/src/utils/migration/migrateReroute.ts @@ -54,7 +54,7 @@ class ConversionContext { /** Reroutes that has at least a valid link pass through it */ validReroutes: Set - #rerouteIdCounter = 0 + private _rerouteIdCounter = 0 constructor(public workflow: WorkflowJSON04) { this.nodeById = _.keyBy(workflow.nodes.map(_.cloneDeep), 'id') @@ -76,7 +76,7 @@ class ConversionContext { pos: getNodeCenter(node), linkIds: [] })) - this.#rerouteIdCounter = reroutes.length + 1 + this._rerouteIdCounter = reroutes.length + 1 this.rerouteByNodeId = _.keyBy(reroutes, 'nodeId') this.rerouteById = _.keyBy(reroutes, 'id') @@ -88,7 +88,7 @@ class ConversionContext { /** * Gets the chain of reroute nodes leading to the given node */ - #getRerouteChain(node: RerouteNode): RerouteNode[] { + private _getRerouteChain(node: RerouteNode): RerouteNode[] { const nodes: RerouteNode[] = [] let currentNode: RerouteNode = node while (currentNode?.type === 'Reroute') { @@ -106,7 +106,7 @@ class ConversionContext { return nodes } - #connectRerouteChain(rerouteNodes: RerouteNode[]): Reroute[] { + private _connectRerouteChain(rerouteNodes: RerouteNode[]): Reroute[] { const reroutes = rerouteNodes.map((node) => this.rerouteByNodeId[node.id]) for (const reroute of reroutes) { this.validReroutes.add(reroute) @@ -121,7 +121,7 @@ class ConversionContext { return reroutes } - #createNewLink( + private _createNewLink( startingLink: ComfyLinkObject, endingLink: ComfyLinkObject, rerouteNodes: RerouteNode[] @@ -136,7 +136,7 @@ class ConversionContext { parentId: reroute.id }) - const reroutes = this.#connectRerouteChain(rerouteNodes) + const reroutes = this._connectRerouteChain(rerouteNodes) for (const reroute of reroutes) { reroute.linkIds ??= [] reroute.linkIds.push(endingLink.id) @@ -153,11 +153,11 @@ class ConversionContext { } } - #createNewInputFloatingLink( + private _createNewInputFloatingLink( endingLink: ComfyLinkObject, rerouteNodes: RerouteNode[] ): ComfyLinkObject { - const reroutes = this.#connectRerouteChain(rerouteNodes) + const reroutes = this._connectRerouteChain(rerouteNodes) for (const reroute of reroutes) { if (!reroute.linkIds?.length) { reroute.floating = { @@ -166,7 +166,7 @@ class ConversionContext { } } return { - id: this.#rerouteIdCounter++, + id: this._rerouteIdCounter++, origin_id: -1, origin_slot: -1, target_id: endingLink.target_id, @@ -176,11 +176,11 @@ class ConversionContext { } } - #createNewOutputFloatingLink( + private _createNewOutputFloatingLink( startingLink: ComfyLinkObject, rerouteNodes: RerouteNode[] ): ComfyLinkObject { - const reroutes = this.#connectRerouteChain(rerouteNodes) + const reroutes = this._connectRerouteChain(rerouteNodes) for (const reroute of reroutes) { if (!reroute.linkIds?.length) { reroute.floating = { @@ -190,7 +190,7 @@ class ConversionContext { } return { - id: this.#rerouteIdCounter++, + id: this._rerouteIdCounter++, origin_id: startingLink.origin_id, origin_slot: startingLink.origin_slot, target_id: -1, @@ -200,7 +200,7 @@ class ConversionContext { } } - #reconnectLinks(nodes: ComfyNode[], links: ComfyLinkObject[]): void { + private _reconnectLinks(nodes: ComfyNode[], links: ComfyLinkObject[]): void { // Remove all existing links on sockets for (const node of nodes) { for (const input of node.inputs ?? []) { @@ -245,18 +245,18 @@ class ConversionContext { const endingRerouteNode = this.nodeById[ endingLink.origin_id ] as RerouteNode - const rerouteNodes = this.#getRerouteChain(endingRerouteNode) + const rerouteNodes = this._getRerouteChain(endingRerouteNode) const startingLink = this.linkById[ rerouteNodes[rerouteNodes.length - 1]?.inputs?.[0]?.link ?? -1 ] if (startingLink) { // Valid link found, create a new link - links.push(this.#createNewLink(startingLink, endingLink, rerouteNodes)) + links.push(this._createNewLink(startingLink, endingLink, rerouteNodes)) } else { // Floating link found, create a new floating link floatingLinks.push( - this.#createNewInputFloatingLink(endingLink, rerouteNodes) + this._createNewInputFloatingLink(endingLink, rerouteNodes) ) } } @@ -270,14 +270,14 @@ class ConversionContext { }) for (const rerouteNode of floatingEndingRerouteNodes) { - const rerouteNodes = this.#getRerouteChain(rerouteNode) + const rerouteNodes = this._getRerouteChain(rerouteNode) const startingLink = this.linkById[ rerouteNodes[rerouteNodes.length - 1]?.inputs?.[0]?.link ?? -1 ] if (startingLink) { floatingLinks.push( - this.#createNewOutputFloatingLink(startingLink, rerouteNodes) + this._createNewOutputFloatingLink(startingLink, rerouteNodes) ) } } @@ -285,7 +285,7 @@ class ConversionContext { const nodes = Object.values(this.nodeById).filter( (node) => node.type !== 'Reroute' ) - this.#reconnectLinks(nodes, links) + this._reconnectLinks(nodes, links) return { ...this.workflow,