Fix all unknown slot props are serialised (#732)

Ensures only specified properties are cloned for serialisation.
This commit is contained in:
filtered
2025-03-10 23:20:34 +11:00
committed by GitHub
parent 4589938ceb
commit e632f5c69b
5 changed files with 49 additions and 27 deletions

View File

@@ -32,7 +32,7 @@ import { LGraphCanvas } from "./LGraphCanvas"
import { type LGraphNodeConstructor, LiteGraph } from "./litegraph"
import { LLink } from "./LLink"
import { createBounds, isInRect, isInRectangle, isPointInRect, snapPoint } from "./measure"
import { ConnectionColorContext, isINodeInputSlot, isWidgetInputSlot, NodeInputSlot, NodeOutputSlot, serializeSlot, toNodeSlotClass } from "./NodeSlot"
import { ConnectionColorContext, inputAsSerialisable, isINodeInputSlot, isWidgetInputSlot, NodeInputSlot, NodeOutputSlot, outputAsSerialisable, toNodeSlotClass } from "./NodeSlot"
import {
LGraphEventMode,
NodeSlotType,
@@ -703,8 +703,8 @@ export class LGraphNode implements Positionable, IPinnable, IColorable {
if (this.constructor === LGraphNode && this.last_serialization)
return this.last_serialization
if (this.inputs) o.inputs = this.inputs.map(input => serializeSlot(input))
if (this.outputs) o.outputs = this.outputs.map(output => serializeSlot(output))
if (this.inputs) o.inputs = this.inputs.map(input => inputAsSerialisable(input))
if (this.outputs) o.outputs = this.outputs.map(output => outputAsSerialisable(output))
if (this.title && this.title != this.constructor.title) o.title = this.title

View File

@@ -1,13 +1,12 @@
import type { CanvasColour, Dictionary, INodeInputSlot, INodeOutputSlot, INodeSlot, ISlotType, IWidgetInputSlot, Point } from "./interfaces"
import type { CanvasColour, Dictionary, INodeInputSlot, INodeOutputSlot, INodeSlot, ISlotType, IWidgetInputSlot, Point, SharedIntersection } from "./interfaces"
import type { LinkId } from "./LLink"
import type { IWidget } from "./types/widgets"
import { LabelPosition, SlotShape, SlotType } from "./draw"
import { LiteGraph } from "./litegraph"
import { LinkDirection, RenderShape } from "./types/globalEnums"
import { ISerialisedNodeOutputSlot } from "./types/serialisation"
import { ISerialisedNodeInputSlot } from "./types/serialisation"
import { omitBy } from "./utils/object"
import { ISerialisableNodeOutput } from "./types/serialisation"
import { ISerialisableNodeInput } from "./types/serialisation"
export interface ConnectionColorContext {
default_connection_color: {
@@ -31,16 +30,32 @@ interface IDrawOptions {
highlight?: boolean
}
export function serializeSlot(slot: INodeInputSlot): ISerialisedNodeInputSlot
export function serializeSlot(slot: INodeOutputSlot): ISerialisedNodeOutputSlot
export function serializeSlot(slot: INodeInputSlot | INodeOutputSlot): ISerialisedNodeInputSlot | ISerialisedNodeOutputSlot {
return omitBy({
...slot,
_layoutElement: undefined,
_data: undefined,
pos: isWidgetInputSlot(slot) ? undefined : slot.pos,
widget: isWidgetInputSlot(slot) ? { name: slot.widget.name } : undefined,
}, value => value === undefined) as ISerialisedNodeInputSlot | ISerialisedNodeOutputSlot
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 widgetInputProps = slot.widget
? { widget: { name: slot.widget.name } }
: { pos: slot.pos }
return {
...shallowCloneCommonProps(slot),
...widgetInputProps,
link: slot.link,
}
}
export function outputAsSerialisable(slot: INodeOutputSlot): ISerialisableNodeOutput {
return {
...shallowCloneCommonProps(slot),
pos: slot.pos,
slot_index: slot.slot_index,
links: slot.links,
}
}
export function toNodeSlotClass(slot: INodeSlot): NodeSlot {

View File

@@ -22,6 +22,13 @@ export type WhenNullish<T, Result> = T & {} | (T extends null ? Result : T exten
/** A type with each of the {@link Properties} made optional. */
export type OptionalProps<T, Properties extends keyof T> = Omit<T, Properties> & { [K in Properties]?: T[K] }
/** Bitwise AND intersection of two types; returns a new, non-union type that includes only properties that exist on both types. */
export type SharedIntersection<T1, T2> = {
[P in keyof T1 as P extends keyof T2 ? P : never]: T1[P]
} & {
[P in keyof T2 as P extends keyof T1 ? P : never]: T2[P]
}
export type CanvasColour = string | CanvasGradient | CanvasPattern
/** An object containing a set of child objects */

View File

@@ -40,10 +40,10 @@ export interface SerialisableGraph {
extra?: Record<any, any>
}
export type ISerialisedNodeInputSlot = Omit<INodeInputSlot, "_layoutElement" | "widget"> & {
export type ISerialisableNodeInput = Omit<INodeInputSlot, "_layoutElement" | "widget"> & {
widget?: { name: string }
}
export type ISerialisedNodeOutputSlot = Omit<INodeOutputSlot, "_layoutElement" | "_data">
export type ISerialisableNodeOutput = Omit<INodeOutputSlot, "_layoutElement" | "_data">
/** Serialised LGraphNode */
export interface ISerialisedNode {
@@ -55,8 +55,8 @@ export interface ISerialisedNode {
flags?: INodeFlags
order?: number
mode?: number
outputs?: ISerialisedNodeOutputSlot[]
inputs?: ISerialisedNodeInputSlot[]
outputs?: ISerialisableNodeOutput[]
inputs?: ISerialisableNodeInput[]
properties?: Dictionary<NodeProperty | undefined>
shape?: RenderShape
boxcolor?: string

View File

@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest"
import { INodeInputSlot, INodeOutputSlot } from "@/interfaces"
import { serializeSlot } from "@/NodeSlot"
import { inputAsSerialisable, outputAsSerialisable } from "@/NodeSlot"
describe("NodeSlot", () => {
describe("serializeSlot", () => {
describe("inputAsSerialisable", () => {
it("removes _data from serialized slot", () => {
const slot: INodeOutputSlot = {
_data: "test data",
@@ -12,7 +12,7 @@ describe("NodeSlot", () => {
type: "STRING",
links: [],
}
const serialized = serializeSlot(slot)
const serialized = outputAsSerialisable(slot)
expect(serialized).not.toHaveProperty("_data")
})
@@ -32,7 +32,7 @@ describe("NodeSlot", () => {
},
}
const serialized = serializeSlot(widgetInputSlot)
const serialized = inputAsSerialisable(widgetInputSlot)
expect(serialized).not.toHaveProperty("pos")
})
@@ -43,7 +43,7 @@ describe("NodeSlot", () => {
pos: [10, 20],
link: null,
}
const serialized = serializeSlot(normalSlot)
const serialized = inputAsSerialisable(normalSlot)
expect(serialized).toHaveProperty("pos")
})
@@ -62,7 +62,7 @@ describe("NodeSlot", () => {
},
}
const serialized = serializeSlot(widgetInputSlot)
const serialized = inputAsSerialisable(widgetInputSlot)
expect(serialized.widget).toEqual({ name: "test-widget" })
expect(serialized.widget).not.toHaveProperty("type")
expect(serialized.widget).not.toHaveProperty("value")