diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 6870169710..fab82c46d1 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -82,6 +82,9 @@ export interface GraphNodeManager { options: Record ): void + // Refresh Vue widgets from LiteGraph node - use after modifying node.widgets + refreshVueWidgets(nodeId: string): void + // Lifecycle methods cleanup(): void } @@ -334,6 +337,38 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { } } + /** + * Refreshes Vue widget state from LiteGraph node widgets. + * Use this after directly modifying node.widgets to sync Vue state. + */ + const refreshVueWidgets = (nodeId: string): void => { + try { + const node = nodeRefs.get(nodeId) + const currentData = vueNodeData.get(nodeId) + if (!node || !currentData) return + + // Re-extract widgets from node + const slotMetadata = new Map() + node.inputs?.forEach((input, index) => { + if (!input?.widget?.name) return + slotMetadata.set(input.widget.name, { + index, + linked: input.link != null + }) + }) + + const freshWidgets = + node.widgets?.map(safeWidgetMapper(node, slotMetadata)) ?? [] + + vueNodeData.set(nodeId, { + ...currentData, + widgets: freshWidgets + }) + } catch (error) { + // Ignore refresh errors + } + } + /** * Creates a wrapped callback for a widget that maintains LiteGraph/Vue sync */ @@ -661,6 +696,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { vueNodeData, getNode, updateVueWidgetOptions, + refreshVueWidgets, cleanup } } diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue index 79e0503561..e6eeb28adb 100644 --- a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue +++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue @@ -133,7 +133,9 @@ const processedWidgets = computed((): ProcessedWidget[] => { for (const widget of widgets) { // Skip if widget is in the hidden list for this node type - if (widget.options?.hidden) continue + if (widget.options?.hidden) { + continue + } if (widget.options?.canvasOnly) continue if (!widget.type) continue if (!shouldRenderAsVue(widget)) continue diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetWebcam.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetWebcam.vue index d8c3a92796..5bb97afebe 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetWebcam.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetWebcam.vue @@ -197,7 +197,8 @@ function createActionWidget({ y: 100, options: { iconClass, - serialize: false + serialize: false, + hidden: false }, callback: onClick } @@ -256,6 +257,8 @@ function removeWidgetsByName(names: string[]) { updateNodeWidgets(node, (widgets) => widgets.filter((widget) => !names.includes(widget.name)) ) + // Refresh Vue state to pick up widget removal + nodeManager.value?.refreshVueWidgets(String(node.id)) }) } @@ -375,6 +378,9 @@ function showWidgets() { return [...sanitizedWidgets, captureWidget] }) + // Refresh Vue state to pick up the new widgets + nodeManager.value?.refreshVueWidgets(String(node.id)) + // Set up watcher to toggle capture button visibility when mode changes setupCaptureOnQueueWatcher() }) @@ -475,6 +481,9 @@ async function captureImage(node: LGraphNode) { return [...preserved, retakeWidget] }) + + // Refresh Vue state to pick up the new widgets + nodeManager.value?.refreshVueWidgets(String(node.id)) } async function handleRetake() {