[Refactor] Split NodeSlot out to separate files (#960)

Split files only; no code changes.  New files moved to `/node`.
This commit is contained in:
filtered
2025-04-24 00:43:55 +10:00
committed by GitHub
parent cbbbb9c694
commit d70260615b
8 changed files with 170 additions and 148 deletions

View File

@@ -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 {

View File

@@ -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,

40
src/node/NodeInputSlot.ts Normal file
View File

@@ -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<INodeInputSlot, "boundingRect">) {
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<IDrawOptions, "doStroke" | "labelPosition">) {
const originalTextAlign = ctx.textAlign
ctx.textAlign = "left"
super.draw(ctx, {
...options,
labelPosition: LabelPosition.Right,
doStroke: false,
})
ctx.textAlign = originalTextAlign
}
}

View File

@@ -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<INodeOutputSlot, "boundingRect">) {
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<IDrawOptions, "doStroke" | "labelPosition">) {
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
}
}

View File

@@ -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<CanvasColour>
}
interface IDrawOptions {
export interface IDrawOptions {
colorContext: ConnectionColorContext
labelPosition?: LabelPosition
lowQuality?: boolean
@@ -27,58 +26,6 @@ interface IDrawOptions {
highlight?: boolean
}
type CommonIoSlotProps = SharedIntersection<ISerialisableNodeInput, ISerialisableNodeOutput>
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<INodeInputSlot, "boundingRect">) {
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<IDrawOptions, "doStroke" | "labelPosition">) {
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<INodeOutputSlot, "boundingRect">) {
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<IDrawOptions, "doStroke" | "labelPosition">) {
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
}
}

67
src/node/slotUtils.ts Normal file
View File

@@ -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<ISerialisableNodeInput, ISerialisableNodeOutput>
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)
}

View File

@@ -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"

View File

@@ -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", () => {