mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-23 08:14:06 +00:00
refactor: migrate ES private fields to TypeScript private for Vue Proxy compatibility (#8440)
## 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 <amp@ampcode.com>
This commit is contained in:
@@ -54,7 +54,7 @@ class ConversionContext {
|
||||
/** Reroutes that has at least a valid link pass through it */
|
||||
validReroutes: Set<Reroute>
|
||||
|
||||
#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,
|
||||
|
||||
Reference in New Issue
Block a user