Skip to content

Commit 5f682ef

Browse files
fix: emit layout change for batch node bounds
1 parent 1df6187 commit 5f682ef

File tree

4 files changed

+113
-16
lines changed

4 files changed

+113
-16
lines changed

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

Lines changed: 62 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,32 @@ class LayoutStoreImpl implements LayoutStore {
10921099
change.nodeIds.push(operation.nodeId)
10931100
}
10941101

1102+
private handleBatchUpdateBounds(
1103+
operation: BatchUpdateBoundsOperation,
1104+
change: LayoutChange
1105+
): void {
1106+
for (const nodeId of operation.nodeIds) {
1107+
const data = operation.bounds[nodeId]
1108+
const ynode = this.ynodes.get(nodeId)
1109+
if (!ynode || !data) continue
1110+
1111+
this.spatialIndex.update(nodeId, data.bounds)
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+
change.nodeIds.push(nodeId)
1121+
}
1122+
1123+
if (change.nodeIds.length) {
1124+
change.type = 'update'
1125+
}
1126+
}
1127+
10951128
private handleCreateLink(
10961129
operation: CreateLinkOperation,
10971130
change: LayoutChange
@@ -1372,19 +1405,38 @@ class LayoutStoreImpl implements LayoutStore {
13721405
const originalSource = this.currentSource
13731406
this.currentSource = LayoutSource.Vue
13741407

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

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 })
1411+
for (const { nodeId, bounds } of updates) {
1412+
const ynode = this.ynodes.get(nodeId)
1413+
if (!ynode) continue
1414+
const currentLayout = yNodeToLayout(ynode)
1415+
1416+
boundsRecord[nodeId] = {
1417+
bounds,
1418+
previousBounds: currentLayout.bounds
13841419
}
1385-
}, this.currentActor)
1420+
nodeIds.push(nodeId)
1421+
}
1422+
1423+
if (!nodeIds.length) {
1424+
this.currentSource = originalSource
1425+
return
1426+
}
1427+
1428+
const operation: BatchUpdateBoundsOperation = {
1429+
type: 'batchUpdateBounds',
1430+
entity: 'node',
1431+
nodeIds,
1432+
bounds: boundsRecord,
1433+
timestamp: Date.now(),
1434+
source: this.currentSource,
1435+
actor: this.currentActor
1436+
}
1437+
1438+
this.applyOperation(operation)
13861439

1387-
// Restore original source
13881440
this.currentSource = originalSource
13891441
}
13901442
}

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

tests-ui/tests/renderer/core/layout/layoutStore.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,45 @@ describe('layoutStore CRDT operations', () => {
176176
unsubscribe()
177177
})
178178

179+
it('should emit change when batch updating node bounds', async () => {
180+
const nodeId = 'test-node-6'
181+
const layout = createTestNode(nodeId)
182+
183+
layoutStore.applyOperation({
184+
type: 'createNode',
185+
entity: 'node',
186+
nodeId,
187+
layout,
188+
timestamp: Date.now(),
189+
source: LayoutSource.External,
190+
actor: 'test'
191+
})
192+
193+
const changes: LayoutChange[] = []
194+
const unsubscribe = layoutStore.onChange((change) => {
195+
changes.push(change)
196+
})
197+
198+
const newBounds = { x: 40, y: 60, width: 220, height: 120 }
199+
layoutStore.batchUpdateNodeBounds([{ nodeId, bounds: newBounds }])
200+
201+
await new Promise((resolve) => setTimeout(resolve, 50))
202+
203+
expect(changes.length).toBeGreaterThan(0)
204+
const lastChange = changes[changes.length - 1]
205+
expect(lastChange.operation.type).toBe('batchUpdateBounds')
206+
if (lastChange.operation.type === 'batchUpdateBounds') {
207+
expect(lastChange.nodeIds).toContain(nodeId)
208+
expect(lastChange.operation.bounds[nodeId]?.bounds).toEqual(newBounds)
209+
}
210+
211+
const nodeRef = layoutStore.getNodeLayoutRef(nodeId)
212+
expect(nodeRef.value?.position).toEqual({ x: 40, y: 60 })
213+
expect(nodeRef.value?.size).toEqual({ width: 220, height: 120 })
214+
215+
unsubscribe()
216+
})
217+
179218
it('should query nodes by spatial bounds', () => {
180219
const nodes = [
181220
{ id: 'node-a', position: { x: 0, y: 0 } },

0 commit comments

Comments
 (0)