mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 10:42:44 +00:00
Dom widget store (#2899)
This commit is contained in:
22
src/components/graph/DomWidgets.vue
Normal file
22
src/components/graph/DomWidgets.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<DomWidget v-for="widget in widgets" :key="widget.id" :widget="widget" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import DomWidget from '@/components/graph/widgets/DomWidget.vue'
|
||||||
|
import { DOMWidget } from '@/scripts/domWidget'
|
||||||
|
import { useDomWidgetStore } from '@/stores/domWidgetStore'
|
||||||
|
|
||||||
|
const domWidgetStore = useDomWidgetStore()
|
||||||
|
const widgets = computed(() =>
|
||||||
|
Array.from(
|
||||||
|
domWidgetStore.widgetInstances.values() as Iterable<
|
||||||
|
DOMWidget<HTMLElement, object | string>
|
||||||
|
>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
</script>
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
</SelectionOverlay>
|
</SelectionOverlay>
|
||||||
<NodeTooltip v-if="tooltipEnabled" />
|
<NodeTooltip v-if="tooltipEnabled" />
|
||||||
<NodeBadge />
|
<NodeBadge />
|
||||||
|
<DomWidgets />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -40,6 +41,7 @@ import { computed, onMounted, ref, watch, watchEffect } from 'vue'
|
|||||||
|
|
||||||
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
|
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
|
||||||
import BottomPanel from '@/components/bottomPanel/BottomPanel.vue'
|
import BottomPanel from '@/components/bottomPanel/BottomPanel.vue'
|
||||||
|
import DomWidgets from '@/components/graph/DomWidgets.vue'
|
||||||
import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue'
|
import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue'
|
||||||
import NodeBadge from '@/components/graph/NodeBadge.vue'
|
import NodeBadge from '@/components/graph/NodeBadge.vue'
|
||||||
import NodeTooltip from '@/components/graph/NodeTooltip.vue'
|
import NodeTooltip from '@/components/graph/NodeTooltip.vue'
|
||||||
|
|||||||
19
src/components/graph/widgets/DomWidget.vue
Normal file
19
src/components/graph/widgets/DomWidget.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="widgetElement" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
import type { DOMWidget } from '@/scripts/domWidget'
|
||||||
|
|
||||||
|
const { widget } = defineProps<{
|
||||||
|
widget: DOMWidget<HTMLElement, any>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const widgetElement = ref<HTMLElement>()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
widgetElement.value.appendChild(widget.element)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LGraphCanvas, LGraphNode } from '@comfyorg/litegraph'
|
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 { ISerialisedNode } from '@comfyorg/litegraph/dist/types/serialisation'
|
||||||
import type {
|
import type {
|
||||||
ICustomWidget,
|
ICustomWidget,
|
||||||
@@ -8,7 +8,9 @@ import type {
|
|||||||
|
|
||||||
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||||
import { app } from '@/scripts/app'
|
import { app } from '@/scripts/app'
|
||||||
|
import { useDomWidgetStore } from '@/stores/domWidgetStore'
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
import { useSettingStore } from '@/stores/settingStore'
|
||||||
|
import { generateRandomSuffix } from '@/utils/formatUtil'
|
||||||
|
|
||||||
interface Rect {
|
interface Rect {
|
||||||
height: number
|
height: number
|
||||||
@@ -19,6 +21,7 @@ interface Rect {
|
|||||||
|
|
||||||
export interface DOMWidget<T extends HTMLElement, V extends object | string>
|
export interface DOMWidget<T extends HTMLElement, V extends object | string>
|
||||||
extends ICustomWidget<T> {
|
extends ICustomWidget<T> {
|
||||||
|
// ICustomWidget properties
|
||||||
type: 'custom'
|
type: 'custom'
|
||||||
element: T
|
element: T
|
||||||
options: DOMWidgetOptions<T, V>
|
options: DOMWidgetOptions<T, V>
|
||||||
@@ -30,6 +33,9 @@ export interface DOMWidget<T extends HTMLElement, V extends object | string>
|
|||||||
*/
|
*/
|
||||||
inputEl?: T
|
inputEl?: T
|
||||||
callback?: (value: V) => void
|
callback?: (value: V) => void
|
||||||
|
// DOMWidget properties
|
||||||
|
/** The unique ID of the widget. */
|
||||||
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DOMWidgetOptions<
|
export interface DOMWidgetOptions<
|
||||||
@@ -146,30 +152,35 @@ LGraphCanvas.prototype.computeVisibleNodes = function (
|
|||||||
export class DOMWidgetImpl<T extends HTMLElement, V extends object | string>
|
export class DOMWidgetImpl<T extends HTMLElement, V extends object | string>
|
||||||
implements DOMWidget<T, V>
|
implements DOMWidget<T, V>
|
||||||
{
|
{
|
||||||
type: 'custom'
|
readonly type: 'custom'
|
||||||
name: string
|
readonly name: string
|
||||||
element: T
|
readonly element: T
|
||||||
options: DOMWidgetOptions<T, V>
|
readonly options: DOMWidgetOptions<T, V>
|
||||||
computedHeight?: number
|
computedHeight?: number
|
||||||
callback?: (value: V) => void
|
callback?: (value: V) => void
|
||||||
private mouseDownHandler?: (event: MouseEvent) => void
|
|
||||||
|
|
||||||
constructor(
|
readonly id: string
|
||||||
name: string,
|
mouseDownHandler?: (event: MouseEvent) => void
|
||||||
type: string,
|
|
||||||
element: T,
|
constructor(obj: {
|
||||||
options: DOMWidgetOptions<T, V> = {}
|
id: string
|
||||||
) {
|
name: string
|
||||||
|
type: string
|
||||||
|
element: T
|
||||||
|
options: DOMWidgetOptions<T, V>
|
||||||
|
}) {
|
||||||
// @ts-expect-error custom widget type
|
// @ts-expect-error custom widget type
|
||||||
this.type = type
|
this.type = obj.type
|
||||||
this.name = name
|
this.name = obj.name
|
||||||
this.element = element
|
this.element = obj.element
|
||||||
this.options = options
|
this.options = obj.options
|
||||||
|
|
||||||
if (element.blur) {
|
this.id = obj.id
|
||||||
|
|
||||||
|
if (this.element.blur) {
|
||||||
this.mouseDownHandler = (event) => {
|
this.mouseDownHandler = (event) => {
|
||||||
if (!element.contains(event.target as HTMLElement)) {
|
if (!this.element.contains(event.target as HTMLElement)) {
|
||||||
element.blur()
|
this.element.blur()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener('mousedown', this.mouseDownHandler)
|
document.addEventListener('mousedown', this.mouseDownHandler)
|
||||||
@@ -304,9 +315,6 @@ LGraphNode.prototype.addDOMWidget = function <
|
|||||||
): DOMWidget<T, V> {
|
): DOMWidget<T, V> {
|
||||||
options = { hideOnZoom: true, selectOn: ['focus', 'click'], ...options }
|
options = { hideOnZoom: true, selectOn: ['focus', 'click'], ...options }
|
||||||
|
|
||||||
if (!element.parentElement) {
|
|
||||||
app.canvasContainer.append(element)
|
|
||||||
}
|
|
||||||
element.hidden = true
|
element.hidden = true
|
||||||
element.style.display = 'none'
|
element.style.display = 'none'
|
||||||
|
|
||||||
@@ -317,7 +325,14 @@ LGraphNode.prototype.addDOMWidget = function <
|
|||||||
element.title = tooltip
|
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
|
// Workaround for https://github.com/Comfy-Org/ComfyUI_frontend/issues/2493
|
||||||
// Some custom nodes are explicitly expecting getter and setter of `value`
|
// Some custom nodes are explicitly expecting getter and setter of `value`
|
||||||
// property to be on instance instead of prototype.
|
// property to be on instance instead of prototype.
|
||||||
@@ -374,5 +389,7 @@ LGraphNode.prototype.addDOMWidget = function <
|
|||||||
options.afterResize?.call(widget, this)
|
options.afterResize?.call(widget, this)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useDomWidgetStore().registerWidget(widget)
|
||||||
|
|
||||||
return widget
|
return widget
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -424,6 +424,7 @@ export const useLitegraphService = () => {
|
|||||||
} else {
|
} else {
|
||||||
const host = createImageHost(this)
|
const host = createImageHost(this)
|
||||||
this.setSizeForImage(true)
|
this.setSizeForImage(true)
|
||||||
|
// @ts-expect-error host is not a standard DOM widget option.
|
||||||
const widget = this.addDOMWidget(
|
const widget = this.addDOMWidget(
|
||||||
ANIM_PREVIEW_WIDGET,
|
ANIM_PREVIEW_WIDGET,
|
||||||
'img',
|
'img',
|
||||||
|
|||||||
36
src/stores/domWidgetStore.ts
Normal file
36
src/stores/domWidgetStore.ts
Normal file
@@ -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<string, DOMWidget<HTMLElement, object | string>>()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register a widget with the store
|
||||||
|
const registerWidget = <T extends HTMLElement, V extends object | string>(
|
||||||
|
widget: DOMWidget<T, V>
|
||||||
|
) => {
|
||||||
|
widgetInstances.value.set(
|
||||||
|
widget.id,
|
||||||
|
markRaw(widget as unknown as DOMWidget<HTMLElement, object | string>)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister a widget from the store
|
||||||
|
const unregisterWidget = (widgetId: string) => {
|
||||||
|
widgetInstances.value.delete(widgetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
widgetInstances,
|
||||||
|
registerWidget,
|
||||||
|
unregisterWidget
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -312,3 +312,9 @@ export const paramsToCacheKey = (params: unknown): string => {
|
|||||||
|
|
||||||
return String(params)
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user