Skip to content

Commit bad2ec6

Browse files
persist size changes
1 parent 06b0eec commit bad2ec6

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
@@ -12,6 +12,7 @@ import * as Y from 'yjs'
1212
import { ACTOR_CONFIG } from '@/renderer/core/layout/constants'
1313
import { LayoutSource } from '@/renderer/core/layout/types'
1414
import type {
15+
BatchUpdateBoundsOperation,
1516
Bounds,
1617
CreateLinkOperation,
1718
CreateNodeOperation,
@@ -863,6 +864,12 @@ class LayoutStoreImpl implements LayoutStore {
863864
case 'deleteNode':
864865
this.handleDeleteNode(operation as DeleteNodeOperation, change)
865866
break
867+
case 'batchUpdateBounds':
868+
this.handleBatchUpdateBounds(
869+
operation as BatchUpdateBoundsOperation,
870+
change
871+
)
872+
break
866873
case 'createLink':
867874
this.handleCreateLink(operation as CreateLinkOperation, change)
868875
break
@@ -1091,6 +1098,38 @@ class LayoutStoreImpl implements LayoutStore {
10911098
change.nodeIds.push(operation.nodeId)
10921099
}
10931100

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

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

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

1386-
// Restore original source
13871445
this.currentSource = originalSource
13881446
}
13891447
}

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
@@ -162,14 +162,6 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
162162
mutations.moveNode(nodeId, position)
163163
}
164164

165-
/**
166-
* Update node size
167-
*/
168-
function resize(newSize: { width: number; height: number }) {
169-
mutations.setSource(LayoutSource.Vue)
170-
mutations.resizeNode(nodeId, newSize)
171-
}
172-
173165
return {
174166
// Reactive state (via customRef)
175167
layoutRef,
@@ -182,7 +174,6 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
182174

183175
// Mutations
184176
moveTo,
185-
resize,
186177

187178
// Drag handlers
188179
startDrag,

0 commit comments

Comments
 (0)