diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 23edf6ea68..3d13eb9ec8 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -75,6 +75,16 @@ export interface GraphNodeManager { // Access to original LiteGraph nodes (non-reactive) getNode(id: string): LGraphNode | undefined + // Update widget options (e.g., hidden, disabled) - triggers Vue reactivity + updateVueWidgetOptions( + nodeId: string, + widgetName: string, + options: Record + ): void + + // Refresh Vue widgets from LiteGraph node - use after modifying node.widgets + refreshVueWidgets(nodeId: string): void + // Lifecycle methods cleanup(): void } @@ -298,6 +308,67 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { } } + /** + * Updates Vue state when widget options change (e.g., hidden, disabled) + */ + const updateVueWidgetOptions = ( + nodeId: string, + widgetName: string, + options: Record + ): void => { + try { + const currentData = vueNodeData.get(nodeId) + if (!currentData?.widgets) return + + const updatedWidgets = currentData.widgets.map((w) => + w.name === widgetName + ? { ...w, options: { ...w.options, ...options } } + : w + ) + // Create a completely new object to ensure Vue reactivity triggers + const updatedData = { + ...currentData, + widgets: updatedWidgets + } + + vueNodeData.set(nodeId, updatedData) + } catch (error) { + console.error('Error updating widget options:', error) + } + } + + /** + * 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) { + console.error('Error refreshing Vue widgets:', error) + } + } + /** * Creates a wrapped callback for a widget that maintains LiteGraph/Vue sync */ @@ -624,6 +695,8 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { return { vueNodeData, getNode, + updateVueWidgetOptions, + refreshVueWidgets, cleanup } } diff --git a/src/locales/en/main.json b/src/locales/en/main.json index d3a34c8f17..e9e013edd0 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -95,6 +95,9 @@ "save": "Save", "saving": "Saving", "no": "No", + "yes": "Yes", + "on": "On", + "off": "Off", "cancel": "Cancel", "close": "Close", "pressKeysForNewBinding": "Press keys for new binding", @@ -144,6 +147,18 @@ "control_before_generate": "control before generate", "choose_file_to_upload": "choose file to upload", "capture": "capture", + "capturePhoto": "Capture Photo", + "captureModeOnRun": "On Run", + "captureModeManual": "Manually", + "capturedImage": "Captured Image", + "retakePhoto": "Retake photo", + "clickToStopLivePreview": "Click to stop live preview", + "failedToCaptureImage": "Failed to capture image", + "noWebcamImageCaptured": "No webcam image captured", + "errorCapturingImage": "Error capturing image: {error}", + "unableToLoadWebcam": "Unable to load webcam: {error}", + "webcamRequiresTLS": "Unable to load webcam. TLS is required when not on localhost. Error: {error}", + "turnOnCamera": "Turn on Camera", "nodes": "Nodes", "community": "Community", "all": "All", diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue index 9a0983a4fb..8776a0290f 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/WidgetButton.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue index 6e5a349d44..30b975fc7c 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue @@ -1,7 +1,7 @@