From 8df41ab040417e7be40f4302ef8a918f213917cf Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Tue, 12 Aug 2025 15:59:24 -0400 Subject: [PATCH] Add vue node feature flag (#4927) --- src/components/graph/DomWidgets.vue | 4 -- src/components/graph/GraphCanvas.vue | 64 +++++++++++++++++++-------- src/composables/useCoreCommands.ts | 27 +++++++++++ src/composables/useFeatureFlags.ts | 18 ++------ src/constants/coreSettings.ts | 29 ++++++------ src/lib/litegraph/src/LGraphCanvas.ts | 13 ++++++ src/schemas/apiSchema.ts | 2 + 7 files changed, 104 insertions(+), 53 deletions(-) diff --git a/src/components/graph/DomWidgets.vue b/src/components/graph/DomWidgets.vue index 25136b887..7edca3f5e 100644 --- a/src/components/graph/DomWidgets.vue +++ b/src/components/graph/DomWidgets.vue @@ -16,7 +16,6 @@ import { computed } from 'vue' import DomWidget from '@/components/graph/widgets/DomWidget.vue' import { useChainCallback } from '@/composables/functional/useChainCallback' -import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { useDomWidgetStore } from '@/stores/domWidgetStore' import { useCanvasStore } from '@/stores/graphStore' @@ -28,9 +27,6 @@ const updateWidgets = () => { const lgCanvas = canvasStore.canvas if (!lgCanvas) return - // Skip updating DOM widgets when Vue nodes mode is enabled - if (LiteGraph.vueNodesMode) return - const lowQuality = lgCanvas.low_quality const currentGraph = lgCanvas.graph diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 8b81ca963..5e463be9a 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -65,6 +65,7 @@ @@ -110,6 +114,7 @@ import DomWidgets from '@/components/graph/DomWidgets.vue' import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue' import MiniMap from '@/components/graph/MiniMap.vue' import NodeTooltip from '@/components/graph/NodeTooltip.vue' +import SelectionOverlay from '@/components/graph/SelectionOverlay.vue' import SelectionToolbox from '@/components/graph/SelectionToolbox.vue' import TitleEditor from '@/components/graph/TitleEditor.vue' import TransformPane from '@/components/graph/TransformPane.vue' @@ -189,7 +194,14 @@ const minimapEnabled = computed(() => settingStore.get('Comfy.Minimap.Visible')) const { shouldRenderVueNodes, isDevModeEnabled } = useFeatureFlags() // TransformPane enabled when Vue nodes are enabled OR debug override -const debugOverrideVueNodes = ref(true) // Default to true for development +const debugOverrideVueNodes = ref(false) +// Persist debug panel visibility in settings so core commands can toggle it +const debugPanelVisible = computed({ + get: () => settingStore.get('Comfy.VueNodes.DebugPanel.Visible') ?? false, + set: (v: boolean) => { + void settingStore.set('Comfy.VueNodes.DebugPanel.Visible', v) + } +}) const transformPaneEnabled = computed( () => shouldRenderVueNodes.value || debugOverrideVueNodes.value ) @@ -269,6 +281,7 @@ watch(canvasRef, () => { // Vue node lifecycle management - initialize after graph is ready let nodeManager: ReturnType | null = null +let cleanupNodeManager: (() => void) | null = null const vueNodeData = ref>(new Map()) const nodeState = ref>(new Map()) const nodePositions = ref>( @@ -291,31 +304,49 @@ const performanceMetrics = reactive({ const nodeDataTrigger = ref(0) const initializeNodeManager = () => { - if (!comfyApp.graph || nodeManager) { - return - } - + if (!comfyApp.graph || nodeManager) return nodeManager = useGraphNodeManager(comfyApp.graph) - + cleanupNodeManager = nodeManager.cleanup // Use the manager's reactive maps directly vueNodeData.value = nodeManager.vueNodeData nodeState.value = nodeManager.nodeState nodePositions.value = nodeManager.nodePositions nodeSizes.value = nodeManager.nodeSizes - detectChangesInRAF = nodeManager.detectChangesInRAF Object.assign(performanceMetrics, nodeManager.performanceMetrics) - // Force computed properties to re-evaluate nodeDataTrigger.value++ } -// Watch for graph availability +const disposeNodeManager = () => { + if (!nodeManager) return + try { + cleanupNodeManager?.() + } catch { + /* empty */ + } + nodeManager = null + cleanupNodeManager = null + // Reset reactive maps to inert defaults + vueNodeData.value = new Map() + nodeState.value = new Map() + nodePositions.value = new Map() + nodeSizes.value = new Map() + // Reset metrics + performanceMetrics.frameTime = 0 + performanceMetrics.updateTime = 0 + performanceMetrics.nodeCount = 0 + performanceMetrics.culledCount = 0 +} + +// Watch for transformPaneEnabled to gate the node manager lifecycle watch( - () => comfyApp.graph, - (graph) => { - if (graph) { + () => transformPaneEnabled.value && Boolean(comfyApp.graph), + (enabled) => { + if (enabled) { initializeNodeManager() + } else { + disposeNodeManager() } }, { immediate: true } @@ -696,11 +727,6 @@ onMounted(async () => { comfyAppReady.value = true - // Initialize node manager after setup is complete - if (comfyApp.graph) { - initializeNodeManager() - } - comfyApp.canvas.onSelectionChange = useChainCallback( comfyApp.canvas.onSelectionChange, () => canvasStore.updateSelectedItems() diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 026ff656d..5b35fd5f2 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -276,6 +276,33 @@ export function useCoreCommands(): ComfyCommand[] { app.canvas.setDirty(true, true) } }, + { + id: 'Experimental.ToggleVueNodes', + label: () => + `Experimental: ${ + useSettingStore().get('Comfy.VueNodes.Enabled') ? 'Disable' : 'Enable' + } Vue Nodes`, + function: async () => { + const settingStore = useSettingStore() + const current = settingStore.get('Comfy.VueNodes.Enabled') ?? false + await settingStore.set('Comfy.VueNodes.Enabled', !current) + } + }, + { + id: 'Experimental.ToggleVueNodeDebugPanel', + label: () => + `Experimental: ${ + useSettingStore().get('Comfy.VueNodes.DebugPanel.Visible') + ? 'Hide' + : 'Show' + } Vue Node Debug Panel`, + function: async () => { + const settingStore = useSettingStore() + const current = + settingStore.get('Comfy.VueNodes.DebugPanel.Visible') ?? false + await settingStore.set('Comfy.VueNodes.DebugPanel.Visible', !current) + } + }, { id: 'Comfy.Canvas.FitView', icon: 'pi pi-expand', diff --git a/src/composables/useFeatureFlags.ts b/src/composables/useFeatureFlags.ts index e503ac989..50fc61a6e 100644 --- a/src/composables/useFeatureFlags.ts +++ b/src/composables/useFeatureFlags.ts @@ -17,21 +17,10 @@ export const useFeatureFlags = () => { */ const isVueNodesEnabled = computed(() => { try { - return settingStore.get('Comfy.VueNodes.Enabled' as any) ?? true // Default to true for development + // Off by default: ensure Vue nodes are disabled unless explicitly enabled + return settingStore.get('Comfy.VueNodes.Enabled') ?? false } catch { - return true // Default to true for development - } - }) - - /** - * Enable Vue widget rendering within Vue nodes - * When disabled, Vue nodes render without widgets (structure only) - */ - const isVueWidgetsEnabled = computed(() => { - try { - return settingStore.get('Comfy.VueNodes.Widgets' as any) ?? true - } catch { - return true + return false } }) @@ -74,7 +63,6 @@ export const useFeatureFlags = () => { return { isVueNodesEnabled, - isVueWidgetsEnabled, isDevModeEnabled, shouldRenderVueNodes, syncVueNodesFlag diff --git a/src/constants/coreSettings.ts b/src/constants/coreSettings.ts index aff4fef6f..b199def76 100644 --- a/src/constants/coreSettings.ts +++ b/src/constants/coreSettings.ts @@ -930,24 +930,23 @@ export const CORE_SETTINGS: SettingParams[] = [ defaultValue: 0 }, - // Vue Node System Settings + /** + * Vue Node System Settings + */ { - id: 'Comfy.VueNodes.Enabled' as any, - category: ['Comfy', 'Vue Nodes'], - experimental: true, - name: 'Enable Vue node rendering', + id: 'Comfy.VueNodes.Enabled', + name: 'Enable Vue node rendering (hidden)', + type: 'hidden', tooltip: - 'Render nodes as Vue components instead of canvas elements. Experimental feature.', - type: 'boolean', - defaultValue: false + 'Render nodes as Vue components instead of canvas. Hidden; toggle via Experimental keybinding.', + defaultValue: false, + experimental: true }, { - id: 'Comfy.VueNodes.Widgets' as any, - category: ['Comfy', 'Vue Nodes', 'Widgets'], - experimental: true, - name: 'Enable Vue widgets', - tooltip: 'Render widgets as Vue components within Vue nodes.', - type: 'boolean', - defaultValue: true + id: 'Comfy.VueNodes.DebugPanel.Visible', + name: 'Vue Nodes Debug Panel Visible (hidden)', + type: 'hidden', + defaultValue: false, + experimental: true } ] diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index 33c05aed5..a4cc5ff7f 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -4999,6 +4999,19 @@ export class LGraphCanvas drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void { this.current_node = node + // When Vue nodes mode is enabled, LiteGraph should not draw node chrome or widgets. + // We still need to keep slot metrics and layout in sync for hit-testing and links. + // Interaction system changes coming later, chances are vue nodes mode will be mostly broken on land + if (LiteGraph.vueNodesMode) { + // Prepare concrete slots and compute layout measures without rendering visuals. + node._setConcreteSlots() + if (!node.collapsed) { + node.arrange() + } + // Skip all node body/widget/title rendering. Vue overlay handles visuals. + return + } + const color = node.renderingColor const bgcolor = node.renderingBgColor diff --git a/src/schemas/apiSchema.ts b/src/schemas/apiSchema.ts index 6e5e2a4d0..b13de249c 100644 --- a/src/schemas/apiSchema.ts +++ b/src/schemas/apiSchema.ts @@ -477,6 +477,8 @@ const zSettings = z.object({ 'Comfy.Minimap.RenderBypassState': z.boolean(), 'Comfy.Minimap.RenderErrorState': z.boolean(), 'Comfy.Canvas.NavigationMode': z.string(), + 'Comfy.VueNodes.Enabled': z.boolean(), + 'Comfy.VueNodes.DebugPanel.Visible': z.boolean(), 'Comfy-Desktop.AutoUpdate': z.boolean(), 'Comfy-Desktop.SendStatistics': z.boolean(), 'Comfy-Desktop.WindowStyle': z.string(),