@tldiagram/core-ui 2.0.4 → 2.0.5
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/index.js +5062 -5031
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +1 -0
- package/dist/utils/string.d.ts +4 -0
- package/dist/utils/string.test.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/ProxyConnectorPanel.tsx +3 -2
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +38 -0
- package/src/pages/ViewEditor/hooks/useViewData.ts +12 -2
- package/src/pages/ViewEditor/index.tsx +18 -13
- package/src/pages/ViewsGrid.tsx +2 -2
- package/src/utils/string.test.ts +19 -0
- package/src/utils/string.ts +7 -0
|
@@ -173,6 +173,7 @@ export declare function useCanvasInteractions({ viewId, canEdit, drawingMode: _d
|
|
|
173
173
|
clientX: number;
|
|
174
174
|
clientY: number;
|
|
175
175
|
}) => void;
|
|
176
|
+
stableOnReconnectPick: (targetElementId: number) => Promise<boolean>;
|
|
176
177
|
showAddingElementAt: (clientX: number, clientY: number, expandResults?: boolean, mode?: "add" | "connect", forceConnect?: boolean) => void;
|
|
177
178
|
onNodesChange: (changes: NodeChange[]) => void;
|
|
178
179
|
onEdgesChange: (changes: EdgeChange[]) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import PanelHeader from './PanelHeader'
|
|
|
6
6
|
import { ChevronRightIcon, NavigationIcon, TrashIcon, EditIcon } from './Icons'
|
|
7
7
|
import { useViewEditorContext } from '../pages/ViewEditor/context'
|
|
8
8
|
import type { Connector } from '../types'
|
|
9
|
+
import { truncate } from '../utils/string'
|
|
9
10
|
|
|
10
11
|
interface Props {
|
|
11
12
|
isOpen: boolean
|
|
@@ -83,11 +84,11 @@ export default function ProxyConnectorPanel({
|
|
|
83
84
|
<VStack align="start" spacing={1} flex={1}>
|
|
84
85
|
<HStack spacing={2}>
|
|
85
86
|
<Text color="white" fontSize="sm" fontWeight="semibold" isTruncated>
|
|
86
|
-
{leaf.source.actualElementName}
|
|
87
|
+
{truncate(leaf.source.actualElementName)}
|
|
87
88
|
</Text>
|
|
88
89
|
<Icon as={ChevronRightIcon} color="whiteAlpha.400" />
|
|
89
90
|
<Text color="white" fontSize="sm" fontWeight="semibold" isTruncated>
|
|
90
|
-
{leaf.target.actualElementName}
|
|
91
|
+
{truncate(leaf.target.actualElementName)}
|
|
91
92
|
</Text>
|
|
92
93
|
</HStack>
|
|
93
94
|
|
|
@@ -1008,6 +1008,43 @@ export function useCanvasInteractions({
|
|
|
1008
1008
|
document.addEventListener('pointercancel', up)
|
|
1009
1009
|
}, [canEdit, clearHandleReconnectListeners, performReconnect, rfNodesRef, _rfEdgesRef, syncHandleReconnectDrag])
|
|
1010
1010
|
|
|
1011
|
+
const stableOnReconnectPick = useCallback(async (targetElementId: number) => {
|
|
1012
|
+
const picking = reconnectPickingRef.current
|
|
1013
|
+
if (!canEdit || !picking) return false
|
|
1014
|
+
|
|
1015
|
+
const oldConnector = _rfEdgesRef.current.find((candidate) => candidate.id === String(picking.edgeId))
|
|
1016
|
+
const pickedNode = rfNodesRef.current.find((node) => node.id === String(targetElementId))
|
|
1017
|
+
if (!oldConnector || !pickedNode) return false
|
|
1018
|
+
|
|
1019
|
+
const fixedNodeId = picking.endpoint === 'source' ? oldConnector.target : oldConnector.source
|
|
1020
|
+
if (fixedNodeId === pickedNode.id) return false
|
|
1021
|
+
const fixedNode = rfNodesRef.current.find((node) => node.id === fixedNodeId)
|
|
1022
|
+
if (!fixedNode) return false
|
|
1023
|
+
|
|
1024
|
+
const closest = picking.endpoint === 'source'
|
|
1025
|
+
? findClosestHandles(pickedNode, fixedNode)
|
|
1026
|
+
: findClosestHandles(fixedNode, pickedNode)
|
|
1027
|
+
const newConnection: Connection = picking.endpoint === 'source'
|
|
1028
|
+
? {
|
|
1029
|
+
source: pickedNode.id,
|
|
1030
|
+
sourceHandle: ensureVisualHandleId(closest.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? closest.sourceHandle,
|
|
1031
|
+
target: fixedNode.id,
|
|
1032
|
+
targetHandle: ensureVisualHandleId(closest.targetHandle, DEFAULT_TARGET_HANDLE_SIDE) ?? closest.targetHandle,
|
|
1033
|
+
}
|
|
1034
|
+
: {
|
|
1035
|
+
source: fixedNode.id,
|
|
1036
|
+
sourceHandle: ensureVisualHandleId(closest.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? closest.sourceHandle,
|
|
1037
|
+
target: pickedNode.id,
|
|
1038
|
+
targetHandle: ensureVisualHandleId(closest.targetHandle, DEFAULT_TARGET_HANDLE_SIDE) ?? closest.targetHandle,
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
reconnectPickingRef.current = null
|
|
1042
|
+
setReconnectPicking(null)
|
|
1043
|
+
setConnectorLongPressMenu(null)
|
|
1044
|
+
await performReconnect(oldConnector, newConnection)
|
|
1045
|
+
return true
|
|
1046
|
+
}, [canEdit, _rfEdgesRef, performReconnect, rfNodesRef])
|
|
1047
|
+
|
|
1011
1048
|
// ── Click-connect ghost cursor tracking ────────────────────────────────────
|
|
1012
1049
|
useEffect(() => {
|
|
1013
1050
|
if (!clickConnectMode) {
|
|
@@ -1528,6 +1565,7 @@ export function useCanvasInteractions({
|
|
|
1528
1565
|
stableOnConnectTo,
|
|
1529
1566
|
stableOnInteractionStart,
|
|
1530
1567
|
stableOnStartHandleReconnect,
|
|
1568
|
+
stableOnReconnectPick,
|
|
1531
1569
|
showAddingElementAt,
|
|
1532
1570
|
// RF event handlers
|
|
1533
1571
|
onNodesChange,
|
|
@@ -227,7 +227,13 @@ export function useViewData({
|
|
|
227
227
|
queryFn: () => api.workspace.views.treeAround(viewId, { ancestorLevels: 2, descendantLevels: 2 }),
|
|
228
228
|
staleTime: 0,
|
|
229
229
|
}).catch(() => null)
|
|
230
|
-
if (tree)
|
|
230
|
+
if (tree) {
|
|
231
|
+
const links = buildViewContentLinks(tree, viewId, viewElementsRef.current)
|
|
232
|
+
useStore.getState().setTreeData(tree)
|
|
233
|
+
useStore.getState().setLinksMap(links.linksMap)
|
|
234
|
+
useStore.getState().setParentLinksMap(links.parentLinksMap)
|
|
235
|
+
useStore.getState().setIncomingLinks(links.incomingLinks)
|
|
236
|
+
}
|
|
231
237
|
}, [queryClient, viewId])
|
|
232
238
|
|
|
233
239
|
// ── Fetch view content ──────────────────────────────────────────────────
|
|
@@ -281,8 +287,12 @@ export function useViewData({
|
|
|
281
287
|
if (fresh) {
|
|
282
288
|
setViewElements(fresh.placements)
|
|
283
289
|
setConnectors(fresh.connectors)
|
|
290
|
+
const links = buildViewContentLinks(treeDataRef.current, viewId, fresh.placements)
|
|
291
|
+
setLinksMap(links.linksMap)
|
|
292
|
+
setParentLinksMap(links.parentLinksMap)
|
|
293
|
+
useStore.getState().setIncomingLinks(links.incomingLinks)
|
|
284
294
|
}
|
|
285
|
-
}, [queryClient, setConnectors, setViewElements, viewId])
|
|
295
|
+
}, [queryClient, setConnectors, setLinksMap, setParentLinksMap, setViewElements, viewId])
|
|
286
296
|
|
|
287
297
|
// ── Element mutation helpers ───────────────────────────────────────────────
|
|
288
298
|
const handleElementDeleted = useCallback((deletedId: number) => {
|
|
@@ -485,6 +485,7 @@ function ViewEditorInner({
|
|
|
485
485
|
const stableOnConnectToRef = useRef<(targetElementId: number) => Promise<void>>(async () => { })
|
|
486
486
|
const stableOnInteractionStartRef = useRef<(elementId: number, options?: { sourceHandle?: string; clientX?: number; clientY?: number }) => void>(() => { })
|
|
487
487
|
const stableOnStartHandleReconnectRef = useRef<(args: { edgeId: string; endpoint: 'source' | 'target'; handleId: string; clientX: number; clientY: number }) => void>(() => { })
|
|
488
|
+
const stableOnReconnectPickRef = useRef<(targetElementId: number) => Promise<boolean>>(async () => false)
|
|
488
489
|
|
|
489
490
|
// ── Drawing engine ────────────────────────────────────────────────────────
|
|
490
491
|
const drawing = useDrawingEngine(viewId)
|
|
@@ -518,18 +519,21 @@ function ViewEditorInner({
|
|
|
518
519
|
stableOnZoomOut: useCallback(async (id: number) => { await stableOnZoomOutRef.current(id) }, []),
|
|
519
520
|
stableOnNavigateToView: useCallback((id: number) => { stableOnNavigateToViewRef.current(id) }, []),
|
|
520
521
|
stableOnSelect: useCallback((obj: PlacedElement) => {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
522
|
+
void stableOnReconnectPickRef.current(obj.element_id).then((handled) => {
|
|
523
|
+
if (handled) return
|
|
524
|
+
setSelectedEdge(null)
|
|
525
|
+
setSelectedProxyConnectorDetails(null)
|
|
526
|
+
closeProxyConnectorPanelRef.current()
|
|
527
|
+
closeConnectorPanelRef.current()
|
|
528
|
+
setSelectedElement({
|
|
529
|
+
id: obj.element_id, name: obj.name, description: obj.description, kind: obj.kind,
|
|
530
|
+
technology: obj.technology, url: obj.url, logo_url: obj.logo_url,
|
|
531
|
+
technology_connectors: obj.technology_connectors, tags: obj.tags, repo: obj.repo,
|
|
532
|
+
branch: obj.branch, file_path: obj.file_path, language: obj.language,
|
|
533
|
+
created_at: '', updated_at: '', has_view: false, view_label: null,
|
|
534
|
+
})
|
|
535
|
+
openElementPanelRef.current()
|
|
531
536
|
})
|
|
532
|
-
openElementPanelRef.current()
|
|
533
537
|
}, []),
|
|
534
538
|
stableOnOpenCodePreview: useCallback((elementId: number) => {
|
|
535
539
|
const obj = previewViewElementsRef.current.find((o) => o.element_id === elementId)
|
|
@@ -1052,7 +1056,8 @@ function ViewEditorInner({
|
|
|
1052
1056
|
stableOnConnectToRef.current = canvas.stableOnConnectTo
|
|
1053
1057
|
stableOnInteractionStartRef.current = canvas.stableOnInteractionStart
|
|
1054
1058
|
stableOnStartHandleReconnectRef.current = canvas.stableOnStartHandleReconnect
|
|
1055
|
-
|
|
1059
|
+
stableOnReconnectPickRef.current = canvas.stableOnReconnectPick
|
|
1060
|
+
}, [canvas.stableOnZoomIn, canvas.stableOnZoomOut, canvas.stableOnNavigateToView, canvas.stableOnRemoveElement, canvas.stableOnConnectTo, canvas.stableOnInteractionStart, canvas.stableOnStartHandleReconnect, canvas.stableOnReconnectPick])
|
|
1056
1061
|
const viewName = view?.name ?? null
|
|
1057
1062
|
|
|
1058
1063
|
const [expandedAncestorGroups, setExpandedAncestorGroups] = useState<Set<string>>(new Set())
|
|
@@ -1780,7 +1785,7 @@ function ViewEditorInner({
|
|
|
1780
1785
|
menu={connectorLongPressMenu}
|
|
1781
1786
|
onEdit={(edgeId) => { const connector = connectors.find((e) => e.id === edgeId); if (connector) { setSelectedEdge(connector); connectorPanel.onOpen() }; setConnectorLongPressMenu(null) }}
|
|
1782
1787
|
onMoveSource={(edgeId) => { const picking = { edgeId, endpoint: 'source' as const }; reconnectPickingRef.current = picking; setReconnectPicking(picking); setConnectorLongPressMenu(null) }}
|
|
1783
|
-
onMoveTarget={(edgeId) => { const picking = { edgeId, endpoint: 'target' as const }; reconnectPickingRef.current = picking; setConnectorLongPressMenu(null) }}
|
|
1788
|
+
onMoveTarget={(edgeId) => { const picking = { edgeId, endpoint: 'target' as const }; reconnectPickingRef.current = picking; setReconnectPicking(picking); setConnectorLongPressMenu(null) }}
|
|
1784
1789
|
onDelete={async (edgeId) => {
|
|
1785
1790
|
setConnectorLongPressMenu(null)
|
|
1786
1791
|
if (!viewId) return
|
package/src/pages/ViewsGrid.tsx
CHANGED
|
@@ -832,7 +832,7 @@ function ViewGridInner({ onShare, treeData, loading, focusedId, onFocusChange, s
|
|
|
832
832
|
onStartRename: () => startEdit(n.id, n.name),
|
|
833
833
|
onDetails: () => handleDetailsOpen(n.id),
|
|
834
834
|
onDelete: () => { setDeleteTargetId(n.id); onDeleteOpen() },
|
|
835
|
-
onShare: onShare ? () => onShare(n.id) : () => {},
|
|
835
|
+
onShare: onShare ? () => onShare(n.id) : () => { },
|
|
836
836
|
onEditNameChange: setEditName,
|
|
837
837
|
onEditCommit: commitEdit,
|
|
838
838
|
onEditCancel: cancelEdit,
|
|
@@ -1088,7 +1088,7 @@ function ViewGridInner({ onShare, treeData, loading, focusedId, onFocusChange, s
|
|
|
1088
1088
|
<Text color="gray.600" fontSize="sm" mb={1}>No views yet.</Text>
|
|
1089
1089
|
{canEdit && (
|
|
1090
1090
|
<>
|
|
1091
|
-
<Text color="gray.700" fontSize="xs" mb={4}>Click "New
|
|
1091
|
+
<Text color="gray.700" fontSize="xs" mb={4}>Click "+ New" to get started.</Text>
|
|
1092
1092
|
|
|
1093
1093
|
</>
|
|
1094
1094
|
)}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { truncate } from './string'
|
|
3
|
+
|
|
4
|
+
describe('truncate', () => {
|
|
5
|
+
it('does not truncate strings shorter than or equal to the limit', () => {
|
|
6
|
+
expect(truncate('hello', 10)).toBe('hello')
|
|
7
|
+
expect(truncate('1234567890', 10)).toBe('1234567890')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('truncates strings longer than the limit and adds ellipsis', () => {
|
|
11
|
+
expect(truncate('hello world', 5)).toBe('hello...')
|
|
12
|
+
expect(truncate('12345678901', 10)).toBe('1234567890...')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('uses default limit of 15', () => {
|
|
16
|
+
expect(truncate('123456789012345')).toBe('123456789012345')
|
|
17
|
+
expect(truncate('1234567890123456')).toBe('123456789012345...')
|
|
18
|
+
})
|
|
19
|
+
})
|