mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 14:27:40 +00:00
test: got it working
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user