Add vue node feature flag (#4927)

This commit is contained in:
Benjamin Lu
2025-08-12 15:59:24 -04:00
committed by Benjamin Lu
parent 2b9a9e2371
commit 8df41ab040
7 changed files with 104 additions and 53 deletions

View File

@@ -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

View File

@@ -65,6 +65,7 @@
<!-- Debug Panel (Development Only) -->
<VueNodeDebugPanel
v-if="debugPanelVisible"
v-model:debug-override-vue-nodes="debugOverrideVueNodes"
v-model:show-performance-overlay="showPerformanceOverlay"
:canvas-viewport="canvasViewport"
@@ -86,8 +87,11 @@
canvasStore.canvas to be initialized. -->
<template v-if="comfyAppReady">
<TitleEditor />
<SelectionToolbox v-if="selectionToolboxEnabled" />
<DomWidgets />
<SelectionOverlay v-if="selectionToolboxEnabled">
<SelectionToolbox />
</SelectionOverlay>
<!-- Render legacy DOM widgets only when Vue nodes are disabled -->
<DomWidgets v-if="!shouldRenderVueNodes" />
</template>
</template>
@@ -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<typeof useGraphNodeManager> | null = null
let cleanupNodeManager: (() => void) | null = null
const vueNodeData = ref<ReadonlyMap<string, VueNodeData>>(new Map())
const nodeState = ref<ReadonlyMap<string, NodeState>>(new Map())
const nodePositions = ref<ReadonlyMap<string, { x: number; y: number }>>(
@@ -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()

View File

@@ -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',

View File

@@ -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

View File

@@ -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
}
]

View File

@@ -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

View File

@@ -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(),