From f7be9157e02d2baeeeeb050bb6b0c5dc50c60c2b Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Thu, 6 Mar 2025 13:23:58 -0500 Subject: [PATCH] Dom widget store (#2899) --- src/components/graph/DomWidgets.vue | 22 ++++++++ src/components/graph/GraphCanvas.vue | 2 + src/components/graph/widgets/DomWidget.vue | 19 +++++++ src/scripts/domWidget.ts | 63 ++++++++++++++-------- src/services/litegraphService.ts | 1 + src/stores/domWidgetStore.ts | 36 +++++++++++++ src/utils/formatUtil.ts | 6 +++ 7 files changed, 126 insertions(+), 23 deletions(-) create mode 100644 src/components/graph/DomWidgets.vue create mode 100644 src/components/graph/widgets/DomWidget.vue create mode 100644 src/stores/domWidgetStore.ts diff --git a/src/components/graph/DomWidgets.vue b/src/components/graph/DomWidgets.vue new file mode 100644 index 000000000..3e25646c6 --- /dev/null +++ b/src/components/graph/DomWidgets.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 5961070d5..1c6eb06e6 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -33,6 +33,7 @@ + diff --git a/src/scripts/domWidget.ts b/src/scripts/domWidget.ts index 72b87ac93..f0b2d78f0 100644 --- a/src/scripts/domWidget.ts +++ b/src/scripts/domWidget.ts @@ -1,5 +1,5 @@ import { LGraphCanvas, LGraphNode } from '@comfyorg/litegraph' -import type { Size, Vector4 } from '@comfyorg/litegraph' +import type { Vector4 } from '@comfyorg/litegraph' import type { ISerialisedNode } from '@comfyorg/litegraph/dist/types/serialisation' import type { ICustomWidget, @@ -8,7 +8,9 @@ import type { import { useChainCallback } from '@/composables/functional/useChainCallback' import { app } from '@/scripts/app' +import { useDomWidgetStore } from '@/stores/domWidgetStore' import { useSettingStore } from '@/stores/settingStore' +import { generateRandomSuffix } from '@/utils/formatUtil' interface Rect { height: number @@ -19,6 +21,7 @@ interface Rect { export interface DOMWidget extends ICustomWidget { + // ICustomWidget properties type: 'custom' element: T options: DOMWidgetOptions @@ -30,6 +33,9 @@ export interface DOMWidget */ inputEl?: T callback?: (value: V) => void + // DOMWidget properties + /** The unique ID of the widget. */ + id: string } export interface DOMWidgetOptions< @@ -146,30 +152,35 @@ LGraphCanvas.prototype.computeVisibleNodes = function ( export class DOMWidgetImpl implements DOMWidget { - type: 'custom' - name: string - element: T - options: DOMWidgetOptions + readonly type: 'custom' + readonly name: string + readonly element: T + readonly options: DOMWidgetOptions computedHeight?: number callback?: (value: V) => void - private mouseDownHandler?: (event: MouseEvent) => void - constructor( - name: string, - type: string, - element: T, - options: DOMWidgetOptions = {} - ) { + readonly id: string + mouseDownHandler?: (event: MouseEvent) => void + + constructor(obj: { + id: string + name: string + type: string + element: T + options: DOMWidgetOptions + }) { // @ts-expect-error custom widget type - this.type = type - this.name = name - this.element = element - this.options = options + this.type = obj.type + this.name = obj.name + this.element = obj.element + this.options = obj.options - if (element.blur) { + this.id = obj.id + + if (this.element.blur) { this.mouseDownHandler = (event) => { - if (!element.contains(event.target as HTMLElement)) { - element.blur() + if (!this.element.contains(event.target as HTMLElement)) { + this.element.blur() } } document.addEventListener('mousedown', this.mouseDownHandler) @@ -304,9 +315,6 @@ LGraphNode.prototype.addDOMWidget = function < ): DOMWidget { options = { hideOnZoom: true, selectOn: ['focus', 'click'], ...options } - if (!element.parentElement) { - app.canvasContainer.append(element) - } element.hidden = true element.style.display = 'none' @@ -317,7 +325,14 @@ LGraphNode.prototype.addDOMWidget = function < element.title = tooltip } - const widget = new DOMWidgetImpl(name, type, element, options) + const widget = new DOMWidgetImpl({ + id: `${this.id}:${name}:${generateRandomSuffix()}`, + name, + type, + element, + options + }) + // Workaround for https://github.com/Comfy-Org/ComfyUI_frontend/issues/2493 // Some custom nodes are explicitly expecting getter and setter of `value` // property to be on instance instead of prototype. @@ -374,5 +389,7 @@ LGraphNode.prototype.addDOMWidget = function < options.afterResize?.call(widget, this) }) + useDomWidgetStore().registerWidget(widget) + return widget } diff --git a/src/services/litegraphService.ts b/src/services/litegraphService.ts index f5076e633..2fcdc91f9 100644 --- a/src/services/litegraphService.ts +++ b/src/services/litegraphService.ts @@ -424,6 +424,7 @@ export const useLitegraphService = () => { } else { const host = createImageHost(this) this.setSizeForImage(true) + // @ts-expect-error host is not a standard DOM widget option. const widget = this.addDOMWidget( ANIM_PREVIEW_WIDGET, 'img', diff --git a/src/stores/domWidgetStore.ts b/src/stores/domWidgetStore.ts new file mode 100644 index 000000000..e0e005418 --- /dev/null +++ b/src/stores/domWidgetStore.ts @@ -0,0 +1,36 @@ +/** + * Stores all DOM widgets that are used in the canvas. + */ +import { defineStore } from 'pinia' +import { markRaw, ref } from 'vue' + +import type { DOMWidget } from '@/scripts/domWidget' + +export const useDomWidgetStore = defineStore('domWidget', () => { + // Map to reference actual widget instances + // Widgets are stored as raw values to avoid reactivity issues + const widgetInstances = ref( + new Map>() + ) + + // Register a widget with the store + const registerWidget = ( + widget: DOMWidget + ) => { + widgetInstances.value.set( + widget.id, + markRaw(widget as unknown as DOMWidget) + ) + } + + // Unregister a widget from the store + const unregisterWidget = (widgetId: string) => { + widgetInstances.value.delete(widgetId) + } + + return { + widgetInstances, + registerWidget, + unregisterWidget + } +}) diff --git a/src/utils/formatUtil.ts b/src/utils/formatUtil.ts index 1477e46ff..e9e7b6f4f 100644 --- a/src/utils/formatUtil.ts +++ b/src/utils/formatUtil.ts @@ -312,3 +312,9 @@ export const paramsToCacheKey = (params: unknown): string => { return String(params) } + +/** + * Generates a random 4-character string to use as a unique suffix + */ +export const generateRandomSuffix = (): string => + Math.random().toString(36).substring(2, 6)