mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-07 16:40:05 +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:
@@ -294,7 +294,7 @@ export class PromptExecutionError extends Error {
|
||||
}
|
||||
|
||||
export class ComfyApi extends EventTarget {
|
||||
#registered = new Set()
|
||||
private _registered = new Set()
|
||||
api_host: string
|
||||
api_base: string
|
||||
/**
|
||||
@@ -451,7 +451,7 @@ export class ComfyApi extends EventTarget {
|
||||
) {
|
||||
// Type assertion: strictFunctionTypes. So long as we emit events in a type-safe fashion, this is safe.
|
||||
super.addEventListener(type, callback as EventListener, options)
|
||||
this.#registered.add(type)
|
||||
this._registered.add(type)
|
||||
}
|
||||
|
||||
override removeEventListener<TEvent extends keyof ApiEvents>(
|
||||
@@ -492,7 +492,7 @@ export class ComfyApi extends EventTarget {
|
||||
/**
|
||||
* Poll status for colab and other things that don't support websockets.
|
||||
*/
|
||||
#pollQueue() {
|
||||
private _pollQueue() {
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const resp = await this.fetchApi('/prompt')
|
||||
@@ -568,7 +568,7 @@ export class ComfyApi extends EventTarget {
|
||||
this.socket.addEventListener('error', () => {
|
||||
if (this.socket) this.socket.close()
|
||||
if (!isReconnect && !opened) {
|
||||
this.#pollQueue()
|
||||
this._pollQueue()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -691,7 +691,7 @@ export class ComfyApi extends EventTarget {
|
||||
)
|
||||
break
|
||||
default:
|
||||
if (this.#registered.has(msg.type)) {
|
||||
if (this._registered.has(msg.type)) {
|
||||
// Fallback for custom types - calls super direct.
|
||||
super.dispatchEvent(
|
||||
new CustomEvent(msg.type, { detail: msg.data })
|
||||
@@ -956,7 +956,7 @@ export class ComfyApi extends EventTarget {
|
||||
* @param {*} type The endpoint to post to
|
||||
* @param {*} body Optional POST data
|
||||
*/
|
||||
async #postItem(type: string, body?: Record<string, unknown>) {
|
||||
private async _postItem(type: string, body?: Record<string, unknown>) {
|
||||
try {
|
||||
await this.fetchApi('/' + type, {
|
||||
method: 'POST',
|
||||
@@ -976,7 +976,7 @@ export class ComfyApi extends EventTarget {
|
||||
* @param {number} id The id of the item to delete
|
||||
*/
|
||||
async deleteItem(type: string, id: string) {
|
||||
await this.#postItem(type, { delete: [id] })
|
||||
await this._postItem(type, { delete: [id] })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -984,7 +984,7 @@ export class ComfyApi extends EventTarget {
|
||||
* @param {string} type The type of list to clear, queue or history
|
||||
*/
|
||||
async clearItems(type: string) {
|
||||
await this.#postItem(type, { clear: true })
|
||||
await this._postItem(type, { clear: true })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -993,7 +993,7 @@ export class ComfyApi extends EventTarget {
|
||||
* @param {string | null} [runningPromptId] Optional Running Prompt ID to interrupt
|
||||
*/
|
||||
async interrupt(runningPromptId: string | null) {
|
||||
await this.#postItem(
|
||||
await this._postItem(
|
||||
'interrupt',
|
||||
runningPromptId ? { prompt_id: runningPromptId } : undefined
|
||||
)
|
||||
|
||||
@@ -241,17 +241,17 @@ function dragElement(dragEl): () => void {
|
||||
}
|
||||
|
||||
class ComfyList {
|
||||
#type
|
||||
#text
|
||||
#reverse
|
||||
private _type
|
||||
private _text
|
||||
private _reverse
|
||||
element: HTMLDivElement
|
||||
button?: HTMLButtonElement
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
constructor(text, type?, reverse?) {
|
||||
this.#text = text
|
||||
this.#type = type || text.toLowerCase()
|
||||
this.#reverse = reverse || false
|
||||
this._text = text
|
||||
this._type = type || text.toLowerCase()
|
||||
this._reverse = reverse || false
|
||||
this.element = $el('div.comfy-list') as HTMLDivElement
|
||||
this.element.style.display = 'none'
|
||||
}
|
||||
@@ -261,7 +261,7 @@ class ComfyList {
|
||||
}
|
||||
|
||||
async load() {
|
||||
const items = await api.getItems(this.#type)
|
||||
const items = await api.getItems(this._type)
|
||||
this.element.replaceChildren(
|
||||
...Object.keys(items).flatMap((section) => [
|
||||
$el('h4', {
|
||||
@@ -269,12 +269,12 @@ class ComfyList {
|
||||
}),
|
||||
$el('div.comfy-list-items', [
|
||||
// @ts-expect-error fixme ts strict error
|
||||
...(this.#reverse ? items[section].reverse() : items[section]).map(
|
||||
...(this._reverse ? items[section].reverse() : items[section]).map(
|
||||
(item: LegacyQueueItem) => {
|
||||
// Allow items to specify a custom remove action (e.g. for interrupt current prompt)
|
||||
const removeAction = item.remove ?? {
|
||||
name: 'Delete',
|
||||
cb: () => api.deleteItem(this.#type, item.prompt[1])
|
||||
cb: () => api.deleteItem(this._type, item.prompt[1])
|
||||
}
|
||||
return $el('div', { textContent: item.prompt[0] + ': ' }, [
|
||||
$el('button', {
|
||||
@@ -311,9 +311,9 @@ class ComfyList {
|
||||
]),
|
||||
$el('div.comfy-list-actions', [
|
||||
$el('button', {
|
||||
textContent: 'Clear ' + this.#text,
|
||||
textContent: 'Clear ' + this._text,
|
||||
onclick: async () => {
|
||||
await api.clearItems(this.#type)
|
||||
await api.clearItems(this._type)
|
||||
await this.load()
|
||||
}
|
||||
}),
|
||||
@@ -339,7 +339,7 @@ class ComfyList {
|
||||
hide() {
|
||||
this.element.style.display = 'none'
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.button.textContent = 'View ' + this.#text
|
||||
this.button.textContent = 'View ' + this._text
|
||||
}
|
||||
|
||||
toggle() {
|
||||
|
||||
@@ -6,7 +6,7 @@ type DialogAction<T> = string | { value?: T; text: string }
|
||||
export class ComfyAsyncDialog<
|
||||
T = string | null
|
||||
> extends ComfyDialog<HTMLDialogElement> {
|
||||
#resolve: (value: T | null) => void = () => {}
|
||||
private _resolve: (value: T | null) => void = () => {}
|
||||
|
||||
constructor(actions?: Array<DialogAction<T>>) {
|
||||
super(
|
||||
@@ -30,7 +30,7 @@ export class ComfyAsyncDialog<
|
||||
super.show(html)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.#resolve = resolve
|
||||
this._resolve = resolve
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,12 +43,12 @@ export class ComfyAsyncDialog<
|
||||
this.element.showModal()
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.#resolve = resolve
|
||||
this._resolve = resolve
|
||||
})
|
||||
}
|
||||
|
||||
override close(result: T | null = null) {
|
||||
this.#resolve(result)
|
||||
this._resolve(result)
|
||||
this.element.close()
|
||||
super.close()
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ type ComfyButtonProps = {
|
||||
}
|
||||
|
||||
export class ComfyButton implements ComfyComponent<HTMLElement> {
|
||||
#over = 0
|
||||
#popupOpen = false
|
||||
private _over = 0
|
||||
private _popupOpen = false
|
||||
isOver = false
|
||||
iconElement = $el('i.mdi')
|
||||
contentElement = $el('span')
|
||||
@@ -123,7 +123,7 @@ export class ComfyButton implements ComfyComponent<HTMLElement> {
|
||||
this.element.addEventListener('click', (e) => {
|
||||
if (this.popup) {
|
||||
// we are either a touch device or triggered by click not hover
|
||||
if (!this.#over) {
|
||||
if (!this._over) {
|
||||
this.popup.toggle()
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ export class ComfyButton implements ComfyComponent<HTMLElement> {
|
||||
internalClasses.push('disabled')
|
||||
}
|
||||
if (this.popup) {
|
||||
if (this.#popupOpen) {
|
||||
if (this._popupOpen) {
|
||||
internalClasses.push('popup-open')
|
||||
} else {
|
||||
internalClasses.push('popup-closed')
|
||||
@@ -172,16 +172,16 @@ export class ComfyButton implements ComfyComponent<HTMLElement> {
|
||||
if (mode === 'hover') {
|
||||
for (const el of [this.element, this.popup.element]) {
|
||||
el.addEventListener('mouseenter', () => {
|
||||
this.popup.open = !!++this.#over
|
||||
this.popup.open = !!++this._over
|
||||
})
|
||||
el.addEventListener('mouseleave', () => {
|
||||
this.popup.open = !!--this.#over
|
||||
this.popup.open = !!--this._over
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
popup.addEventListener('change', () => {
|
||||
this.#popupOpen = popup.open
|
||||
this._popupOpen = popup.open
|
||||
this.updateClasses()
|
||||
})
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ export class ComfyPopup extends EventTarget {
|
||||
this.open = prop(this, 'open', false, (v, o) => {
|
||||
if (v === o) return
|
||||
if (v) {
|
||||
this.#show()
|
||||
this._show()
|
||||
} else {
|
||||
this.#hide()
|
||||
this._hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -65,24 +65,24 @@ export class ComfyPopup extends EventTarget {
|
||||
this.open = !this.open
|
||||
}
|
||||
|
||||
#hide() {
|
||||
private _hide() {
|
||||
this.element.classList.remove('open')
|
||||
window.removeEventListener('resize', this.update)
|
||||
window.removeEventListener('click', this.#clickHandler, { capture: true })
|
||||
window.removeEventListener('keydown', this.#escHandler, { capture: true })
|
||||
window.removeEventListener('click', this._clickHandler, { capture: true })
|
||||
window.removeEventListener('keydown', this._escHandler, { capture: true })
|
||||
|
||||
this.dispatchEvent(new CustomEvent('close'))
|
||||
this.dispatchEvent(new CustomEvent('change'))
|
||||
}
|
||||
|
||||
#show() {
|
||||
private _show() {
|
||||
this.element.classList.add('open')
|
||||
this.update()
|
||||
|
||||
window.addEventListener('resize', this.update)
|
||||
window.addEventListener('click', this.#clickHandler, { capture: true })
|
||||
window.addEventListener('click', this._clickHandler, { capture: true })
|
||||
if (this.closeOnEscape) {
|
||||
window.addEventListener('keydown', this.#escHandler, { capture: true })
|
||||
window.addEventListener('keydown', this._escHandler, { capture: true })
|
||||
}
|
||||
|
||||
this.dispatchEvent(new CustomEvent('open'))
|
||||
@@ -90,7 +90,7 @@ export class ComfyPopup extends EventTarget {
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
#escHandler = (e) => {
|
||||
private _escHandler = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
this.open = false
|
||||
e.preventDefault()
|
||||
@@ -99,7 +99,7 @@ export class ComfyPopup extends EventTarget {
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
#clickHandler = (e) => {
|
||||
private _clickHandler = (e) => {
|
||||
/** @type {any} */
|
||||
const target = e.target
|
||||
if (
|
||||
|
||||
@@ -5,11 +5,11 @@ export class ComfyDialog<
|
||||
> extends EventTarget {
|
||||
element: T
|
||||
textElement!: HTMLElement
|
||||
#buttons: HTMLButtonElement[] | null
|
||||
private _buttons: HTMLButtonElement[] | null
|
||||
|
||||
constructor(type = 'div', buttons: HTMLButtonElement[] | null = null) {
|
||||
super()
|
||||
this.#buttons = buttons
|
||||
this._buttons = buttons
|
||||
this.element = $el(type + '.comfy-modal', { parent: document.body }, [
|
||||
$el('div.comfy-modal-content', [
|
||||
$el('p', { $: (p) => (this.textElement = p) }),
|
||||
@@ -20,7 +20,7 @@ export class ComfyDialog<
|
||||
|
||||
createButtons() {
|
||||
return (
|
||||
this.#buttons ?? [
|
||||
this._buttons ?? [
|
||||
$el('button', {
|
||||
type: 'button',
|
||||
textContent: 'Close',
|
||||
|
||||
Reference in New Issue
Block a user