diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index fd9ed9552..3efd28044 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -26,6 +26,7 @@ import type { Size, } from "./interfaces" import type { LGraph } from "./LGraph" +import type { ConnectionColorContext } from "./node/NodeSlot" import type { CanvasEventDetail, CanvasMouseEvent, @@ -57,7 +58,6 @@ import { overlapBounding, snapPoint, } from "./measure" -import { type ConnectionColorContext } from "./NodeSlot" import { Reroute, type RerouteId } from "./Reroute" import { stringOrEmpty } from "./strings" import { diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index 205e04656..a5a823fa6 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -20,6 +20,7 @@ import type { Size, } from "./interfaces" import type { LGraph } from "./LGraph" +import type { ConnectionColorContext } from "./node/NodeSlot" import type { Reroute, RerouteId } from "./Reroute" import type { CanvasMouseEvent } from "./types/events" import type { ISerialisedNode } from "./types/serialisation" @@ -32,7 +33,9 @@ import { LGraphCanvas } from "./LGraphCanvas" import { type LGraphNodeConstructor, LiteGraph } from "./litegraph" import { LLink } from "./LLink" import { createBounds, isInRect, isInRectangle, isPointInRect, snapPoint } from "./measure" -import { ConnectionColorContext, inputAsSerialisable, isINodeInputSlot, isWidgetInputSlot, NodeInputSlot, NodeOutputSlot, outputAsSerialisable, toNodeSlotClass } from "./NodeSlot" +import { NodeInputSlot } from "./node/NodeInputSlot" +import { NodeOutputSlot } from "./node/NodeOutputSlot" +import { inputAsSerialisable, isINodeInputSlot, isWidgetInputSlot, outputAsSerialisable, toNodeSlotClass } from "./node/slotUtils" import { LGraphEventMode, NodeSlotType, diff --git a/src/node/NodeInputSlot.ts b/src/node/NodeInputSlot.ts new file mode 100644 index 000000000..c4ecf5b8c --- /dev/null +++ b/src/node/NodeInputSlot.ts @@ -0,0 +1,40 @@ +import type { INodeInputSlot, INodeOutputSlot, OptionalProps } from "@/interfaces" +import type { LinkId } from "@/LLink" + +import { LabelPosition } from "@/draw" +import { LiteGraph } from "@/litegraph" +import { type IDrawOptions, NodeSlot } from "@/node/NodeSlot" + +export class NodeInputSlot extends NodeSlot implements INodeInputSlot { + link: LinkId | null + + get isWidgetInputSlot(): boolean { + return !!this.widget + } + + constructor(slot: OptionalProps) { + super(slot) + this.link = slot.link + } + + override isConnected(): boolean { + return this.link != null + } + + override isValidTarget(fromSlot: INodeInputSlot | INodeOutputSlot): boolean { + return "links" in fromSlot && LiteGraph.isValidConnection(this.type, fromSlot.type) + } + + override draw(ctx: CanvasRenderingContext2D, options: Omit) { + const originalTextAlign = ctx.textAlign + ctx.textAlign = "left" + + super.draw(ctx, { + ...options, + labelPosition: LabelPosition.Right, + doStroke: false, + }) + + ctx.textAlign = originalTextAlign + } +} diff --git a/src/node/NodeOutputSlot.ts b/src/node/NodeOutputSlot.ts new file mode 100644 index 000000000..bb5beb06a --- /dev/null +++ b/src/node/NodeOutputSlot.ts @@ -0,0 +1,47 @@ +import type { INodeInputSlot, INodeOutputSlot, OptionalProps } from "@/interfaces" +import type { LinkId } from "@/LLink" + +import { LabelPosition } from "@/draw" +import { LiteGraph } from "@/litegraph" +import { type IDrawOptions, NodeSlot } from "@/node/NodeSlot" + +export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot { + links: LinkId[] | null + _data?: unknown + slot_index?: number + + get isWidgetInputSlot(): false { + return false + } + + constructor(slot: OptionalProps) { + super(slot) + this.links = slot.links + this._data = slot._data + this.slot_index = slot.slot_index + } + + override isValidTarget(fromSlot: INodeInputSlot | INodeOutputSlot): boolean { + return "link" in fromSlot && LiteGraph.isValidConnection(this.type, fromSlot.type) + } + + override isConnected(): boolean { + return this.links != null && this.links.length > 0 + } + + override draw(ctx: CanvasRenderingContext2D, options: Omit) { + const originalTextAlign = ctx.textAlign + const originalStrokeStyle = ctx.strokeStyle + ctx.textAlign = "right" + ctx.strokeStyle = "black" + + super.draw(ctx, { + ...options, + labelPosition: LabelPosition.Left, + doStroke: true, + }) + + ctx.textAlign = originalTextAlign + ctx.strokeStyle = originalStrokeStyle + } +} diff --git a/src/NodeSlot.ts b/src/node/NodeSlot.ts similarity index 60% rename from src/NodeSlot.ts rename to src/node/NodeSlot.ts index 8e8750618..dc551c309 100644 --- a/src/NodeSlot.ts +++ b/src/node/NodeSlot.ts @@ -1,12 +1,11 @@ -import type { CanvasColour, Dictionary, INodeInputSlot, INodeOutputSlot, INodeSlot, ISlotType, IWidgetInputSlot, IWidgetLocator, OptionalProps, Point, Rect, SharedIntersection } from "./interfaces" -import type { LinkId } from "./LLink" -import type { IWidget } from "./types/widgets" +import type { CanvasColour, Dictionary, INodeInputSlot, INodeOutputSlot, INodeSlot, ISlotType, IWidgetLocator, OptionalProps, Point, Rect } from "@/interfaces" -import { LabelPosition, SlotShape, SlotType } from "./draw" -import { LiteGraph } from "./litegraph" -import { getCentre } from "./measure" -import { LinkDirection, RenderShape } from "./types/globalEnums" -import { ISerialisableNodeInput, ISerialisableNodeOutput } from "./types/serialisation" +import { LabelPosition, SlotShape, SlotType } from "@/draw" +import { LiteGraph } from "@/litegraph" +import { getCentre } from "@/measure" +import { LinkDirection, RenderShape } from "@/types/globalEnums" + +import { NodeInputSlot } from "./NodeInputSlot" export interface ConnectionColorContext { default_connection_color: { @@ -19,7 +18,7 @@ export interface ConnectionColorContext { default_connection_color_byTypeOff: Dictionary } -interface IDrawOptions { +export interface IDrawOptions { colorContext: ConnectionColorContext labelPosition?: LabelPosition lowQuality?: boolean @@ -27,58 +26,6 @@ interface IDrawOptions { highlight?: boolean } -type CommonIoSlotProps = SharedIntersection - -export function shallowCloneCommonProps(slot: CommonIoSlotProps): CommonIoSlotProps { - const { color_off, color_on, dir, label, localized_name, locked, name, nameLocked, removable, shape, type } = slot - return { color_off, color_on, dir, label, localized_name, locked, name, nameLocked, removable, shape, type } -} - -export function inputAsSerialisable(slot: INodeInputSlot): ISerialisableNodeInput { - const { link } = slot - const widgetOrPos = slot.widget - ? { widget: { name: slot.widget.name } } - : { pos: slot.pos } - - return { - ...shallowCloneCommonProps(slot), - ...widgetOrPos, - link, - } -} - -export function outputAsSerialisable(slot: INodeOutputSlot & { widget?: IWidget }): ISerialisableNodeOutput { - const { pos, slot_index, links, widget } = slot - // Output widgets do not exist in Litegraph; this is a temporary downstream workaround. - const outputWidget = widget - ? { widget: { name: widget.name } } - : null - - return { - ...shallowCloneCommonProps(slot), - ...outputWidget, - pos, - slot_index, - links, - } -} - -export function toNodeSlotClass(slot: INodeInputSlot | INodeOutputSlot): NodeInputSlot | NodeOutputSlot { - if (slot instanceof NodeInputSlot || slot instanceof NodeOutputSlot) return slot - - return "link" in slot - ? new NodeInputSlot(slot) - : new NodeOutputSlot(slot) -} - -/** - * Type guard: Whether this input slot is attached to a widget. - * @param slot The slot to check. - */ -export function isWidgetInputSlot(slot: INodeInputSlot): slot is IWidgetInputSlot { - return !!slot.widget -} - export abstract class NodeSlot implements INodeSlot { name: string localized_name?: string @@ -291,86 +238,3 @@ export abstract class NodeSlot implements INodeSlot { ctx.fillStyle = originalFillStyle } } - -export function isINodeInputSlot(slot: INodeSlot): slot is INodeInputSlot { - return "link" in slot -} - -export class NodeInputSlot extends NodeSlot implements INodeInputSlot { - link: LinkId | null - - get isWidgetInputSlot(): boolean { - return !!this.widget - } - - constructor(slot: OptionalProps) { - super(slot) - this.link = slot.link - } - - override isConnected(): boolean { - return this.link != null - } - - override isValidTarget(fromSlot: INodeInputSlot | INodeOutputSlot): boolean { - return "links" in fromSlot && LiteGraph.isValidConnection(this.type, fromSlot.type) - } - - override draw(ctx: CanvasRenderingContext2D, options: Omit) { - const originalTextAlign = ctx.textAlign - ctx.textAlign = "left" - - super.draw(ctx, { - ...options, - labelPosition: LabelPosition.Right, - doStroke: false, - }) - - ctx.textAlign = originalTextAlign - } -} - -export function isINodeOutputSlot(slot: INodeSlot): slot is INodeOutputSlot { - return "links" in slot -} - -export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot { - links: LinkId[] | null - _data?: unknown - slot_index?: number - - get isWidgetInputSlot(): false { - return false - } - - constructor(slot: OptionalProps) { - super(slot) - this.links = slot.links - this._data = slot._data - this.slot_index = slot.slot_index - } - - override isValidTarget(fromSlot: INodeInputSlot | INodeOutputSlot): boolean { - return "link" in fromSlot && LiteGraph.isValidConnection(this.type, fromSlot.type) - } - - override isConnected(): boolean { - return this.links != null && this.links.length > 0 - } - - override draw(ctx: CanvasRenderingContext2D, options: Omit) { - const originalTextAlign = ctx.textAlign - const originalStrokeStyle = ctx.strokeStyle - ctx.textAlign = "right" - ctx.strokeStyle = "black" - - super.draw(ctx, { - ...options, - labelPosition: LabelPosition.Left, - doStroke: true, - }) - - ctx.textAlign = originalTextAlign - ctx.strokeStyle = originalStrokeStyle - } -} diff --git a/src/node/slotUtils.ts b/src/node/slotUtils.ts new file mode 100644 index 000000000..f151175da --- /dev/null +++ b/src/node/slotUtils.ts @@ -0,0 +1,67 @@ +import type { IWidgetInputSlot, SharedIntersection } from "@/interfaces" +import type { INodeInputSlot, INodeOutputSlot, INodeSlot, IWidget } from "@/litegraph" +import type { ISerialisableNodeInput, ISerialisableNodeOutput } from "@/types/serialisation" + +import { NodeInputSlot } from "./NodeInputSlot" +import { NodeOutputSlot } from "./NodeOutputSlot" + +type CommonIoSlotProps = SharedIntersection + +export function shallowCloneCommonProps(slot: CommonIoSlotProps): CommonIoSlotProps { + const { color_off, color_on, dir, label, localized_name, locked, name, nameLocked, removable, shape, type } = slot + return { color_off, color_on, dir, label, localized_name, locked, name, nameLocked, removable, shape, type } +} + +export function inputAsSerialisable(slot: INodeInputSlot): ISerialisableNodeInput { + const { link } = slot + const widgetOrPos = slot.widget + ? { widget: { name: slot.widget.name } } + : { pos: slot.pos } + + return { + ...shallowCloneCommonProps(slot), + ...widgetOrPos, + link, + } +} + +export function outputAsSerialisable(slot: INodeOutputSlot & { widget?: IWidget }): ISerialisableNodeOutput { + const { pos, slot_index, links, widget } = slot + // Output widgets do not exist in Litegraph; this is a temporary downstream workaround. + const outputWidget = widget + ? { widget: { name: widget.name } } + : null + + return { + ...shallowCloneCommonProps(slot), + ...outputWidget, + pos, + slot_index, + links, + } +} + +export function isINodeInputSlot(slot: INodeSlot): slot is INodeInputSlot { + return "link" in slot +} + +export function isINodeOutputSlot(slot: INodeSlot): slot is INodeOutputSlot { + return "links" in slot +} + +/** + * Type guard: Whether this input slot is attached to a widget. + * @param slot The slot to check. + */ + +export function isWidgetInputSlot(slot: INodeInputSlot): slot is IWidgetInputSlot { + return !!slot.widget +} + +export function toNodeSlotClass(slot: INodeInputSlot | INodeOutputSlot): NodeInputSlot | NodeOutputSlot { + if (slot instanceof NodeInputSlot || slot instanceof NodeOutputSlot) return slot + + return "link" in slot + ? new NodeInputSlot(slot) + : new NodeOutputSlot(slot) +} diff --git a/test/LGraphNode.test.ts b/test/LGraphNode.test.ts index f2967a567..b75701dbf 100644 --- a/test/LGraphNode.test.ts +++ b/test/LGraphNode.test.ts @@ -2,7 +2,8 @@ import { describe, expect } from "vitest" import { LGraphNode, LiteGraph } from "@/litegraph" import { LGraph } from "@/litegraph" -import { NodeInputSlot, NodeOutputSlot } from "@/NodeSlot" +import { NodeInputSlot } from "@/node/NodeInputSlot" +import { NodeOutputSlot } from "@/node/NodeOutputSlot" import { test } from "./testExtensions" diff --git a/test/NodeSlot.test.ts b/test/NodeSlot.test.ts index b8333d6ca..a4e53e6c2 100644 --- a/test/NodeSlot.test.ts +++ b/test/NodeSlot.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest" import { INodeInputSlot, INodeOutputSlot } from "@/interfaces" -import { inputAsSerialisable, outputAsSerialisable } from "@/NodeSlot" +import { inputAsSerialisable, outputAsSerialisable } from "@/node/slotUtils" describe("NodeSlot", () => { describe("inputAsSerialisable", () => {