test: got it working

This commit is contained in:
--list
2025-11-03 11:47:34 -08:00
parent 72389637ed
commit 4dd29e3c0b
7 changed files with 79 additions and 140 deletions

View File

@@ -10,8 +10,9 @@
v-model:model-value="inputValue"
v-focus
type="text"
size="small"
size="large"
fluid
class="text-2xl"
:pt="{
root: {
onBlur: finishEditing,

View File

@@ -4,15 +4,13 @@ import { shallowRef, watch } from 'vue'
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager'
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
import { useVueNodesMigrationDismissed } from '@/composables/useVueNodesMigrationDismissed'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'
import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
import { app as comfyApp } from '@/scripts/app'
import { useToastStore } from '@/platform/updates/common/toastStore'
function useVueNodeLifecycleIndividual() {
const canvasStore = useCanvasStore()
@@ -21,9 +19,7 @@ function useVueNodeLifecycleIndividual() {
const nodeManager = shallowRef<GraphNodeManager | null>(null)
const { startSync } = useLayoutSync()
const isVueNodeToastDismissed = useVueNodesMigrationDismissed()
const { startSync, stopSync } = useLayoutSync()
const initializeNodeManager = () => {
// Use canvas graph if available (handles subgraph contexts), fallback to app graph
@@ -34,13 +30,22 @@ function useVueNodeLifecycleIndividual() {
const manager = useGraphNodeManager(activeGraph)
nodeManager.value = manager
// Initialize layout system with existing nodes from active graph
const nodes = activeGraph._nodes.map((node: LGraphNode) => ({
id: node.id.toString(),
pos: [node.pos[0], node.pos[1]] as [number, number],
size: [node.size[0], node.size[1]] as [number, number]
}))
layoutStore.initializeFromLiteGraph(nodes)
// Only initialize layout store if it's empty (first time enabling Vue nodes)
// On subsequent mode switches, preserve existing layout data to prevent drift
const hasExistingLayouts = activeGraph._nodes.some(
(node: LGraphNode) =>
layoutStore.getNodeLayoutRef(node.id.toString()).value !== null
)
if (!hasExistingLayouts) {
// First time: initialize from Litegraph
const nodes = activeGraph._nodes.map((node: LGraphNode) => ({
id: node.id.toString(),
pos: [node.pos[0], node.pos[1]] as [number, number],
size: [node.size[0], node.size[1]] as [number, number]
}))
layoutStore.initializeFromLiteGraph(nodes)
}
// Seed reroutes into the Layout Store so hit-testing uses the new path
for (const reroute of activeGraph.reroutes.values()) {
@@ -74,23 +79,17 @@ function useVueNodeLifecycleIndividual() {
/* empty */
}
nodeManager.value = null
// Stop layout sync when Vue nodes are disabled
stopSync()
}
// Watch for Vue nodes enabled state changes
watch(
() => shouldRenderVueNodes.value && Boolean(comfyApp.canvas?.graph),
(enabled, wasEnabled) => {
(enabled) => {
if (enabled) {
initializeNodeManager()
ensureCorrectLayoutScale()
if (!wasEnabled && !isVueNodeToastDismissed.value) {
useToastStore().add({
group: 'vue-nodes-migration',
severity: 'info',
life: 0
})
}
} else {
comfyApp.canvas?.setDirty(true, true)
disposeNodeManagerAndSyncs()

View File

@@ -47,7 +47,6 @@ export function useLayoutSync() {
liteNode.size[0] !== layout.size.width ||
liteNode.size[1] !== layout.size.height
) {
// Use setSize() to trigger onResize callback
liteNode.setSize([layout.size.width, layout.size.height])
}
}

View File

@@ -33,10 +33,12 @@
"
:style="[
{
transform: `translate(${position.x ?? 0}px, ${(position.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px)`,
// Position in Litegraph coordinates, then scale down from Vue DOM size to visual size
transform: `translate(${position.x ?? 0}px, ${(position.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px) scale(${VUE_TO_LITEGRAPH_SCALE})`,
zIndex: zIndex,
opacity: nodeOpacity,
'--node-component-surface': nodeBodyBackgroundColor
'--node-component-surface': nodeBodyBackgroundColor,
transformOrigin: 'top left'
},
dragStyle
]"
@@ -285,16 +287,25 @@ const handleContextMenu = (event: MouseEvent) => {
}
}
// Coordinate system constants:
// - Layout store uses Litegraph coordinate system (smaller values)
// - Vue nodes need larger intrinsic size due to padding/spacing
// - We scale up DOM by 2, then scale down visually by 1/2 via CSS transform
// - This achieves correct visual size while allowing proper intrinsic sizing
const LITEGRAPH_TO_VUE_SCALE = 2
const VUE_TO_LITEGRAPH_SCALE = 1 / 2 // 0.5
onMounted(() => {
// Set initial DOM size from layout store, but respect intrinsic content minimum
// Set initial DOM size from layout store (convert Litegraph coords to Vue DOM coords)
// Layout store contains Litegraph coordinates, we scale up for Vue's larger intrinsic size
if (size.value && nodeContainerRef.value) {
nodeContainerRef.value.style.setProperty(
'--node-width',
`${size.value.width}px`
`${size.value.width * LITEGRAPH_TO_VUE_SCALE}px`
)
nodeContainerRef.value.style.setProperty(
'--node-height',
`${size.value.height}px`
`${(size.value.height + LiteGraph.NODE_TITLE_HEIGHT) * LITEGRAPH_TO_VUE_SCALE}px`
)
}
})
@@ -341,9 +352,16 @@ const { startResize } = useNodeResize(
(result, element) => {
if (isCollapsed.value) return
// Convert from visual/canvas coordinates to Vue DOM coordinates
// result.size is the visual size from getBoundingClientRect (after CSS transform)
// This already includes NODE_TITLE_HEIGHT in the visual measurement
// We just need to scale up to DOM size by inverting the transform scale
const domWidth = result.size.width * LITEGRAPH_TO_VUE_SCALE
const domHeight = result.size.height * LITEGRAPH_TO_VUE_SCALE
// Apply size directly to DOM element - ResizeObserver will pick this up
element.style.setProperty('--node-width', `${result.size.width}px`)
element.style.setProperty('--node-height', `${result.size.height}px`)
element.style.setProperty('--node-width', `${domWidth}px`)
element.style.setProperty('--node-height', `${domHeight}px`)
const currentPosition = position.value
const deltaX = Math.abs(result.position.x - currentPosition.x)

View File

@@ -86,25 +86,44 @@ const resizeObserver = new ResizeObserver((entries) => {
if (!elementType || !elementId) continue
// Use contentBoxSize when available; fall back to contentRect for older engines/tests
const contentBox = Array.isArray(entry.contentBoxSize)
? entry.contentBoxSize[0]
: {
inlineSize: entry.contentRect.width,
blockSize: entry.contentRect.height
}
const width = contentBox.inlineSize
const height = contentBox.blockSize
// Use borderBoxSize to include borders in measurements
// This matches the visual size that will be scaled by CSS transform
// Fallback to getBoundingClientRect for older engines
let width: number
let height: number
if (entry.borderBoxSize) {
const borderBox = Array.isArray(entry.borderBoxSize)
? entry.borderBoxSize[0]
: entry.borderBoxSize
width = borderBox.inlineSize
height = borderBox.blockSize
} else {
// Fallback: use getBoundingClientRect which gives us borderBox size
const rect = element.getBoundingClientRect()
width = rect.width
height = rect.height
}
// Screen-space rect
const rect = element.getBoundingClientRect()
const [cx, cy] = conv.clientPosToCanvasPos([rect.left, rect.top])
const topLeftCanvas = { x: cx, y: cy }
// Convert Vue DOM coordinates (scaled by 2x) to Litegraph coordinates
// - Layout store uses Litegraph coordinate system as single source of truth
// - Vue nodes are rendered at 2x size in DOM, then scaled down 0.5 via CSS transform
// - We use borderBoxSize (includes border) to match the visual size after transform
// - This prevents drift: borderBox 200px → store 100px → next cycle borderBox 200px ✓
const VUE_TO_LITEGRAPH_SCALE = 0.5
const bounds: Bounds = {
x: topLeftCanvas.x,
y: topLeftCanvas.y + LiteGraph.NODE_TITLE_HEIGHT,
width: Math.max(0, width),
height: Math.max(0, height - LiteGraph.NODE_TITLE_HEIGHT)
width: Math.max(0, width * VUE_TO_LITEGRAPH_SCALE),
height: Math.max(
0,
height * VUE_TO_LITEGRAPH_SCALE - LiteGraph.NODE_TITLE_HEIGHT
)
}
let updates = updatesByType.get(elementType)

View File

@@ -1,94 +0,0 @@
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { createBounds } from '@/lib/litegraph/src/measure'
import { useSettingStore } from '@/platform/settings/settingStore'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import type { NodeBoundsUpdate } from '@/renderer/core/layout/types'
import { app as comfyApp } from '@/scripts/app'
const SCALE_FACTOR = 1.75
export function ensureCorrectLayoutScale() {
const settingStore = useSettingStore()
const autoScaleLayoutSetting = settingStore.get(
'Comfy.VueNodes.AutoScaleLayout'
)
if (autoScaleLayoutSetting === false) {
return
}
const canvas = comfyApp.canvas
const graph = canvas?.graph
if (!graph || !graph.nodes) return
if (graph.extra?.vueNodesScaled === true) {
return
}
const vueNodesEnabled = settingStore.get('Comfy.VueNodes.Enabled')
if (!vueNodesEnabled) {
return
}
const lgBounds = createBounds(graph.nodes)
if (!lgBounds) return
const allVueNodes = layoutStore.getAllNodes().value
const originX = lgBounds[0]
const originY = lgBounds[1]
const lgNodesById = new Map(
graph.nodes.map((node) => [String(node.id), node])
)
const yjsMoveNodeUpdates: NodeBoundsUpdate[] = []
for (const vueNode of allVueNodes.values()) {
const lgNode = lgNodesById.get(String(vueNode.id))
if (!lgNode) continue
const lgBodyY = lgNode.pos[1] - LiteGraph.NODE_TITLE_HEIGHT
const relativeX = lgNode.pos[0] - originX
const relativeY = lgBodyY - originY
const newX = originX + relativeX * SCALE_FACTOR
const newY = originY + relativeY * SCALE_FACTOR
const newWidth = lgNode.width * SCALE_FACTOR
const newHeight = lgNode.height * SCALE_FACTOR
yjsMoveNodeUpdates.push({
nodeId: vueNode.id,
bounds: {
x: newX,
y: newY,
width: newWidth,
height: newHeight
}
})
}
layoutStore.batchUpdateNodeBounds(yjsMoveNodeUpdates)
graph.groups.forEach((group) => {
const groupBodyY = group.pos[1] - LiteGraph.NODE_TITLE_HEIGHT
const relativeX = group.pos[0] - originX
const relativeY = groupBodyY - originY
const newPosY =
originY + relativeY * SCALE_FACTOR + LiteGraph.NODE_TITLE_HEIGHT
group.pos = [originX + relativeX * SCALE_FACTOR, newPosY]
group.size = [group.size[0] * SCALE_FACTOR, group.size[1] * SCALE_FACTOR]
})
const originScreen = canvas.ds.convertOffsetToCanvas([originX, originY])
canvas.ds.changeScale(canvas.ds.scale / SCALE_FACTOR, originScreen)
if (!graph.extra) graph.extra = {}
graph.extra.vueNodesScaled = true
}

View File

@@ -101,7 +101,6 @@ import { $el, ComfyUI } from './ui'
import { ComfyAppMenu } from './ui/menu/index'
import { clone } from './utils'
import { type ComfyWidgetConstructor } from './widgets'
import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
@@ -1201,8 +1200,6 @@ export class ComfyApp {
// @ts-expect-error Discrepancies between zod and litegraph - in progress
this.graph.configure(graphData)
ensureCorrectLayoutScale()
if (
restore_view &&
useSettingStore().get('Comfy.EnableWorkflowViewRestore')