Code cleanup, docs, TS types (#278)

* Fix test TS types

Prefer relative imports over @imports

* Add tuple names SerialisedLLinkArray

* nit

* [Refactor] LGraphCanvas.computeConnectionPoint

* [Refactor] Add LGraphCanvas.#dirty()

* Remove string LLink IDs

No downstream support for strings at present.

* nit - Rename

* nit

* nit

* nit - Remove unused code

* nit - Doc

* nit

* nit - Remove redundant code

* Remove unused: LGraphCanvas.isOverNodeBox

* Optimise - remove 2d context save/restore

* [Refactor] Move node collapsed check to class

* Add minor code change to support stricter types

* Revert accidental deletion in 0f2d04d

0f2d04d09e5e8a27a6d46d8c910dc077ed2d9071
This commit is contained in:
filtered
2024-11-05 13:08:59 +11:00
committed by GitHub
parent e1a118a70f
commit e6961f1c5a
8 changed files with 154 additions and 162 deletions

View File

@@ -1,5 +1,5 @@
import type { Dictionary, IContextMenuValue, ISlotType, MethodNames, Point } from "./interfaces"
import type { ISerialisedGraph } from "@/types/serialisation"
import type { ISerialisedGraph } from "./types/serialisation"
import { LGraphEventMode, TitleMode } from "./types/globalEnums"
import { LiteGraph } from "./litegraph"
import { LGraphCanvas } from "./LGraphCanvas"
@@ -22,10 +22,6 @@ type ParamsArray<T extends Record<any, any>, K extends MethodNames<T>> = Paramet
+ onNodeAdded: when a new node is added to the graph
+ onNodeRemoved: when a node inside this graph is removed
+ onNodeConnectionChange: some connection has changed in the graph (connected or disconnected)
*
* @class LGraph
* @constructor
* @param {Object} o data from previous serialization [optional]
*/
export class LGraph {
@@ -113,6 +109,10 @@ export class LGraph {
private _input_nodes?: LGraphNode[]
/**
* See {@link LGraph}
* @param o data from previous serialization [optional]
*/
constructor(o?: ISerialisedGraph) {
if (LiteGraph.debug) console.log("Graph created")

View File

@@ -1,4 +1,4 @@
import type { CanvasColour, Dictionary, Direction, IBoundaryNodes, IContextMenuOptions, INodeSlot, INodeInputSlot, INodeOutputSlot, IOptionalSlotData, Point, Rect, Rect32, Size, IContextMenuValue, ISlotType, ConnectingLink, NullableProperties, Positionable } from "./interfaces"
import type { CanvasColour, Dictionary, Direction, IBoundaryNodes, IContextMenuOptions, INodeSlot, INodeInputSlot, INodeOutputSlot, IOptionalSlotData, Point, Rect, Rect32, Size, IContextMenuValue, ISlotType, ConnectingLink, NullableProperties, Positionable, ReadOnlyPoint } from "./interfaces"
import type { IWidget, TWidgetValue } from "./types/widgets"
import { LGraphNode, type NodeId } from "./LGraphNode"
import type { CanvasDragEvent, CanvasMouseEvent, CanvasWheelEvent, CanvasEventDetail, CanvasPointerEvent } from "./types/events"
@@ -1268,8 +1268,7 @@ export class LGraphCanvas {
this.dragging_canvas = false
this.dirty_canvas = true
this.dirty_bgcanvas = true
this.#dirty()
this.dirty_area = null
this.node_in_panel = null
@@ -1573,6 +1572,13 @@ export class LGraphCanvas {
if (fgcanvas) this.dirty_canvas = true
if (bgcanvas) this.dirty_bgcanvas = true
}
/** Marks the entire canvas as dirty. */
#dirty(): void {
this.dirty_canvas = true
this.dirty_bgcanvas = true
}
/**
* Used to attach the canvas in a popup
*
@@ -1643,7 +1649,7 @@ export class LGraphCanvas {
const y = graphPos[1] - node.pos[1]
for (const widget of node.widgets) {
if(widget.hidden || (widget.advanced && !node.showAdvanced)) continue;
if (widget.hidden || (widget.advanced && !node.showAdvanced)) continue
let widgetWidth, widgetHeight
if (widget.computeSize) {
@@ -1701,7 +1707,8 @@ export class LGraphCanvas {
if (this.set_canvas_dirty_on_mouse_event)
this.dirty_canvas = true
if (!this.graph) return
const { graph } = this
if (!graph) return
this.adjustMouseEvent(e)
@@ -1723,7 +1730,7 @@ export class LGraphCanvas {
if (!is_inside) return
let node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes)
let node = graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes)
let skip_action = false
const now = LiteGraph.getTime()
const is_double_click = (now - this.last_mouseclick < 300)
@@ -1745,11 +1752,12 @@ export class LGraphCanvas {
//left button mouse / single finger
if (e.which == 1 && !this.pointer_is_double) {
if ((e.metaKey || e.ctrlKey) && !e.altKey) {
this.dragging_rectangle = new Float32Array(4)
this.dragging_rectangle[0] = e.canvasX
this.dragging_rectangle[1] = e.canvasY
this.dragging_rectangle[2] = 1
this.dragging_rectangle[3] = 1
const dragRect = new Float32Array(4)
dragRect[0] = e.canvasX
dragRect[1] = e.canvasY
dragRect[2] = 1
dragRect[3] = 1
this.dragging_rectangle = dragRect
skip_action = true
}
@@ -1762,11 +1770,11 @@ export class LGraphCanvas {
cloned.pos[0] += 5
cloned.pos[1] += 5
this.graph.add(cloned, false)
graph.add(cloned, false)
node = cloned
skip_action = true
if (this.allow_dragnodes) {
this.graph.beforeChange()
graph.beforeChange()
this.node_dragged = node
this.isDragging = true
}
@@ -1789,7 +1797,7 @@ export class LGraphCanvas {
//Search for corner for resize
if (!skip_action &&
node.resizable !== false && node.inResizeCorner(e.canvasX, e.canvasY)) {
this.graph.beforeChange()
graph.beforeChange()
this.resizing_node = node
this.canvas.style.cursor = "se-resize"
skip_action = true
@@ -1813,9 +1821,9 @@ export class LGraphCanvas {
this.connecting_links = []
for (const linkId of output.links) {
const link = this.graph._links.get(linkId)
const link = graph._links.get(linkId)
const slot = link.target_slot
const linked_node = this.graph._nodes_by_id[link.target_id]
const linked_node = graph._nodes_by_id[link.target_id]
const input = linked_node.inputs[slot]
const pos = linked_node.getConnectionPos(true, slot)
@@ -1888,9 +1896,9 @@ export class LGraphCanvas {
if (input.link !== null) {
//before disconnecting
const link_info = this.graph._links.get(input.link)
const link_info = graph._links.get(input.link)
const slot = link_info.origin_slot
const linked_node = this.graph._nodes_by_id[link_info.origin_id]
const linked_node = graph._nodes_by_id[link_info.origin_id]
if (LiteGraph.click_do_break_link_to || (LiteGraph.ctrl_alt_click_do_break_link && e.ctrlKey && e.altKey && !e.shiftKey)) {
node.disconnectInput(i)
} else if (e.shiftKey) {
@@ -1998,7 +2006,7 @@ export class LGraphCanvas {
if (!block_drag_node) {
if (this.allow_dragnodes) {
this.graph.beforeChange()
graph.beforeChange()
this.node_dragged = node
this.isDragging = true
}
@@ -2038,7 +2046,7 @@ export class LGraphCanvas {
}
if (overLink) {
const slot = overLink.origin_slot
const originNode = this.graph._nodes_by_id[overLink.origin_id]
const originNode = graph._nodes_by_id[overLink.origin_id]
this.connecting_links ??= []
this.connecting_links.push({
@@ -2060,7 +2068,7 @@ export class LGraphCanvas {
this.ctx.lineWidth = lineWidth
}
const group = this.graph.getGroupOnPos(e.canvasX, e.canvasY)
const group = graph.getGroupOnPos(e.canvasX, e.canvasY)
this.selected_group = group
if (group && !this.read_only) {
if (e.ctrlKey) {
@@ -2197,12 +2205,11 @@ export class LGraphCanvas {
}
this.last_mouse[0] = e.clientX
this.last_mouse[1] = e.clientY
this.last_mouse = [e.clientX, e.clientY]
this.last_mouseclick = LiteGraph.getTime()
this.last_mouse_dragging = true
this.graph.change()
graph.change()
//this is to ensure to defocus(blur) if a text input element is on focus
if (!ref_window.document.activeElement ||
@@ -2276,8 +2283,7 @@ export class LGraphCanvas {
} 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_canvas = true
this.dirty_bgcanvas = true
this.#dirty()
} else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) {
if (this.connecting_links) this.dirty_canvas = true
@@ -2354,8 +2360,6 @@ export class LGraphCanvas {
highlightInput = node.inputs[targetSlotId]
}
}
} else if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {
//mouse on top of the corner box, don't know what to do
} else {
//check if I have a slot below de mouse
if (inputId != -1 && node.inputs[inputId] && LiteGraph.isValidConnection(firstLink.output.type, node.inputs[inputId].type)) {
@@ -2373,8 +2377,6 @@ export class LGraphCanvas {
node.getConnectionPos(false, targetSlotId, pos)
highlightPos = pos
}
} else if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {
//mouse on top of the corner box, don't know what to do
} else {
//check if I have a slot below de mouse
if (outputId != -1 && node.outputs[outputId] && LiteGraph.isValidConnection(firstLink.input.type, node.outputs[outputId].type)) {
@@ -2439,8 +2441,7 @@ export class LGraphCanvas {
const deltaY = delta[1] / this.ds.scale
allItems.forEach(x => x.move(deltaX, deltaY, true))
this.dirty_canvas = true
this.dirty_bgcanvas = true
this.#dirty()
function addToSetRecursively(item: Positionable, items: Set<Positionable>): void {
if (items.has(item)) return
@@ -2458,8 +2459,7 @@ export class LGraphCanvas {
this.resizing_node.setSize(desired_size)
this.canvas.style.cursor = "se-resize"
this.dirty_canvas = true
this.dirty_bgcanvas = true
this.#dirty()
}
}
@@ -2506,30 +2506,31 @@ export class LGraphCanvas {
this.selected_group = null
this.isDragging = false
let node = this.graph.getNodeOnPos(
const node = this.graph.getNodeOnPos(
e.canvasX,
e.canvasY,
this.visible_nodes
)
if (this.dragging_rectangle) {
const dragRect = this.dragging_rectangle
if (dragRect) {
if (this.graph) {
const nodes = this.graph._nodes
const node_bounding = new Float32Array(4)
//compute bounding and flip if left to right
const w = Math.abs(this.dragging_rectangle[2])
const h = Math.abs(this.dragging_rectangle[3])
const startx = this.dragging_rectangle[2] < 0
? this.dragging_rectangle[0] - w
: this.dragging_rectangle[0]
const starty = this.dragging_rectangle[3] < 0
? this.dragging_rectangle[1] - h
: this.dragging_rectangle[1]
this.dragging_rectangle[0] = startx
this.dragging_rectangle[1] = starty
this.dragging_rectangle[2] = w
this.dragging_rectangle[3] = h
const w = Math.abs(dragRect[2])
const h = Math.abs(dragRect[3])
const startx = dragRect[2] < 0
? dragRect[0] - w
: dragRect[0]
const starty = dragRect[3] < 0
? dragRect[1] - h
: dragRect[1]
dragRect[0] = startx
dragRect[1] = starty
dragRect[2] = w
dragRect[3] = h
// test dragging rect size, if minimun simulate a click
if (!node || (w > 10 && h > 10)) {
@@ -2537,7 +2538,7 @@ export class LGraphCanvas {
const to_select = []
for (const nodeX of nodes) {
nodeX.getBounding(node_bounding)
if (!overlapBounding(this.dragging_rectangle, node_bounding)) continue
if (!overlapBounding(dragRect, node_bounding)) continue
to_select.push(nodeX)
}
@@ -2547,8 +2548,7 @@ export class LGraphCanvas {
// Select groups
const groups = this.graph.groups
for (const group of groups) {
const r = this.dragging_rectangle
if (!containsRect(r, group._bounding)) continue
if (!containsRect(dragRect, group._bounding)) continue
this.selectedItems.add(group)
group.recomputeInsideNodes()
group.selected = true
@@ -2567,8 +2567,7 @@ export class LGraphCanvas {
for (const link of this.connecting_links) {
//dragging a connection
this.dirty_canvas = true
this.dirty_bgcanvas = true
this.#dirty()
//slot below mouse? connect
if (link.output) {
@@ -2648,38 +2647,27 @@ export class LGraphCanvas {
this.connecting_links = null
} //not dragging connection
else if (this.resizing_node) {
this.dirty_canvas = true
this.dirty_bgcanvas = true
this.#dirty()
this.graph.afterChange(this.resizing_node)
this.resizing_node = null
} else if (this.node_dragged) {
//node being dragged?
node = this.node_dragged
if (node &&
e.click_time < 300 &&
isInsideRectangle(e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT)) {
node.collapse()
const { node_dragged } = this
if (e.click_time < 300 && node_dragged?.isPointInCollapse(e.canvasX, e.canvasY)) {
node_dragged.collapse()
}
this.dirty_canvas = true
this.dirty_bgcanvas = true
this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0])
this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1])
this.#dirty()
node_dragged.pos[0] = Math.round(node_dragged.pos[0])
node_dragged.pos[1] = Math.round(node_dragged.pos[1])
if (this.graph.config.align_to_grid || this.align_to_grid) {
this.node_dragged.alignToGrid()
node_dragged.alignToGrid()
}
this.onNodeMoved?.(this.node_dragged)
this.graph.afterChange(this.node_dragged)
this.onNodeMoved?.(node_dragged)
this.graph.afterChange(node_dragged)
this.node_dragged = null
} //no node being dragged
else {
//get node over
node = this.graph.getNodeOnPos(
e.canvasX,
e.canvasY,
this.visible_nodes
)
if (!node && e.click_time < 300 && !this.graph.groups.some(x => x.isPointInTitlebar(e.canvasX, e.canvasY))) {
this.deselectAll()
}
@@ -2750,20 +2738,7 @@ export class LGraphCanvas {
e.preventDefault()
return false
}
/**
* returns true if a position (in graph space) is on top of a node little corner box
**/
isOverNodeBox(node: LGraphNode, canvasx: number, canvasy: number): boolean {
const title_height = LiteGraph.NODE_TITLE_HEIGHT
return Boolean(isInsideRectangle(
canvasx,
canvasy,
node.pos[0] + 2,
node.pos[1] + 2 - title_height,
title_height - 4,
title_height - 4
))
}
/**
* returns the INDEX if a position (in graph space) is on top of a node input slot
**/
@@ -3414,8 +3389,7 @@ export class LGraphCanvas {
**/
setZoom(value: number, zooming_center: Point) {
this.ds.changeScale(value, zooming_center)
this.dirty_canvas = true
this.dirty_bgcanvas = true
this.#dirty()
}
/**
* converts a coordinate from graph coordinates to canvas2D coordinates
@@ -5270,7 +5244,7 @@ export class LGraphCanvas {
}
//render arrow
ctx.save()
const transform = ctx.getTransform()
ctx.translate(posA[0], posA[1])
ctx.rotate(angleA)
ctx.beginPath()
@@ -5278,8 +5252,8 @@ export class LGraphCanvas {
ctx.lineTo(0, +7)
ctx.lineTo(+5, -3)
ctx.fill()
ctx.restore()
ctx.save()
ctx.setTransform(transform)
ctx.translate(posC[0], posC[1])
ctx.rotate(angleB)
ctx.beginPath()
@@ -5287,7 +5261,7 @@ export class LGraphCanvas {
ctx.lineTo(0, +7)
ctx.lineTo(+5, -3)
ctx.fill()
ctx.restore()
ctx.setTransform(transform)
}
//circle
@@ -5314,59 +5288,67 @@ export class LGraphCanvas {
}
}
}
//returns the link center point based on curvature
computeConnectionPoint(a: Point,
b: Point,
/**
* Finds a point along a spline represented by a to b, with spline endpoint directions dictacted by start_dir and end_dir.
* @param a Start point
* @param b End point
* @param t Time: distance between points (e.g 0.25 is 25% along the line)
* @param start_dir Spline start direction
* @param end_dir Spline end direction
* @returns The point at {@link t} distance along the spline a-b.
*/
computeConnectionPoint(
a: ReadOnlyPoint,
b: ReadOnlyPoint,
t: number,
start_dir: number,
end_dir: number): number[] {
start_dir: LinkDirection,
end_dir: LinkDirection
): Point {
start_dir ||= LinkDirection.RIGHT
end_dir ||= LinkDirection.LEFT
const dist = distance(a, b)
const p0 = a
const p1 = [a[0], a[1]]
const p2 = [b[0], b[1]]
const p3 = b
const pa: Point = [a[0], a[1]]
const pb: Point = [b[0], b[1]]
switch (start_dir) {
case LinkDirection.LEFT:
p1[0] += dist * -0.25
break
case LinkDirection.RIGHT:
p1[0] += dist * 0.25
break
case LinkDirection.UP:
p1[1] += dist * -0.25
break
case LinkDirection.DOWN:
p1[1] += dist * 0.25
break
}
switch (end_dir) {
case LinkDirection.LEFT:
p2[0] += dist * -0.25
break
case LinkDirection.RIGHT:
p2[0] += dist * 0.25
break
case LinkDirection.UP:
p2[1] += dist * -0.25
break
case LinkDirection.DOWN:
p2[1] += dist * 0.25
break
}
this.#addSplineOffset(pa, start_dir, dist)
this.#addSplineOffset(pb, end_dir, dist)
const c1 = (1 - t) * (1 - t) * (1 - t)
const c2 = 3 * ((1 - t) * (1 - t)) * t
const c3 = 3 * (1 - t) * (t * t)
const c4 = t * t * t
const x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0]
const y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1]
const x = c1 * a[0] + c2 * pa[0] + c3 * pb[0] + c4 * b[0]
const y = c1 * a[1] + c2 * pa[1] + c3 * pb[1] + c4 * b[1]
return [x, y]
}
/**
* Modifies an existing point, adding a single-axis offset.
* @param point The point to add the offset to
* @param direction The direction to add the offset in
* @param dist Distance to offset
* @param factor Distance is mulitplied by this value. Default: 0.25
*/
#addSplineOffset(point: Point, direction: LinkDirection, dist: number, factor = 0.25): void {
switch (direction) {
case LinkDirection.LEFT:
point[0] += dist * -factor
break
case LinkDirection.RIGHT:
point[0] += dist * factor
break
case LinkDirection.UP:
point[1] += dist * -factor
break
case LinkDirection.DOWN:
point[1] += dist * factor
break
}
}
drawExecutionOrder(ctx: CanvasRenderingContext2D): void {
ctx.shadowColor = "transparent"
ctx.globalAlpha = 0.25
@@ -5425,7 +5407,7 @@ export class LGraphCanvas {
for (let i = 0; i < widgets.length; ++i) {
const w = widgets[i]
if(w.hidden || (w.advanced && !node.showAdvanced)) continue;
if (w.hidden || (w.advanced && !node.showAdvanced)) continue
const y = w.y || posY
if (w === this.link_over_widget) {
@@ -5914,8 +5896,7 @@ export class LGraphCanvas {
switchLiveMode(transition: boolean): void {
if (!transition) {
this.live_mode = !this.live_mode
this.dirty_canvas = true
this.dirty_bgcanvas = true
this.#dirty()
return
}

View File

@@ -1513,6 +1513,17 @@ export class LGraphNode implements Positionable, IPinnable {
return isXyInRectangle(x, y, this.boundingRect)
}
/**
* Checks if the provided point is inside this node's collapse button area.
* @param x X co-ordinate to check
* @param y Y co-ordinate to check
* @returns true if the x,y point is in the collapse button area, otherwise false
*/
isPointInCollapse(x: number, y: number): boolean {
const squareLength = LiteGraph.NODE_TITLE_HEIGHT
return isInsideRectangle(x, y, this.pos[0], this.pos[1] - squareLength, squareLength, squareLength)
}
/**
* checks if a point is inside a node slot, and returns info about which slot
* @param x
@@ -1915,9 +1926,9 @@ export class LGraphNode implements Positionable, IPinnable {
}
}
const nextId = LiteGraph.use_uuids
? LiteGraph.uuidv4()
: ++graph.last_link_id
// UUID: LinkIds
// const nextId = LiteGraph.use_uuids ? LiteGraph.uuidv4() : ++graph.last_link_id
const nextId = ++graph.last_link_id
//create link class
link_info = new LLink(
@@ -2300,7 +2311,7 @@ export class LGraphNode implements Positionable, IPinnable {
}
/**
* Collapse the node to make it smaller on the canvas
* Toggle node collapse (makes it smaller on the canvas)
**/
collapse(force?: boolean): void {
if (!this.collapsible && !force) return

View File

@@ -2,9 +2,9 @@ import type { CanvasColour, ISlotType } from "./interfaces"
import type { NodeId } from "./LGraphNode"
import type { Serialisable, SerialisableLLink } from "./types/serialisation"
export type LinkId = number | string
export type LinkId = number
export type SerialisedLLinkArray = [LinkId, NodeId, number, NodeId, number, ISlotType]
export type SerialisedLLinkArray = [id: LinkId, origin_id: NodeId, origin_slot: number, target_id: NodeId, target_slot: number, type: ISlotType]
//this is the class in charge of storing link information
export class LLink implements Serialisable<SerialisableLLink> {

View File

@@ -2,10 +2,10 @@
* Event interfaces for event extension
*/
import type { ConnectingLink, LinkReleaseContextExtended } from "@/litegraph"
import type { IWidget } from "@/types/widgets"
import type { LGraphNode } from "@/LGraphNode"
import type { LGraphGroup } from "@/LGraphGroup"
import type { ConnectingLink, LinkReleaseContextExtended } from "../litegraph"
import type { IWidget } from "./widgets"
import type { LGraphNode } from "../LGraphNode"
import type { LGraphGroup } from "../LGraphGroup"
/** For Canvas*Event - adds graph space co-ordinates (property names are shipped) */
export interface ICanvasPosition {

View File

@@ -1,10 +1,10 @@
import type { ISlotType, Dictionary, INodeFlags, INodeInputSlot, INodeOutputSlot, Point, Rect, Size } from "@/interfaces"
import type { LGraph } from "@/LGraph"
import type { IGraphGroupFlags, LGraphGroup } from "@/LGraphGroup"
import type { LGraphNode, NodeId } from "@/LGraphNode"
import type { LiteGraph } from "@/litegraph"
import type { LinkId, LLink } from "@/LLink"
import type { TWidgetValue } from "@/types/widgets"
import type { ISlotType, Dictionary, INodeFlags, INodeInputSlot, INodeOutputSlot, Point, Rect, Size } from "../interfaces"
import type { LGraph } from "../LGraph"
import type { IGraphGroupFlags, LGraphGroup } from "../LGraphGroup"
import type { LGraphNode, NodeId } from "../LGraphNode"
import type { LiteGraph } from "../litegraph"
import type { LinkId, LLink } from "../LLink"
import type { TWidgetValue } from "../types/widgets"
import { RenderShape } from "./globalEnums"
/**

View File

@@ -1,5 +1,5 @@
import { CanvasColour, Point, Size } from "@/interfaces"
import type { LGraphCanvas, LGraphNode } from "@/litegraph"
import { CanvasColour, Point, Size } from "../interfaces"
import type { LGraphCanvas, LGraphNode } from "../litegraph"
import type { CanvasMouseEvent } from "./events"
export interface IWidgetOptions<TValue = unknown> extends Record<string, unknown> {

View File

@@ -1,5 +1,5 @@
import type { Dictionary, Direction, IBoundaryNodes } from "@/interfaces"
import type { LGraphNode } from "@/LGraphNode"
import type { Dictionary, Direction, IBoundaryNodes } from "../interfaces"
import type { LGraphNode } from "../LGraphNode"
/**
* Finds the nodes that are farthest in all four directions, representing the boundary of the nodes.