Skip to content

Commit 5650ccb

Browse files
persist size changes
1 parent 45ebc59 commit 5650ccb

File tree

10 files changed

+331
-55
lines changed

10 files changed

+331
-55
lines changed

src/renderer/core/layout/store/layoutStore.ts

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as Y from 'yjs'
1010

1111
import { ACTOR_CONFIG } from '@/renderer/core/layout/constants'
1212
import type {
13+
BatchUpdateBoundsOperation,
1314
CreateLinkOperation,
1415
CreateNodeOperation,
1516
CreateRerouteOperation,
@@ -864,6 +865,12 @@ class LayoutStoreImpl implements LayoutStore {
864865
case 'deleteNode':
865866
this.handleDeleteNode(operation as DeleteNodeOperation, change)
866867
break
868+
case 'batchUpdateBounds':
869+
this.handleBatchUpdateBounds(
870+
operation as BatchUpdateBoundsOperation,
871+
change
872+
)
873+
break
867874
case 'createLink':
868875
this.handleCreateLink(operation as CreateLinkOperation, change)
869876
break
@@ -1092,6 +1099,38 @@ class LayoutStoreImpl implements LayoutStore {
10921099
change.nodeIds.push(operation.nodeId)
10931100
}
10941101

1102+
private handleBatchUpdateBounds(
1103+
operation: BatchUpdateBoundsOperation,
1104+
change: LayoutChange
1105+
): void {
1106+
const spatialUpdates: Array<{ nodeId: NodeId; bounds: Bounds }> = []
1107+
1108+
for (const nodeId of operation.nodeIds) {
1109+
const data = operation.bounds[nodeId]
1110+
const ynode = this.ynodes.get(nodeId)
1111+
if (!ynode || !data) continue
1112+
1113+
ynode.set('position', { x: data.bounds.x, y: data.bounds.y })
1114+
ynode.set('size', {
1115+
width: data.bounds.width,
1116+
height: data.bounds.height
1117+
})
1118+
ynode.set('bounds', data.bounds)
1119+
1120+
spatialUpdates.push({ nodeId, bounds: data.bounds })
1121+
change.nodeIds.push(nodeId)
1122+
}
1123+
1124+
// Batch update spatial index for better performance
1125+
if (spatialUpdates.length > 0) {
1126+
this.spatialIndex.batchUpdate(spatialUpdates)
1127+
}
1128+
1129+
if (change.nodeIds.length) {
1130+
change.type = 'update'
1131+
}
1132+
}
1133+
10951134
private handleCreateLink(
10961135
operation: CreateLinkOperation,
10971136
change: LayoutChange
@@ -1372,19 +1411,38 @@ class LayoutStoreImpl implements LayoutStore {
13721411
const originalSource = this.currentSource
13731412
this.currentSource = LayoutSource.Vue
13741413

1375-
this.ydoc.transact(() => {
1376-
for (const { nodeId, bounds } of updates) {
1377-
const ynode = this.ynodes.get(nodeId)
1378-
if (!ynode) continue
1414+
const nodeIds: NodeId[] = []
1415+
const boundsRecord: BatchUpdateBoundsOperation['bounds'] = {}
13791416

1380-
this.spatialIndex.update(nodeId, bounds)
1381-
ynode.set('bounds', bounds)
1382-
ynode.set('position', { x: bounds.x, y: bounds.y })
1383-
ynode.set('size', { width: bounds.width, height: bounds.height })
1417+
for (const { nodeId, bounds } of updates) {
1418+
const ynode = this.ynodes.get(nodeId)
1419+
if (!ynode) continue
1420+
const currentLayout = yNodeToLayout(ynode)
1421+
1422+
boundsRecord[nodeId] = {
1423+
bounds,
1424+
previousBounds: currentLayout.bounds
13841425
}
1385-
}, this.currentActor)
1426+
nodeIds.push(nodeId)
1427+
}
1428+
1429+
if (!nodeIds.length) {
1430+
this.currentSource = originalSource
1431+
return
1432+
}
1433+
1434+
const operation: BatchUpdateBoundsOperation = {
1435+
type: 'batchUpdateBounds',
1436+
entity: 'node',
1437+
nodeIds,
1438+
bounds: boundsRecord,
1439+
timestamp: Date.now(),
1440+
source: this.currentSource,
1441+
actor: this.currentActor
1442+
}
1443+
1444+
this.applyOperation(operation)
13861445

1387-
// Restore original source
13881446
this.currentSource = originalSource
13891447
}
13901448
}

src/renderer/core/layout/sync/useLinkLayoutSync.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,11 @@ export function useLinkLayoutSync() {
267267
case 'resizeNode':
268268
recomputeLinksForNode(parseInt(change.operation.nodeId))
269269
break
270+
case 'batchUpdateBounds':
271+
for (const nodeId of change.operation.nodeIds) {
272+
recomputeLinksForNode(parseInt(nodeId))
273+
}
274+
break
270275
case 'createLink':
271276
recomputeLinkById(change.operation.linkId)
272277
break

src/renderer/core/layout/types.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ type OperationType =
122122
| 'createNode'
123123
| 'deleteNode'
124124
| 'setNodeVisibility'
125-
| 'batchUpdate'
125+
| 'batchUpdateBounds'
126126
| 'createLink'
127127
| 'deleteLink'
128128
| 'createReroute'
@@ -184,10 +184,11 @@ interface SetNodeVisibilityOperation extends NodeOpBase {
184184
/**
185185
* Batch update operation for atomic multi-property changes
186186
*/
187-
interface BatchUpdateOperation extends NodeOpBase {
188-
type: 'batchUpdate'
189-
updates: Partial<NodeLayout>
190-
previousValues: Partial<NodeLayout>
187+
export interface BatchUpdateBoundsOperation extends OperationMeta {
188+
entity: 'node'
189+
type: 'batchUpdateBounds'
190+
nodeIds: NodeId[]
191+
bounds: Record<NodeId, { bounds: Bounds; previousBounds: Bounds }>
191192
}
192193

193194
/**
@@ -244,7 +245,7 @@ export type LayoutOperation =
244245
| CreateNodeOperation
245246
| DeleteNodeOperation
246247
| SetNodeVisibilityOperation
247-
| BatchUpdateOperation
248+
| BatchUpdateBoundsOperation
248249
| CreateLinkOperation
249250
| DeleteLinkOperation
250251
| CreateRerouteOperation

src/renderer/core/spatial/SpatialIndex.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ export class SpatialIndexManager {
5555
this.invalidateCache()
5656
}
5757

58+
/**
59+
* Batch update multiple nodes' bounds in the spatial index
60+
* More efficient than calling update() multiple times as it only invalidates cache once
61+
*/
62+
batchUpdate(updates: Array<{ nodeId: NodeId; bounds: Bounds }>): void {
63+
for (const { nodeId, bounds } of updates) {
64+
this.quadTree.update(nodeId, bounds)
65+
}
66+
this.invalidateCache()
67+
}
68+
5869
/**
5970
* Remove a node from the spatial index
6071
*/

src/renderer/extensions/vueNodes/components/LGraphNode.vue

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ import {
153153
import { cn } from '@/utils/tailwindUtil'
154154
155155
import { useNodeResize } from '../composables/useNodeResize'
156+
import { calculateIntrinsicSize } from '../utils/calculateIntrinsicSize'
156157
import NodeContent from './NodeContent.vue'
157158
import NodeHeader from './NodeHeader.vue'
158159
import NodeSlots from './NodeSlots.vue'
@@ -245,7 +246,7 @@ onErrorCaptured((error) => {
245246
})
246247
247248
// Use layout system for node position and dragging
248-
const { position, size, zIndex, resize } = useNodeLayout(() => nodeData.id)
249+
const { position, size, zIndex } = useNodeLayout(() => nodeData.id)
249250
const { pointerHandlers, isDragging, dragStyle } = useNodePointerInteractions(
250251
() => nodeData,
251252
handleNodeSelect
@@ -267,13 +268,19 @@ const handleContextMenu = (event: MouseEvent) => {
267268
}
268269
269270
onMounted(() => {
270-
if (size.value && transformState?.camera) {
271-
const scale = transformState.camera.z
272-
const screenSize = {
273-
width: size.value.width * scale,
274-
height: size.value.height * scale
275-
}
276-
resize(screenSize)
271+
// Set initial DOM size from layout store, but respect intrinsic content minimum
272+
if (size.value && nodeContainerRef.value && transformState) {
273+
const intrinsicMin = calculateIntrinsicSize(
274+
nodeContainerRef.value,
275+
transformState.camera.z
276+
)
277+
278+
// Use the larger of stored size or intrinsic minimum
279+
const finalWidth = Math.max(size.value.width, intrinsicMin.width)
280+
const finalHeight = Math.max(size.value.height, intrinsicMin.height)
281+
282+
nodeContainerRef.value.style.width = `${finalWidth}px`
283+
nodeContainerRef.value.style.height = `${finalHeight}px`
277284
}
278285
})
279286

src/renderer/extensions/vueNodes/composables/useNodeResize.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEventListener } from '@vueuse/core'
22
import { ref } from 'vue'
33

44
import type { TransformState } from '@/renderer/core/layout/injectionKeys'
5+
import { calculateIntrinsicSize } from '@/renderer/extensions/vueNodes/utils/calculateIntrinsicSize'
56

67
interface Size {
78
width: number
@@ -53,29 +54,16 @@ export function useNodeResize(
5354
if (!(nodeElement instanceof HTMLElement)) return
5455

5556
const rect = nodeElement.getBoundingClientRect()
56-
57-
// Calculate intrinsic content size once at start
58-
const originalWidth = nodeElement.style.width
59-
const originalHeight = nodeElement.style.height
60-
nodeElement.style.width = 'auto'
61-
nodeElement.style.height = 'auto'
62-
63-
const intrinsicRect = nodeElement.getBoundingClientRect()
64-
65-
// Restore original size
66-
nodeElement.style.width = originalWidth
67-
nodeElement.style.height = originalHeight
68-
69-
// Convert to canvas coordinates using transform state
7057
const scale = transformState.camera.z
58+
59+
// Calculate current size in canvas coordinates
7160
resizeStartSize.value = {
7261
width: rect.width / scale,
7362
height: rect.height / scale
7463
}
75-
intrinsicMinSize.value = {
76-
width: intrinsicRect.width / scale,
77-
height: intrinsicRect.height / scale
78-
}
64+
65+
// Calculate intrinsic content size (minimum based on content)
66+
intrinsicMinSize.value = calculateIntrinsicSize(nodeElement, scale)
7967

8068
const handlePointerMove = (moveEvent: PointerEvent) => {
8169
if (

src/renderer/extensions/vueNodes/layout/useNodeLayout.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,6 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
167167
mutations.moveNode(nodeId, position)
168168
}
169169

170-
/**
171-
* Update node size
172-
*/
173-
function resize(newSize: { width: number; height: number }) {
174-
mutations.setSource(LayoutSource.Vue)
175-
mutations.resizeNode(nodeId, newSize)
176-
}
177-
178170
return {
179171
// Reactive state (via customRef)
180172
layoutRef,
@@ -187,7 +179,6 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
187179

188180
// Mutations
189181
moveTo,
190-
resize,
191182

192183
// Drag handlers
193184
startDrag,

0 commit comments

Comments
 (0)