diff --git a/src/components/selectionbar/SubgraphNode.vue b/src/components/selectionbar/SubgraphNode.vue index 6047752d4e..2af45a2458 100644 --- a/src/components/selectionbar/SubgraphNode.vue +++ b/src/components/selectionbar/SubgraphNode.vue @@ -6,14 +6,14 @@ import draggable from 'vuedraggable' import SearchBox from '@/components/common/SearchBox.vue' import SubgraphNodeWidget from '@/components/selectionbar/SubgraphNodeWidget.vue' import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue' +import { + type ProxyWidgetsProperty, + parseProxyWidgets +} from '@/core/schemas/proxyWidget' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' -import { - type ProxyWidgetsProperty, - parseProxyWidgets -} from '@/schemas/proxyWidget' type WidgetItem = [LGraphNode, IBaseWidget] diff --git a/src/core/graph/subgraph/proxyWidget.ts b/src/core/graph/subgraph/proxyWidget.ts index 8992995b2d..d54b1bfc22 100644 --- a/src/core/graph/subgraph/proxyWidget.ts +++ b/src/core/graph/subgraph/proxyWidget.ts @@ -1,7 +1,12 @@ import { useNodeImage } from '@/composables/node/useNodeImage' import { parseProxyWidgets } from '@/core/schemas/proxyWidget' -import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' +import type { + LGraph, + LGraphCanvas, + LGraphNode +} from '@/lib/litegraph/src/litegraph' import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode' +import type { ISerialisedNode } from '@/lib/litegraph/src/types/serialisation' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets.ts' import { disconnectedWidget } from '@/lib/litegraph/src/widgets/DisconnectedWidget' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' @@ -43,8 +48,25 @@ function isProxyWidget(w: IBaseWidget): w is ProxyWidget { return (w as { _overlay?: Overlay })?._overlay?.isProxyWidget ?? false } +export function registerProxyWidgets(canvas: LGraphCanvas) { + //NOTE: canvasStore hasn't been initialized yet + canvas.canvas.addEventListener<'subgraph-opened'>('subgraph-opened', (e) => { + const { subgraph, fromNode } = e.detail + const pw = parseProxyWidgets(fromNode.properties.proxyWidgets) + for (const node of subgraph.nodes) { + for (const widget of node.widgets ?? []) { + widget.promoted = pw.some(([n, w]) => node.id == n && widget.name == w) + } + } + }) + SubgraphNode.prototype.onConfigure = onConfigure +} + const originalOnConfigure = SubgraphNode.prototype.onConfigure -SubgraphNode.prototype.onConfigure = function (serialisedNode) { +const onConfigure = function ( + this: LGraphNode, + serialisedNode: ISerialisedNode +) { if (!this.isSubgraphNode()) throw new Error("Can't add proxyWidgets to non-subgraphNode") @@ -62,13 +84,16 @@ SubgraphNode.prototype.onConfigure = function (serialisedNode) { set: (property: string) => { const parsed = parseProxyWidgets(property) const { deactivateWidget, setWidget } = useDomWidgetStore() - for (const w of this.widgets.filter((w) => isProxyWidget(w))) { - if (w instanceof DOMWidgetImpl) deactivateWidget(w.id) + const isActiveGraph = useCanvasStore().canvas?.graph === this.graph + if (isActiveGraph) { + for (const w of this.widgets.filter((w) => isProxyWidget(w))) { + if (w instanceof DOMWidgetImpl) deactivateWidget(w.id) + } } this.widgets = this.widgets.filter((w) => !isProxyWidget(w)) for (const [nodeId, widgetName] of parsed) { const w = addProxyWidget(this, `${nodeId}`, widgetName) - if (w instanceof DOMWidgetImpl) setWidget(w) + if (isActiveGraph && w instanceof DOMWidgetImpl) setWidget(w) } proxyWidgets = property canvasStore.canvas?.setDirty(true, true) @@ -89,16 +114,18 @@ function addProxyWidget( nodeId, widgetName, graph: subgraphNode.subgraph, - name, - label: name, - isProxyWidget: true, - y: 0, - last_y: undefined, - width: undefined, - computedHeight: undefined, afterQueued: undefined, + computedHeight: undefined, + isProxyWidget: true, + label: name, + last_y: undefined, + name, + node: subgraphNode, onRemove: undefined, - node: subgraphNode + promoted: undefined, + serialize: false, + width: undefined, + y: 0 } return addProxyFromOverlay(subgraphNode, overlay) } diff --git a/src/core/graph/subgraph/proxyWidgetUtils.ts b/src/core/graph/subgraph/proxyWidgetUtils.ts index 76c9591eff..fd5ce784b5 100644 --- a/src/core/graph/subgraph/proxyWidgetUtils.ts +++ b/src/core/graph/subgraph/proxyWidgetUtils.ts @@ -1,13 +1,13 @@ +import { + type ProxyWidgetsProperty, + parseProxyWidgets +} from '@/core/schemas/proxyWidget' import type { IContextMenuValue, LGraphNode } from '@/lib/litegraph/src/litegraph' import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets.ts' -import { - type ProxyWidgetsProperty, - parseProxyWidgets -} from '@/schemas/proxyWidget' import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore' function pushWidgets(node: SubgraphNode, ...widgets: [string, string][]) { diff --git a/src/core/schemas/proxyWidget.ts b/src/core/schemas/proxyWidget.ts index a85e50a2a0..44978fc3bf 100644 --- a/src/core/schemas/proxyWidget.ts +++ b/src/core/schemas/proxyWidget.ts @@ -4,7 +4,7 @@ import { fromZodError } from 'zod-validation-error' import type { NodeProperty } from '@/lib/litegraph/src/LGraphNode' const proxyWidgetsPropertySchema = z.array(z.tuple([z.string(), z.string()])) -type ProxyWidgetsProperty = z.infer +export type ProxyWidgetsProperty = z.infer export function parseProxyWidgets( property: NodeProperty | undefined diff --git a/src/schemas/proxyWidget.ts b/src/schemas/proxyWidget.ts deleted file mode 100644 index ec035797b8..0000000000 --- a/src/schemas/proxyWidget.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { z } from 'zod' -import { fromZodError } from 'zod-validation-error' - -import type { NodeProperty } from '@/lib/litegraph/src/LGraphNode' - -const proxyWidgetsPropertySchema = z.array(z.tuple([z.string(), z.string()])) -export type ProxyWidgetsProperty = z.infer - -export function parseProxyWidgets( - property: NodeProperty | undefined -): ProxyWidgetsProperty { - if (typeof property !== 'string') { - console.error(`Found non-string value for properties.proxyWidgets`) - return [] - } - const parsed = JSON.parse(property) - const result = proxyWidgetsPropertySchema.safeParse(parsed) - if (result.success) return result.data ?? [] - - const error = fromZodError(result.error) - console.error(`Invalid assignment for properties.proxyWidgets:\n${error}`) - return [] -} diff --git a/src/scripts/app.ts b/src/scripts/app.ts index a5869be20f..5870da0122 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -3,6 +3,7 @@ import type { ToastMessageOptions } from 'primevue/toast' import { reactive } from 'vue' import { useCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion' +import { registerProxyWidgets } from '@/core/graph/subgraph/proxyWidget' import { st, t } from '@/i18n' import { LGraph, @@ -42,7 +43,6 @@ import { getFromIsobmffFile } from '@/scripts/metadata/isobmff' import { getMp3Metadata } from '@/scripts/metadata/mp3' import { getOggMetadata } from '@/scripts/metadata/ogg' import { getSvgMetadata } from '@/scripts/metadata/svg' -import { registerProxyWidgets } from '@/scripts/proxyWidget' import { useDialogService } from '@/services/dialogService' import { useExtensionService } from '@/services/extensionService' import { useLitegraphService } from '@/services/litegraphService' diff --git a/src/scripts/proxyWidget.ts b/src/scripts/proxyWidget.ts deleted file mode 100644 index bcc2179b78..0000000000 --- a/src/scripts/proxyWidget.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { useNodeImage } from '@/composables/node/useNodeImage' -import type { - LGraph, - LGraphCanvas, - LGraphNode -} from '@/lib/litegraph/src/litegraph' -import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode' -import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets.ts' -import { disconnectedWidget } from '@/lib/litegraph/src/widgets/DisconnectedWidget' -import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' -import { parseProxyWidgets } from '@/schemas/proxyWidget' -import { DOMWidgetImpl } from '@/scripts/domWidget' -import { useDomWidgetStore } from '@/stores/domWidgetStore' -import { useNodeOutputStore } from '@/stores/imagePreviewStore' -import { getNodeByExecutionId } from '@/utils/graphTraversalUtil' - -export function registerProxyWidgets(canvas: LGraphCanvas) { - //NOTE: canvasStore hasn't been initialized yet - canvas.canvas.addEventListener<'subgraph-opened'>('subgraph-opened', (e) => { - const { subgraph, fromNode } = e.detail - const pw = parseProxyWidgets(fromNode.properties.proxyWidgets) - for (const node of subgraph.nodes) { - for (const widget of node.widgets ?? []) { - widget.promoted = pw.some(([n, w]) => node.id == n && widget.name == w) - } - } - }) - const originalOnConfigure = SubgraphNode.prototype.onConfigure - SubgraphNode.prototype.onConfigure = function onConfigure(serialisedNode) { - if (!this.isSubgraphNode()) - throw new Error("Can't add proxyWidgets to non-subgraphNode") - - const canvasStore = useCanvasStore() - const subgraphNode = this - //Must give value to proxyWidgets prior to defining or it won't serialize - subgraphNode.properties.proxyWidgets ??= '[]' - let proxyWidgets = subgraphNode.properties.proxyWidgets - - originalOnConfigure?.bind(this)?.(serialisedNode) - - Object.defineProperty(subgraphNode.properties, 'proxyWidgets', { - get: () => { - return proxyWidgets - }, - set: (property: string) => { - const parsed = parseProxyWidgets(property) - const { widgetStates } = useDomWidgetStore() - const isActiveGraph = - useCanvasStore().canvas?.graph === subgraphNode.graph - if (isActiveGraph) { - for (const w of subgraphNode.widgets.filter((w) => - isProxyWidget(w) - )) { - if (w instanceof DOMWidgetImpl && widgetStates.has(w.id)) { - const widgetState = widgetStates.get(w.id) - if (!widgetState) continue - widgetState.active = false - } - } - } - //NOTE: This does not apply to pushed entries, only initial load - subgraphNode.widgets = subgraphNode.widgets.filter( - (w) => !isProxyWidget(w) - ) - for (const [nodeId, widgetName] of parsed) { - const w = addProxyWidget(subgraphNode, `${nodeId}`, widgetName) - if (isActiveGraph && w instanceof DOMWidgetImpl) { - const widgetState = widgetStates.get(w.id) - if (!widgetState) continue - widgetState.active = true - widgetState.widget = w - } - } - proxyWidgets = property - canvasStore.canvas?.setDirty(true, true) - subgraphNode._setConcreteSlots() - subgraphNode.arrange() - } - }) - subgraphNode.properties.proxyWidgets = proxyWidgets - } -} - -type Overlay = Partial & { - graph: LGraph - nodeId: string - widgetName: string - isProxyWidget: boolean - node?: LGraphNode -} -type ProxyWidget = IBaseWidget & { _overlay: Overlay } -function isProxyWidget(w: IBaseWidget): w is ProxyWidget { - return (w as { _overlay?: Overlay })?._overlay?.isProxyWidget ?? false -} - -function addProxyWidget( - subgraphNode: SubgraphNode, - nodeId: string, - widgetName: string -) { - const name = `${nodeId}: ${widgetName}` - const overlay = { - nodeId, - widgetName, - graph: subgraphNode.subgraph, - afterQueued: undefined, - computedHeight: undefined, - isProxyWidget: true, - label: name, - last_y: undefined, - name, - node: subgraphNode, - onRemove: undefined, - promoted: undefined, - serialize: false, - width: undefined, - y: 0 - } - return addProxyFromOverlay(subgraphNode, overlay) -} -function resolveLinkedWidget( - overlay: Overlay -): [LGraphNode | undefined, IBaseWidget | undefined] { - const { graph, nodeId, widgetName } = overlay - const n = getNodeByExecutionId(graph, nodeId) - if (!n) return [undefined, undefined] - return [n, n.widgets?.find((w: IBaseWidget) => w.name === widgetName)] -} -function addProxyFromOverlay(subgraphNode: SubgraphNode, overlay: Overlay) { - let [linkedNode, linkedWidget] = resolveLinkedWidget(overlay) - let backingWidget = linkedWidget ?? disconnectedWidget - if (overlay.widgetName == '$$canvas-image-preview') - overlay.node = new Proxy(subgraphNode, { - get(_t, p) { - if (p == 'imgs') { - if (linkedNode) { - const images = - useNodeOutputStore().getNodeOutputs(linkedNode)?.images ?? [] - if (images !== linkedNode.images) { - linkedNode.images = images - useNodeImage(linkedNode).showPreview() - } - return linkedNode.imgs - } - return [] - } - return Reflect.get(subgraphNode, p) - } - }) - /** - * A set of handlers which define widget interaction - * Many arguments are shared between function calls - * @param {{IBaseWidget} _t - The "target" the call is originally made on. - * This argument is never used, but must be defined for typechecking - * @param {{string}} property - The name of the accessed value. - * Checked for conditional logic, but never changed - * @param {{object}} receiver - The object the result is set to - * and the vlaue used as 'this' if property is a get/set method - * @param {{unknown}} value - only used on set calls. The thing being assigned - */ - const handler = { - get(_t: IBaseWidget, property: string, receiver: object) { - let redirectedTarget: object = backingWidget - let redirectedReceiver = receiver - if (property == '_overlay') return overlay - else if (property == 'value') redirectedReceiver = backingWidget - if (overlay.hasOwnProperty(property)) { - redirectedTarget = overlay - redirectedReceiver = overlay - } - return Reflect.get(redirectedTarget, property, redirectedReceiver) - }, - set(_t: IBaseWidget, property: string, value: unknown, receiver: object) { - let redirectedTarget: object = backingWidget - let redirectedReceiver = receiver - if (property == 'value') redirectedReceiver = backingWidget - else if (property == 'computedHeight') { - //update linkage regularly, but no more than once per frame - ;[linkedNode, linkedWidget] = resolveLinkedWidget(overlay) - backingWidget = linkedWidget ?? disconnectedWidget - } - if (overlay.hasOwnProperty(property)) { - redirectedTarget = overlay - redirectedReceiver = overlay - } - return Reflect.set(redirectedTarget, property, value, redirectedReceiver) - }, - getPrototypeOf() { - return Reflect.getPrototypeOf(backingWidget) - }, - ownKeys() { - return Reflect.ownKeys(backingWidget) - }, - has(_t: IBaseWidget, property: string) { - let redirectedTarget: object = backingWidget - if (overlay.hasOwnProperty(property)) { - redirectedTarget = overlay - } - return Reflect.has(redirectedTarget, property) - } - } - const w = new Proxy(disconnectedWidget, handler) - subgraphNode.widgets.push(w) - return w -}