@tldiagram/core-ui 1.93.0 → 1.94.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ElementNode.d.ts +5 -1
- package/dist/components/ZUI/ZUICanvas.d.ts +1 -0
- package/dist/index.js +9285 -9742
- package/dist/pages/InfiniteZoom.d.ts +5 -2
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +9 -3
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.test.d.ts +1 -0
- package/dist/pages/ViewEditor/hooks/useViewData.d.ts +7 -3
- package/dist/pages/ViewsGrid.d.ts +9 -1
- package/dist/store/useStore.d.ts +2 -0
- package/package.json +6 -5
- package/src/components/ElementNode.tsx +10 -1
- package/src/components/ElementPanel.tsx +1 -2
- package/src/components/LayoutSection.tsx +27 -11
- package/src/components/ZUI/ZUICanvas.tsx +138 -1
- package/src/pages/InfiniteZoom.tsx +14 -5
- package/src/pages/ViewEditor/context.tsx +4 -1
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.test.ts +30 -0
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +139 -42
- package/src/pages/ViewEditor/hooks/useViewData.ts +4 -3
- package/src/pages/ViewEditor/index.tsx +24 -40
- package/src/pages/Views.tsx +552 -83
- package/src/pages/ViewsGrid.tsx +26 -337
- package/src/store/useStore.test.ts +13 -0
- package/src/store/useStore.ts +42 -0
|
@@ -129,6 +129,12 @@ export function applyNodeChangesWithStructuralSharing(changes: NodeChange[], nod
|
|
|
129
129
|
return didChange ? nextNodes : nodes
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
export function getConnectorDeletionTarget(
|
|
133
|
+
selectedConnector: Connector | null,
|
|
134
|
+
) {
|
|
135
|
+
return selectedConnector?.id ?? null
|
|
136
|
+
}
|
|
137
|
+
|
|
132
138
|
interface CanvasInteractionOptions {
|
|
133
139
|
viewId: number | null
|
|
134
140
|
canEdit: boolean
|
|
@@ -164,12 +170,11 @@ interface CanvasInteractionOptions {
|
|
|
164
170
|
openConnectorPanel: () => void
|
|
165
171
|
closeConnectorPanel: () => void
|
|
166
172
|
selectedElement: LibraryElement | null
|
|
167
|
-
|
|
173
|
+
selectedConnector: Connector | null
|
|
168
174
|
connectors: Connector[]
|
|
169
175
|
layers: ViewLayer[]
|
|
170
176
|
setSelectedElement: React.Dispatch<React.SetStateAction<LibraryElement | null>>
|
|
171
177
|
setSelectedEdge: (e: Connector | null) => void
|
|
172
|
-
setSelectedEdgeId: (id: number | null) => void
|
|
173
178
|
setSelectedProxyConnectorDetails: React.Dispatch<React.SetStateAction<import('../../../crossBranch/types').ProxyConnectorDetails | null>>
|
|
174
179
|
openProxyConnectorPanel: () => void
|
|
175
180
|
closeProxyConnectorPanel: () => void
|
|
@@ -202,6 +207,12 @@ type HandleReconnectDragState = {
|
|
|
202
207
|
hoveredHandleId?: string
|
|
203
208
|
}
|
|
204
209
|
|
|
210
|
+
type InteractionStartOptions = {
|
|
211
|
+
sourceHandle?: string
|
|
212
|
+
clientX?: number
|
|
213
|
+
clientY?: number
|
|
214
|
+
}
|
|
215
|
+
|
|
205
216
|
export function useCanvasInteractions({
|
|
206
217
|
viewId,
|
|
207
218
|
canEdit,
|
|
@@ -237,12 +248,11 @@ export function useCanvasInteractions({
|
|
|
237
248
|
openConnectorPanel: openConnectorPanel,
|
|
238
249
|
closeConnectorPanel: closeConnectorPanel,
|
|
239
250
|
selectedElement,
|
|
240
|
-
|
|
251
|
+
selectedConnector,
|
|
241
252
|
connectors,
|
|
242
253
|
layers,
|
|
243
254
|
setSelectedElement,
|
|
244
255
|
setSelectedEdge,
|
|
245
|
-
setSelectedEdgeId,
|
|
246
256
|
setSelectedProxyConnectorDetails,
|
|
247
257
|
openProxyConnectorPanel,
|
|
248
258
|
closeProxyConnectorPanel,
|
|
@@ -288,6 +298,7 @@ export function useCanvasInteractions({
|
|
|
288
298
|
const suppressNextPaneClickRef = useRef(false)
|
|
289
299
|
const longPressCanvasRef = useRef<{ timer: ReturnType<typeof setTimeout>; clientX: number; clientY: number } | null>(null)
|
|
290
300
|
const pendingConnectionSourceRef = useRef(pendingConnectionSource)
|
|
301
|
+
const pendingConnectionSourceHandleRef = useRef<string | null>(null)
|
|
291
302
|
pendingConnectionSourceRef.current = pendingConnectionSource
|
|
292
303
|
const clickConnectModeRef = useRef(clickConnectMode)
|
|
293
304
|
clickConnectModeRef.current = clickConnectMode
|
|
@@ -323,6 +334,12 @@ export function useCanvasInteractions({
|
|
|
323
334
|
isReconnectingRef.current = false
|
|
324
335
|
}, [clearHandleReconnectListeners])
|
|
325
336
|
|
|
337
|
+
const finalizeConnectorCreate = useCallback(async (connector: Connector) => {
|
|
338
|
+
upsertConnectorGraphSnapshot(connector)
|
|
339
|
+
upsertConnector(connector)
|
|
340
|
+
await refreshElements()
|
|
341
|
+
}, [refreshElements, upsertConnector])
|
|
342
|
+
|
|
326
343
|
// ── Ref-forwarded callbacks ────────────────────────────────────────────────
|
|
327
344
|
const openConnectorPanelRef = useRef(openConnectorPanel)
|
|
328
345
|
openConnectorPanelRef.current = openConnectorPanel
|
|
@@ -372,8 +389,10 @@ export function useCanvasInteractions({
|
|
|
372
389
|
if (!canEdit || viewId === null || !addingElementAt || addingElementAt.mode !== 'add') return
|
|
373
390
|
const { flowX, flowY } = addingElementAt
|
|
374
391
|
const sourceId = pendingConnectionSourceRef.current
|
|
392
|
+
const pendingSourceHandle = pendingConnectionSourceHandleRef.current
|
|
375
393
|
setAddingElementAt(null)
|
|
376
394
|
setPendingConnectionSource(null)
|
|
395
|
+
pendingConnectionSourceHandleRef.current = null
|
|
377
396
|
try {
|
|
378
397
|
const obj = await api.elements.create({ name, kind: '' })
|
|
379
398
|
await api.workspace.views.placements.add(viewId, obj.id, flowX - 100, flowY - 40)
|
|
@@ -387,21 +406,22 @@ export function useCanvasInteractions({
|
|
|
387
406
|
: { sourceHandle: 'right', targetHandle: 'left' }
|
|
388
407
|
const newConnector = await api.workspace.connectors.create(viewId, {
|
|
389
408
|
source_element_id: sourceId, target_element_id: obj.id,
|
|
390
|
-
source_handle: sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
409
|
+
source_handle: pendingSourceHandle ?? sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
391
410
|
})
|
|
392
411
|
const connector = connectorToConnector(newConnector)
|
|
393
|
-
|
|
394
|
-
upsertConnector(connector)
|
|
412
|
+
await finalizeConnectorCreate(connector)
|
|
395
413
|
}
|
|
396
414
|
} catch { /* intentionally empty */ }
|
|
397
|
-
}, [
|
|
415
|
+
}, [addingElementAt, canEdit, finalizeConnectorCreate, refreshElements, rfNodesRef, viewId, viewElementsRef])
|
|
398
416
|
|
|
399
417
|
const handleConfirmExistingElement = useCallback(async (obj: LibraryElement) => {
|
|
400
418
|
if (!canEdit || viewId === null || !addingElementAt || addingElementAt.mode !== 'add') return
|
|
401
419
|
const { flowX, flowY } = addingElementAt
|
|
402
420
|
const sourceId = pendingConnectionSourceRef.current
|
|
421
|
+
const pendingSourceHandle = pendingConnectionSourceHandleRef.current
|
|
403
422
|
setAddingElementAt(null)
|
|
404
423
|
setPendingConnectionSource(null)
|
|
424
|
+
pendingConnectionSourceHandleRef.current = null
|
|
405
425
|
try {
|
|
406
426
|
if (!existingElementIds.has(obj.id)) {
|
|
407
427
|
await api.workspace.views.placements.add(viewId, obj.id, flowX - 100, flowY - 40)
|
|
@@ -419,21 +439,22 @@ export function useCanvasInteractions({
|
|
|
419
439
|
: { sourceHandle: 'right', targetHandle: 'left' }
|
|
420
440
|
const newConnector = await api.workspace.connectors.create(viewId, {
|
|
421
441
|
source_element_id: sourceId, target_element_id: obj.id,
|
|
422
|
-
source_handle: sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
442
|
+
source_handle: pendingSourceHandle ?? sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
423
443
|
})
|
|
424
444
|
const connector = connectorToConnector(newConnector)
|
|
425
|
-
|
|
426
|
-
upsertConnector(connector)
|
|
445
|
+
await finalizeConnectorCreate(connector)
|
|
427
446
|
}
|
|
428
447
|
} catch { /* intentionally empty */ }
|
|
429
|
-
}, [
|
|
448
|
+
}, [addingElementAt, canEdit, existingElementIds, finalizeConnectorCreate, refreshElements, rfNodesRef, viewId, viewElementsRef])
|
|
430
449
|
|
|
431
450
|
const handleConfirmConnectExistingElement = useCallback(async (obj: LibraryElement) => {
|
|
432
451
|
if (!canEdit || viewId === null || !addingElementAt || addingElementAt.mode !== 'connect') return
|
|
433
452
|
const { flowX, flowY } = addingElementAt
|
|
434
453
|
const sourceId = pendingConnectionSourceRef.current
|
|
454
|
+
const pendingSourceHandle = pendingConnectionSourceHandleRef.current
|
|
435
455
|
setAddingElementAt(null)
|
|
436
456
|
setPendingConnectionSource(null)
|
|
457
|
+
pendingConnectionSourceHandleRef.current = null
|
|
437
458
|
if (sourceId == null || sourceId === obj.id) return
|
|
438
459
|
try {
|
|
439
460
|
const sourceNode = rfNodesRef.current.find((n) => n.id === String(sourceId))
|
|
@@ -443,15 +464,14 @@ export function useCanvasInteractions({
|
|
|
443
464
|
const newConnector = await api.workspace.connectors.create(viewId, {
|
|
444
465
|
source_element_id: sourceId,
|
|
445
466
|
target_element_id: obj.id,
|
|
446
|
-
source_handle: sourceHandle,
|
|
467
|
+
source_handle: pendingSourceHandle ?? sourceHandle,
|
|
447
468
|
target_handle: targetHandle,
|
|
448
469
|
direction: 'forward',
|
|
449
470
|
})
|
|
450
471
|
const connector = connectorToConnector(newConnector)
|
|
451
|
-
|
|
452
|
-
upsertConnector(connector)
|
|
472
|
+
await finalizeConnectorCreate(connector)
|
|
453
473
|
} catch { /* intentionally empty */ }
|
|
454
|
-
}, [addingElementAt, canEdit,
|
|
474
|
+
}, [addingElementAt, canEdit, finalizeConnectorCreate, rfNodesRef, viewId])
|
|
455
475
|
|
|
456
476
|
// ── Zoom-in / zoom-out stable callbacks ───────────────────────────────────
|
|
457
477
|
const stableOnZoomIn = useCallback(async (elementId: number) => {
|
|
@@ -541,9 +561,86 @@ export function useCanvasInteractions({
|
|
|
541
561
|
removeElementPlacement(elementId)
|
|
542
562
|
handleElementDeleted(elementId)
|
|
543
563
|
setInteractionSourceId(null)
|
|
564
|
+
pendingConnectionSourceHandleRef.current = null
|
|
544
565
|
} catch { /* intentionally empty */ }
|
|
545
566
|
}, [canEdit, viewId, removeElementPlacement, handleElementDeleted])
|
|
546
567
|
|
|
568
|
+
const connectClickModeToHandle = useCallback(async (targetElementId: number, targetHandle: string) => {
|
|
569
|
+
if (!canEdit) return
|
|
570
|
+
const cid = viewIdRef.current
|
|
571
|
+
const sourceElementId = interactionSourceIdRef.current
|
|
572
|
+
if (cid === null || sourceElementId === null || sourceElementId === targetElementId) return
|
|
573
|
+
|
|
574
|
+
const sourceHandle = pendingConnectionSourceHandleRef.current ??
|
|
575
|
+
(clickConnectModeRef.current?.sourceHandle
|
|
576
|
+
? getLogicalHandleId(clickConnectModeRef.current.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? DEFAULT_SOURCE_HANDLE_SIDE
|
|
577
|
+
: DEFAULT_SOURCE_HANDLE_SIDE)
|
|
578
|
+
const logicalTargetHandle = getLogicalHandleId(targetHandle, DEFAULT_TARGET_HANDLE_SIDE) ?? DEFAULT_TARGET_HANDLE_SIDE
|
|
579
|
+
|
|
580
|
+
setInteractionSourceId(null)
|
|
581
|
+
setPendingConnectionSource(null)
|
|
582
|
+
pendingConnectionSourceHandleRef.current = null
|
|
583
|
+
setClickConnectMode(null)
|
|
584
|
+
setClickConnectCursorPos(null)
|
|
585
|
+
setConnectGhostPos(null)
|
|
586
|
+
|
|
587
|
+
try {
|
|
588
|
+
const newConnector = await api.workspace.connectors.create(cid, {
|
|
589
|
+
source_element_id: sourceElementId,
|
|
590
|
+
target_element_id: targetElementId,
|
|
591
|
+
source_handle: sourceHandle,
|
|
592
|
+
target_handle: logicalTargetHandle,
|
|
593
|
+
direction: 'forward',
|
|
594
|
+
})
|
|
595
|
+
const connector = connectorToConnector(newConnector)
|
|
596
|
+
await finalizeConnectorCreate(connector)
|
|
597
|
+
} catch { /* intentionally empty */ }
|
|
598
|
+
}, [canEdit, finalizeConnectorCreate, interactionSourceIdRef, viewIdRef])
|
|
599
|
+
|
|
600
|
+
const stableOnInteractionStart = useCallback((elementId: number, options?: InteractionStartOptions) => {
|
|
601
|
+
if (!canEdit) return
|
|
602
|
+
const sourceHandle = options?.sourceHandle
|
|
603
|
+
|
|
604
|
+
if (sourceHandle) {
|
|
605
|
+
const cursorPos = options?.clientX !== undefined && options.clientY !== undefined
|
|
606
|
+
? { x: options.clientX, y: options.clientY }
|
|
607
|
+
: lastMousePosRef.current
|
|
608
|
+
? { x: lastMousePosRef.current.clientX, y: lastMousePosRef.current.clientY }
|
|
609
|
+
: null
|
|
610
|
+
if (!cursorPos) return
|
|
611
|
+
|
|
612
|
+
const activeSourceId = interactionSourceIdRef.current
|
|
613
|
+
if (activeSourceId !== null && activeSourceId !== elementId) {
|
|
614
|
+
void connectClickModeToHandle(elementId, sourceHandle)
|
|
615
|
+
return
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
pendingConnectionSourceHandleRef.current = getLogicalHandleId(sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? DEFAULT_SOURCE_HANDLE_SIDE
|
|
619
|
+
setInteractionSourceId(elementId)
|
|
620
|
+
setPendingConnectionSource(null)
|
|
621
|
+
setAddingElementAt(null)
|
|
622
|
+
setClickConnectMode({
|
|
623
|
+
sourceNodeId: String(elementId),
|
|
624
|
+
sourceHandle: ensureVisualHandleId(sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? sourceHandle,
|
|
625
|
+
})
|
|
626
|
+
setClickConnectCursorPos(cursorPos)
|
|
627
|
+
setConnectGhostPos(cursorPos)
|
|
628
|
+
return
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const isCancelling = interactionSourceIdRef.current === elementId
|
|
632
|
+
const nextSourceId = isCancelling ? null : elementId
|
|
633
|
+
|
|
634
|
+
if (isCancelling) pendingConnectionSourceHandleRef.current = null
|
|
635
|
+
setInteractionSourceId(nextSourceId)
|
|
636
|
+
|
|
637
|
+
if (isCancelling) {
|
|
638
|
+
setClickConnectMode(null)
|
|
639
|
+
setClickConnectCursorPos(null)
|
|
640
|
+
setConnectGhostPos(null)
|
|
641
|
+
}
|
|
642
|
+
}, [canEdit, connectClickModeToHandle, interactionSourceIdRef])
|
|
643
|
+
|
|
547
644
|
// ── Node/connector changes ─────────────────────────────────────────────────────
|
|
548
645
|
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
|
549
646
|
if (!canEdit) {
|
|
@@ -614,10 +711,9 @@ export function useCanvasInteractions({
|
|
|
614
711
|
direction: 'forward', style: 'bezier',
|
|
615
712
|
})
|
|
616
713
|
const connector = connectorToConnector(newConnector)
|
|
617
|
-
|
|
618
|
-
upsertConnector(connector)
|
|
714
|
+
await finalizeConnectorCreate(connector)
|
|
619
715
|
} catch { /* intentionally empty */ }
|
|
620
|
-
}, [canEdit,
|
|
716
|
+
}, [canEdit, finalizeConnectorCreate, viewId])
|
|
621
717
|
|
|
622
718
|
const onConnectStart = useCallback((_: React.MouseEvent | React.TouchEvent, { nodeId }: OnConnectStartParams) => {
|
|
623
719
|
if (!canEdit || isReconnectingRef.current) return
|
|
@@ -677,15 +773,14 @@ export function useCanvasInteractions({
|
|
|
677
773
|
source_handle: sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
678
774
|
}).then((connector) => {
|
|
679
775
|
const next = connectorToConnector(connector)
|
|
680
|
-
|
|
681
|
-
upsertConnector(next)
|
|
776
|
+
void finalizeConnectorCreate(next)
|
|
682
777
|
}).catch(() => { /* intentionally empty */ })
|
|
683
778
|
} else {
|
|
684
779
|
setPendingConnectionSource(sourceElementId)
|
|
685
780
|
suppressNextPaneClickRef.current = true
|
|
686
781
|
showAddingElementAt(clientX, clientY, true, 'connect', 'shiftKey' in event && event.shiftKey)
|
|
687
782
|
}
|
|
688
|
-
}, [canEdit,
|
|
783
|
+
}, [canEdit, finalizeConnectorCreate, showAddingElementAt, rfNodesRef, viewIdRef])
|
|
689
784
|
|
|
690
785
|
// ── Reconnect ──────────────────────────────────────────────────────────────
|
|
691
786
|
const performReconnect = useCallback(async (oldConnector: RFEdge, newConnection: Connection) => {
|
|
@@ -695,7 +790,6 @@ export function useCanvasInteractions({
|
|
|
695
790
|
const targetId = parseNumericId(newConnection.target)
|
|
696
791
|
if (edgeId === null || sourceId === null || targetId === null) return
|
|
697
792
|
setRfEdges((eds) => reconnectEdge(oldConnector, newConnection, eds))
|
|
698
|
-
setSelectedEdgeId(null)
|
|
699
793
|
try {
|
|
700
794
|
const existingData = oldConnector.data as Connector
|
|
701
795
|
const sourceHandle = getLogicalHandleId(newConnection.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE)
|
|
@@ -713,7 +807,7 @@ export function useCanvasInteractions({
|
|
|
713
807
|
upsertConnectorGraphSnapshot(connector)
|
|
714
808
|
replaceConnector(connector)
|
|
715
809
|
} catch { /* intentionally empty */ }
|
|
716
|
-
}, [canEdit, replaceConnector, viewId, setRfEdges
|
|
810
|
+
}, [canEdit, replaceConnector, viewId, setRfEdges])
|
|
717
811
|
const onReconnect = useCallback(async (oldConnector: RFEdge, newConnection: Connection) => {
|
|
718
812
|
await performReconnect(oldConnector, newConnection)
|
|
719
813
|
}, [performReconnect])
|
|
@@ -904,7 +998,6 @@ export function useCanvasInteractions({
|
|
|
904
998
|
setSelectedElement(null)
|
|
905
999
|
closeElementPanel()
|
|
906
1000
|
setSelectedEdge(null)
|
|
907
|
-
setSelectedEdgeId(null)
|
|
908
1001
|
closeConnectorPanel()
|
|
909
1002
|
setSelectedProxyConnectorDetails((rfConnector.data as { details?: import('../../../crossBranch/types').ProxyConnectorDetails }).details ?? null)
|
|
910
1003
|
openProxyConnectorPanel()
|
|
@@ -912,16 +1005,14 @@ export function useCanvasInteractions({
|
|
|
912
1005
|
}
|
|
913
1006
|
const clickedId = parseNumericId(rfConnector.id)
|
|
914
1007
|
if (clickedId === null) return
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
924
|
-
}, [closeConnectorPanel, closeElementPanel, connectors, openProxyConnectorPanel, selectedEdgeId, setSelectedEdge, setSelectedEdgeId, setSelectedElement, setSelectedProxyConnectorDetails])
|
|
1008
|
+
const connector = connectors.find((e) => e.id === clickedId)
|
|
1009
|
+
if (!connector) return
|
|
1010
|
+
setSelectedElement(null)
|
|
1011
|
+
closeElementPanel()
|
|
1012
|
+
setSelectedProxyConnectorDetails(null)
|
|
1013
|
+
setSelectedEdge(connector)
|
|
1014
|
+
openConnectorPanelRef.current()
|
|
1015
|
+
}, [closeElementPanel, connectors, openProxyConnectorPanel, setSelectedEdge, setSelectedElement, setSelectedProxyConnectorDetails])
|
|
925
1016
|
|
|
926
1017
|
// ── Pane interactions ─────────────────────────────────────────────────────
|
|
927
1018
|
const onPaneClick = useCallback((e: React.MouseEvent) => {
|
|
@@ -930,7 +1021,6 @@ export function useCanvasInteractions({
|
|
|
930
1021
|
setReconnectPicking(null)
|
|
931
1022
|
setSelectedElement(null)
|
|
932
1023
|
setSelectedEdge(null)
|
|
933
|
-
setSelectedEdgeId(null)
|
|
934
1024
|
setSelectedProxyConnectorDetails(null)
|
|
935
1025
|
setConnectorLongPressMenu(null)
|
|
936
1026
|
setCanvasMenu(null)
|
|
@@ -953,14 +1043,18 @@ export function useCanvasInteractions({
|
|
|
953
1043
|
} else {
|
|
954
1044
|
setInteractionSourceId(null)
|
|
955
1045
|
setPendingConnectionSource(sourceId)
|
|
1046
|
+
pendingConnectionSourceHandleRef.current = clickConnectModeRef.current?.sourceHandle
|
|
1047
|
+
? getLogicalHandleId(clickConnectModeRef.current.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? DEFAULT_SOURCE_HANDLE_SIDE
|
|
1048
|
+
: pendingConnectionSourceHandleRef.current
|
|
956
1049
|
showAddingElementAt(e.clientX, e.clientY, true, 'connect', e.shiftKey)
|
|
957
1050
|
}
|
|
958
1051
|
return
|
|
959
1052
|
}
|
|
960
1053
|
setInteractionSourceId(null)
|
|
961
1054
|
setPendingConnectionSource(null)
|
|
1055
|
+
pendingConnectionSourceHandleRef.current = null
|
|
962
1056
|
setAddingElementAt(null)
|
|
963
|
-
}, [stableOnConnectTo, showAddingElementAt, closeElementPanel, closeConnectorPanel, closeProxyConnectorPanel, rfNodesRef, interactionSourceIdRef, setSelectedElement, setSelectedEdge,
|
|
1057
|
+
}, [stableOnConnectTo, showAddingElementAt, closeElementPanel, closeConnectorPanel, closeProxyConnectorPanel, rfNodesRef, interactionSourceIdRef, setSelectedElement, setSelectedEdge, setSelectedProxyConnectorDetails])
|
|
964
1058
|
|
|
965
1059
|
const onPaneContextMenu = useCallback((e: React.MouseEvent) => {
|
|
966
1060
|
e.preventDefault()
|
|
@@ -1124,10 +1218,12 @@ export function useCanvasInteractions({
|
|
|
1124
1218
|
setSelectedElement(null)
|
|
1125
1219
|
closeElementPanel()
|
|
1126
1220
|
}
|
|
1127
|
-
} else
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1221
|
+
} else {
|
|
1222
|
+
const connectorId = getConnectorDeletionTarget(selectedConnector)
|
|
1223
|
+
if (connectorId === null) return
|
|
1224
|
+
api.workspace.connectors.delete('', connectorId).then(() => {
|
|
1225
|
+
handleConnectorDeleted(connectorId)
|
|
1226
|
+
setSelectedEdge(null)
|
|
1131
1227
|
closeConnectorPanel()
|
|
1132
1228
|
}).catch(() => { /* intentionally empty */ })
|
|
1133
1229
|
}
|
|
@@ -1236,7 +1332,7 @@ export function useCanvasInteractions({
|
|
|
1236
1332
|
}
|
|
1237
1333
|
window.addEventListener('keydown', handler)
|
|
1238
1334
|
return () => window.removeEventListener('keydown', handler)
|
|
1239
|
-
}, [canEdit, refreshGrid, selectedElement,
|
|
1335
|
+
}, [canEdit, refreshGrid, selectedElement, selectedConnector, viewId, stableOnRemoveElement, handleConnectorDeleted, handleElementPermanentlyDeleted, closeElementPanel, closeConnectorPanel, viewIdRef, incomingLinksRef, treeDataRef, navigateRef, rfNodesRef, viewElementsRef, setLinksMap, showAddingElementAt, setSelectedElement, setSelectedEdge, containerRef, linksMapRef])
|
|
1240
1336
|
|
|
1241
1337
|
// ── DnD handlers ──────────────────────────────────────────────────────────
|
|
1242
1338
|
const onDragOver = useCallback((e: React.DragEvent) => {
|
|
@@ -1351,6 +1447,7 @@ export function useCanvasInteractions({
|
|
|
1351
1447
|
stableOnHoverZoom,
|
|
1352
1448
|
stableOnRemoveElement,
|
|
1353
1449
|
stableOnConnectTo,
|
|
1450
|
+
stableOnInteractionStart,
|
|
1354
1451
|
stableOnStartHandleReconnect,
|
|
1355
1452
|
showAddingElementAt,
|
|
1356
1453
|
// RF event handlers
|
|
@@ -23,7 +23,7 @@ interface ViewDataOptions {
|
|
|
23
23
|
viewId: number | null
|
|
24
24
|
interactionSourceId: number | null
|
|
25
25
|
clickConnectMode: { sourceNodeId: string; sourceHandle: string; targetHandle?: string } | null
|
|
26
|
-
|
|
26
|
+
selectedConnector: Connector | null
|
|
27
27
|
activeTags: string[]
|
|
28
28
|
hiddenLayerTags: string[]
|
|
29
29
|
hoveredLayerTags: string[] | null
|
|
@@ -35,7 +35,7 @@ interface ViewDataOptions {
|
|
|
35
35
|
stableOnNavigateToView: (id: number) => void
|
|
36
36
|
stableOnSelect: (obj: PlacedElement) => void
|
|
37
37
|
stableOnOpenCodePreview: (elementId: number) => void
|
|
38
|
-
stableOnInteractionStart: (elementId: number) => void
|
|
38
|
+
stableOnInteractionStart: (elementId: number, options?: { sourceHandle?: string; clientX?: number; clientY?: number }) => void
|
|
39
39
|
stableOnConnectTo: (targetElementId: number) => Promise<void>
|
|
40
40
|
stableOnStartHandleReconnect: (args: { edgeId: string; endpoint: 'source' | 'target'; handleId: string; clientX: number; clientY: number }) => void
|
|
41
41
|
stableOnRemoveElement: (elementId: number) => Promise<void>
|
|
@@ -152,7 +152,7 @@ export function useViewData({
|
|
|
152
152
|
viewId,
|
|
153
153
|
interactionSourceId,
|
|
154
154
|
clickConnectMode,
|
|
155
|
-
|
|
155
|
+
selectedConnector,
|
|
156
156
|
activeTags,
|
|
157
157
|
hiddenLayerTags,
|
|
158
158
|
hoveredLayerTags,
|
|
@@ -170,6 +170,7 @@ export function useViewData({
|
|
|
170
170
|
stableOnHoverZoom,
|
|
171
171
|
hoveredZoomRef,
|
|
172
172
|
}: ViewDataOptions) {
|
|
173
|
+
const selectedEdgeId = selectedConnector?.id ?? null
|
|
173
174
|
const queryClient = useQueryClient()
|
|
174
175
|
const view = useStore((state) => state.view)
|
|
175
176
|
const setView = useStore((state) => state.setView)
|
|
@@ -85,7 +85,7 @@ import { useCrossBranchContextSettings } from '../../crossBranch/settings'
|
|
|
85
85
|
import { removeConnectorGraphSnapshot, upsertConnectorGraphSnapshot, useWorkspaceGraphSnapshot } from '../../crossBranch/store'
|
|
86
86
|
import type { ProxyConnectorDetails } from '../../crossBranch/types'
|
|
87
87
|
import { useDemoRevealViewport, type ViewEditorDemoOptions } from '../../demo/viewEditor'
|
|
88
|
-
import { useStore } from '../../store/useStore'
|
|
88
|
+
import { buildElementLibraryItems, useStore } from '../../store/useStore'
|
|
89
89
|
|
|
90
90
|
const nodeTypes = {
|
|
91
91
|
elementNode: ElementNode,
|
|
@@ -201,10 +201,8 @@ function ViewEditorInner({
|
|
|
201
201
|
const closeImportModalRef = useRef(importModal.onClose)
|
|
202
202
|
closeImportModalRef.current = importModal.onClose
|
|
203
203
|
|
|
204
|
-
|
|
205
204
|
const [selectedElement, setSelectedElement] = useState<WorkspaceElement | null>(null)
|
|
206
205
|
const [selectedEdge, setSelectedEdge] = useState<Connector | null>(null)
|
|
207
|
-
const [selectedEdgeId, setSelectedEdgeId] = useState<number | null>(null)
|
|
208
206
|
const [selectedProxyConnectorDetails, setSelectedProxyConnectorDetails] = useState<ProxyConnectorDetails | null>(null)
|
|
209
207
|
const [previewElement, setPreviewElement] = useState<PlacedElement | null>(null)
|
|
210
208
|
const [libraryOpen, setLibraryOpen] = useState(() => {
|
|
@@ -228,6 +226,7 @@ function ViewEditorInner({
|
|
|
228
226
|
const setStoreSnapToGrid = useStore((state) => state.setSnapToGrid)
|
|
229
227
|
const upsertStoreConnector = useStore((state) => state.upsertConnector)
|
|
230
228
|
const removeStoreConnector = useStore((state) => state.removeConnector)
|
|
229
|
+
const refreshElementsRef = useRef<() => Promise<void>>(async () => {})
|
|
231
230
|
const setSnapToGrid = useCallback((snap: boolean) => {
|
|
232
231
|
setStoreSnapToGrid(snap)
|
|
233
232
|
if (typeof window !== 'undefined') localStorage.setItem('diag:snapToGrid', String(snap))
|
|
@@ -348,6 +347,7 @@ function ViewEditorInner({
|
|
|
348
347
|
|
|
349
348
|
// stableOnConnectTo is wired after canvasInteractions is declared
|
|
350
349
|
const stableOnConnectToRef = useRef<(targetElementId: number) => Promise<void>>(async () => { })
|
|
350
|
+
const stableOnInteractionStartRef = useRef<(elementId: number, options?: { sourceHandle?: string; clientX?: number; clientY?: number }) => void>(() => { })
|
|
351
351
|
const stableOnStartHandleReconnectRef = useRef<(args: { edgeId: string; endpoint: 'source' | 'target'; handleId: string; clientX: number; clientY: number }) => void>(() => { })
|
|
352
352
|
|
|
353
353
|
// ── Drawing engine ────────────────────────────────────────────────────────
|
|
@@ -370,7 +370,7 @@ function ViewEditorInner({
|
|
|
370
370
|
viewId,
|
|
371
371
|
interactionSourceId: interactionSourceIdRef.current,
|
|
372
372
|
clickConnectMode: null, // wired after canvasInteractions
|
|
373
|
-
|
|
373
|
+
selectedConnector: selectedEdge,
|
|
374
374
|
activeTags,
|
|
375
375
|
hiddenLayerTags,
|
|
376
376
|
hoveredLayerTags,
|
|
@@ -381,7 +381,6 @@ function ViewEditorInner({
|
|
|
381
381
|
stableOnNavigateToView: useCallback((id: number) => { stableOnNavigateToViewRef.current(id) }, []),
|
|
382
382
|
stableOnSelect: useCallback((obj: PlacedElement) => {
|
|
383
383
|
setSelectedEdge(null)
|
|
384
|
-
setSelectedEdgeId(null)
|
|
385
384
|
setSelectedProxyConnectorDetails(null)
|
|
386
385
|
closeProxyConnectorPanelRef.current()
|
|
387
386
|
closeConnectorPanelRef.current()
|
|
@@ -401,10 +400,9 @@ function ViewEditorInner({
|
|
|
401
400
|
openCodePreviewRef.current()
|
|
402
401
|
}
|
|
403
402
|
}, []),
|
|
404
|
-
stableOnInteractionStart: useCallback((elementId: number) => {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}, [canEdit]),
|
|
403
|
+
stableOnInteractionStart: useCallback((elementId: number, options?: { sourceHandle?: string; clientX?: number; clientY?: number }) => {
|
|
404
|
+
stableOnInteractionStartRef.current(elementId, options)
|
|
405
|
+
}, []),
|
|
408
406
|
stableOnConnectTo: useCallback(async (targetElementId: number) => {
|
|
409
407
|
await stableOnConnectToRef.current(targetElementId)
|
|
410
408
|
}, []),
|
|
@@ -432,6 +430,7 @@ function ViewEditorInner({
|
|
|
432
430
|
handleElementDeleted, handleElementPermanentlyDeleted, handleElementSaved,
|
|
433
431
|
setAllElements: _setAllElements,
|
|
434
432
|
} = data
|
|
433
|
+
refreshElementsRef.current = refreshElements
|
|
435
434
|
|
|
436
435
|
const tagCounts = useMemo(() => {
|
|
437
436
|
const counts: Record<string, number> = {}
|
|
@@ -499,27 +498,7 @@ function ViewEditorInner({
|
|
|
499
498
|
return unsub
|
|
500
499
|
}, [fitView, viewId, refreshElements])
|
|
501
500
|
|
|
502
|
-
const existingElements = useMemo(() =>
|
|
503
|
-
return viewElements.map(obj => ({
|
|
504
|
-
id: obj.element_id,
|
|
505
|
-
name: obj.name,
|
|
506
|
-
kind: obj.kind,
|
|
507
|
-
description: obj.description,
|
|
508
|
-
technology: obj.technology,
|
|
509
|
-
url: obj.url,
|
|
510
|
-
logo_url: obj.logo_url,
|
|
511
|
-
technology_connectors: obj.technology_connectors,
|
|
512
|
-
tags: obj.tags,
|
|
513
|
-
repo: obj.repo,
|
|
514
|
-
branch: obj.branch,
|
|
515
|
-
file_path: obj.file_path,
|
|
516
|
-
language: obj.language,
|
|
517
|
-
created_at: '',
|
|
518
|
-
updated_at: '',
|
|
519
|
-
has_view: false,
|
|
520
|
-
view_label: null,
|
|
521
|
-
} as WorkspaceElement))
|
|
522
|
-
}, [viewElements])
|
|
501
|
+
const existingElements = useMemo(() => buildElementLibraryItems(allElements, viewElements), [allElements, viewElements])
|
|
523
502
|
|
|
524
503
|
const availableTags = useMemo(() => {
|
|
525
504
|
const tags = new Set<string>()
|
|
@@ -551,7 +530,6 @@ function ViewEditorInner({
|
|
|
551
530
|
const match = viewElements.find((element) => element.element_id === requestedElementId)
|
|
552
531
|
if (!match) return
|
|
553
532
|
setSelectedEdge(null)
|
|
554
|
-
setSelectedEdgeId(null)
|
|
555
533
|
setSelectedProxyConnectorDetails(null)
|
|
556
534
|
closeConnectorPanelRef.current()
|
|
557
535
|
closeProxyConnectorPanelRef.current()
|
|
@@ -654,10 +632,10 @@ function ViewEditorInner({
|
|
|
654
632
|
closeElementPanel: useCallback(() => closeElementPanelRef.current(), []),
|
|
655
633
|
openConnectorPanel: useCallback(() => openConnectorPanelRef.current(), []),
|
|
656
634
|
closeConnectorPanel: useCallback(() => closeConnectorPanelRef.current(), []),
|
|
657
|
-
selectedElement,
|
|
635
|
+
selectedElement, selectedConnector: selectedEdge, connectors,
|
|
658
636
|
layers,
|
|
659
637
|
setSelectedElement,
|
|
660
|
-
setSelectedEdge,
|
|
638
|
+
setSelectedEdge,
|
|
661
639
|
setSelectedProxyConnectorDetails,
|
|
662
640
|
openProxyConnectorPanel: useCallback(() => openProxyConnectorPanelRef.current(), []),
|
|
663
641
|
closeProxyConnectorPanel: useCallback(() => closeProxyConnectorPanelRef.current(), []),
|
|
@@ -665,6 +643,7 @@ function ViewEditorInner({
|
|
|
665
643
|
handleConnectorDeleted: useCallback((edgeId: number) => {
|
|
666
644
|
if (viewId != null) removeConnectorGraphSnapshot(viewId, edgeId)
|
|
667
645
|
removeStoreConnector(edgeId)
|
|
646
|
+
void refreshElementsRef.current()
|
|
668
647
|
}, [removeStoreConnector, viewId]),
|
|
669
648
|
handleUpdateTags,
|
|
670
649
|
drawingCanvasRef,
|
|
@@ -678,8 +657,9 @@ function ViewEditorInner({
|
|
|
678
657
|
stableOnNavigateToViewRef.current = canvas.stableOnNavigateToView
|
|
679
658
|
stableOnRemoveElementRef.current = canvas.stableOnRemoveElement
|
|
680
659
|
stableOnConnectToRef.current = canvas.stableOnConnectTo
|
|
660
|
+
stableOnInteractionStartRef.current = canvas.stableOnInteractionStart
|
|
681
661
|
stableOnStartHandleReconnectRef.current = canvas.stableOnStartHandleReconnect
|
|
682
|
-
}, [canvas.stableOnZoomIn, canvas.stableOnZoomOut, canvas.stableOnNavigateToView, canvas.stableOnRemoveElement, canvas.stableOnConnectTo, canvas.stableOnStartHandleReconnect])
|
|
662
|
+
}, [canvas.stableOnZoomIn, canvas.stableOnZoomOut, canvas.stableOnNavigateToView, canvas.stableOnRemoveElement, canvas.stableOnConnectTo, canvas.stableOnInteractionStart, canvas.stableOnStartHandleReconnect])
|
|
683
663
|
const viewName = view?.name ?? null
|
|
684
664
|
|
|
685
665
|
const [expandedAncestorGroups, setExpandedAncestorGroups] = useState<Set<string>>(new Set())
|
|
@@ -702,7 +682,6 @@ function ViewEditorInner({
|
|
|
702
682
|
onSelectProxyDetails: useCallback((details: ProxyConnectorDetails) => {
|
|
703
683
|
setSelectedElement(null)
|
|
704
684
|
setSelectedEdge(null)
|
|
705
|
-
setSelectedEdgeId(null)
|
|
706
685
|
closeConnectorPanelRef.current()
|
|
707
686
|
closeElementPanelRef.current()
|
|
708
687
|
setSelectedProxyConnectorDetails(details)
|
|
@@ -915,7 +894,7 @@ function ViewEditorInner({
|
|
|
915
894
|
return () => observer.disconnect()
|
|
916
895
|
}, [maybeFitView])
|
|
917
896
|
|
|
918
|
-
useEffect(() => {
|
|
897
|
+
useEffect(() => { needsFitView.current = true }, [viewId])
|
|
919
898
|
|
|
920
899
|
// ── Dynamic viewport bounds ────────────────────────────────────────────────
|
|
921
900
|
useEffect(() => {
|
|
@@ -1023,10 +1002,15 @@ function ViewEditorInner({
|
|
|
1023
1002
|
upsertConnectorGraphSnapshot(updated)
|
|
1024
1003
|
upsertStoreConnector(updated)
|
|
1025
1004
|
}, [upsertStoreConnector])
|
|
1026
|
-
const
|
|
1005
|
+
const handleConnectorDeleted = useCallback((edgeId: number) => {
|
|
1027
1006
|
if (viewId != null) removeConnectorGraphSnapshot(viewId, edgeId)
|
|
1028
1007
|
removeStoreConnector(edgeId)
|
|
1029
|
-
|
|
1008
|
+
void refreshElements()
|
|
1009
|
+
}, [refreshElements, removeStoreConnector, viewId])
|
|
1010
|
+
const handleConnectorDeleteInPanel = useCallback((edgeId: number) => {
|
|
1011
|
+
handleConnectorDeleted(edgeId)
|
|
1012
|
+
setSelectedEdge(null)
|
|
1013
|
+
}, [handleConnectorDeleted, setSelectedEdge])
|
|
1030
1014
|
const handleViewSave = useCallback((updated: ViewTreeNode) => setView(updated), [setView])
|
|
1031
1015
|
|
|
1032
1016
|
// ── Library helpers ────────────────────────────────────────────────────────
|
|
@@ -1419,8 +1403,8 @@ function ViewEditorInner({
|
|
|
1419
1403
|
onSave={handleConnectorSave} autoSave
|
|
1420
1404
|
onDelete={handleConnectorDeleteInPanel}
|
|
1421
1405
|
hasBackdrop={isMobileLayout}
|
|
1422
|
-
|
|
1423
|
-
|
|
1406
|
+
connectorPanelAfterContentSlot={connectorPanelAfterContentSlot}
|
|
1407
|
+
/>
|
|
1424
1408
|
<ProxyConnectorPanel
|
|
1425
1409
|
isOpen={proxyConnectorPanel.isOpen}
|
|
1426
1410
|
onClose={proxyConnectorPanel.onClose}
|