mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-26 01:09:46 +00:00
Compare commits
1 Commits
pr1-job-ou
...
test/vue-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dd29e3c0b |
@@ -10,8 +10,9 @@
|
|||||||
v-model:model-value="inputValue"
|
v-model:model-value="inputValue"
|
||||||
v-focus
|
v-focus
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="large"
|
||||||
fluid
|
fluid
|
||||||
|
class="text-2xl"
|
||||||
:pt="{
|
:pt="{
|
||||||
root: {
|
root: {
|
||||||
onBlur: finishEditing,
|
onBlur: finishEditing,
|
||||||
|
|||||||
@@ -4,15 +4,13 @@ import { shallowRef, watch } from 'vue'
|
|||||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||||
import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||||
import { useVueNodesMigrationDismissed } from '@/composables/useVueNodesMigrationDismissed'
|
|
||||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||||
import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'
|
import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'
|
||||||
import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
|
|
||||||
import { app as comfyApp } from '@/scripts/app'
|
import { app as comfyApp } from '@/scripts/app'
|
||||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
|
||||||
|
|
||||||
function useVueNodeLifecycleIndividual() {
|
function useVueNodeLifecycleIndividual() {
|
||||||
const canvasStore = useCanvasStore()
|
const canvasStore = useCanvasStore()
|
||||||
@@ -21,9 +19,7 @@ function useVueNodeLifecycleIndividual() {
|
|||||||
|
|
||||||
const nodeManager = shallowRef<GraphNodeManager | null>(null)
|
const nodeManager = shallowRef<GraphNodeManager | null>(null)
|
||||||
|
|
||||||
const { startSync } = useLayoutSync()
|
const { startSync, stopSync } = useLayoutSync()
|
||||||
|
|
||||||
const isVueNodeToastDismissed = useVueNodesMigrationDismissed()
|
|
||||||
|
|
||||||
const initializeNodeManager = () => {
|
const initializeNodeManager = () => {
|
||||||
// Use canvas graph if available (handles subgraph contexts), fallback to app graph
|
// Use canvas graph if available (handles subgraph contexts), fallback to app graph
|
||||||
@@ -34,13 +30,22 @@ function useVueNodeLifecycleIndividual() {
|
|||||||
const manager = useGraphNodeManager(activeGraph)
|
const manager = useGraphNodeManager(activeGraph)
|
||||||
nodeManager.value = manager
|
nodeManager.value = manager
|
||||||
|
|
||||||
// Initialize layout system with existing nodes from active graph
|
// Only initialize layout store if it's empty (first time enabling Vue nodes)
|
||||||
const nodes = activeGraph._nodes.map((node: LGraphNode) => ({
|
// On subsequent mode switches, preserve existing layout data to prevent drift
|
||||||
id: node.id.toString(),
|
const hasExistingLayouts = activeGraph._nodes.some(
|
||||||
pos: [node.pos[0], node.pos[1]] as [number, number],
|
(node: LGraphNode) =>
|
||||||
size: [node.size[0], node.size[1]] as [number, number]
|
layoutStore.getNodeLayoutRef(node.id.toString()).value !== null
|
||||||
}))
|
)
|
||||||
layoutStore.initializeFromLiteGraph(nodes)
|
|
||||||
|
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
|
// Seed reroutes into the Layout Store so hit-testing uses the new path
|
||||||
for (const reroute of activeGraph.reroutes.values()) {
|
for (const reroute of activeGraph.reroutes.values()) {
|
||||||
@@ -74,23 +79,17 @@ function useVueNodeLifecycleIndividual() {
|
|||||||
/* empty */
|
/* empty */
|
||||||
}
|
}
|
||||||
nodeManager.value = null
|
nodeManager.value = null
|
||||||
|
|
||||||
|
// Stop layout sync when Vue nodes are disabled
|
||||||
|
stopSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch for Vue nodes enabled state changes
|
// Watch for Vue nodes enabled state changes
|
||||||
watch(
|
watch(
|
||||||
() => shouldRenderVueNodes.value && Boolean(comfyApp.canvas?.graph),
|
() => shouldRenderVueNodes.value && Boolean(comfyApp.canvas?.graph),
|
||||||
(enabled, wasEnabled) => {
|
(enabled) => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
initializeNodeManager()
|
initializeNodeManager()
|
||||||
ensureCorrectLayoutScale()
|
|
||||||
|
|
||||||
if (!wasEnabled && !isVueNodeToastDismissed.value) {
|
|
||||||
useToastStore().add({
|
|
||||||
group: 'vue-nodes-migration',
|
|
||||||
severity: 'info',
|
|
||||||
life: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
comfyApp.canvas?.setDirty(true, true)
|
comfyApp.canvas?.setDirty(true, true)
|
||||||
disposeNodeManagerAndSyncs()
|
disposeNodeManagerAndSyncs()
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export function useLayoutSync() {
|
|||||||
liteNode.size[0] !== layout.size.width ||
|
liteNode.size[0] !== layout.size.width ||
|
||||||
liteNode.size[1] !== layout.size.height
|
liteNode.size[1] !== layout.size.height
|
||||||
) {
|
) {
|
||||||
// Use setSize() to trigger onResize callback
|
|
||||||
liteNode.setSize([layout.size.width, layout.size.height])
|
liteNode.setSize([layout.size.width, layout.size.height])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,10 +33,12 @@
|
|||||||
"
|
"
|
||||||
:style="[
|
: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,
|
zIndex: zIndex,
|
||||||
opacity: nodeOpacity,
|
opacity: nodeOpacity,
|
||||||
'--node-component-surface': nodeBodyBackgroundColor
|
'--node-component-surface': nodeBodyBackgroundColor,
|
||||||
|
transformOrigin: 'top left'
|
||||||
},
|
},
|
||||||
dragStyle
|
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(() => {
|
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) {
|
if (size.value && nodeContainerRef.value) {
|
||||||
nodeContainerRef.value.style.setProperty(
|
nodeContainerRef.value.style.setProperty(
|
||||||
'--node-width',
|
'--node-width',
|
||||||
`${size.value.width}px`
|
`${size.value.width * LITEGRAPH_TO_VUE_SCALE}px`
|
||||||
)
|
)
|
||||||
nodeContainerRef.value.style.setProperty(
|
nodeContainerRef.value.style.setProperty(
|
||||||
'--node-height',
|
'--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) => {
|
(result, element) => {
|
||||||
if (isCollapsed.value) return
|
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
|
// Apply size directly to DOM element - ResizeObserver will pick this up
|
||||||
element.style.setProperty('--node-width', `${result.size.width}px`)
|
element.style.setProperty('--node-width', `${domWidth}px`)
|
||||||
element.style.setProperty('--node-height', `${result.size.height}px`)
|
element.style.setProperty('--node-height', `${domHeight}px`)
|
||||||
|
|
||||||
const currentPosition = position.value
|
const currentPosition = position.value
|
||||||
const deltaX = Math.abs(result.position.x - currentPosition.x)
|
const deltaX = Math.abs(result.position.x - currentPosition.x)
|
||||||
|
|||||||
@@ -86,25 +86,44 @@ const resizeObserver = new ResizeObserver((entries) => {
|
|||||||
|
|
||||||
if (!elementType || !elementId) continue
|
if (!elementType || !elementId) continue
|
||||||
|
|
||||||
// Use contentBoxSize when available; fall back to contentRect for older engines/tests
|
// Use borderBoxSize to include borders in measurements
|
||||||
const contentBox = Array.isArray(entry.contentBoxSize)
|
// This matches the visual size that will be scaled by CSS transform
|
||||||
? entry.contentBoxSize[0]
|
// Fallback to getBoundingClientRect for older engines
|
||||||
: {
|
let width: number
|
||||||
inlineSize: entry.contentRect.width,
|
let height: number
|
||||||
blockSize: entry.contentRect.height
|
|
||||||
}
|
if (entry.borderBoxSize) {
|
||||||
const width = contentBox.inlineSize
|
const borderBox = Array.isArray(entry.borderBoxSize)
|
||||||
const height = contentBox.blockSize
|
? 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
|
// Screen-space rect
|
||||||
const rect = element.getBoundingClientRect()
|
const rect = element.getBoundingClientRect()
|
||||||
const [cx, cy] = conv.clientPosToCanvasPos([rect.left, rect.top])
|
const [cx, cy] = conv.clientPosToCanvasPos([rect.left, rect.top])
|
||||||
const topLeftCanvas = { x: cx, y: cy }
|
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 = {
|
const bounds: Bounds = {
|
||||||
x: topLeftCanvas.x,
|
x: topLeftCanvas.x,
|
||||||
y: topLeftCanvas.y + LiteGraph.NODE_TITLE_HEIGHT,
|
y: topLeftCanvas.y + LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
width: Math.max(0, width),
|
width: Math.max(0, width * VUE_TO_LITEGRAPH_SCALE),
|
||||||
height: Math.max(0, height - LiteGraph.NODE_TITLE_HEIGHT)
|
height: Math.max(
|
||||||
|
0,
|
||||||
|
height * VUE_TO_LITEGRAPH_SCALE - LiteGraph.NODE_TITLE_HEIGHT
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let updates = updatesByType.get(elementType)
|
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 { ComfyAppMenu } from './ui/menu/index'
|
||||||
import { clone } from './utils'
|
import { clone } from './utils'
|
||||||
import { type ComfyWidgetConstructor } from './widgets'
|
import { type ComfyWidgetConstructor } from './widgets'
|
||||||
import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
|
|
||||||
|
|
||||||
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
|
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
|
// @ts-expect-error Discrepancies between zod and litegraph - in progress
|
||||||
this.graph.configure(graphData)
|
this.graph.configure(graphData)
|
||||||
|
|
||||||
ensureCorrectLayoutScale()
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
restore_view &&
|
restore_view &&
|
||||||
useSettingStore().get('Comfy.EnableWorkflowViewRestore')
|
useSettingStore().get('Comfy.EnableWorkflowViewRestore')
|
||||||
|
|||||||
Reference in New Issue
Block a user