@tldiagram/core-ui 1.92.0 → 1.93.0
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/api/client.d.ts +13 -1
- package/dist/components/ElementNode.d.ts +9 -0
- package/dist/config/runtime-vscode.d.ts +1 -0
- package/dist/config/runtime.d.ts +1 -0
- package/dist/index.js +10344 -9294
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +2 -1
- package/dist/pages/ViewEditor/hooks/useViewData.d.ts +20 -21
- package/dist/shims/empty-node-module.d.ts +2 -0
- package/dist/store/useStore.d.ts +78 -0
- package/dist/store/useStore.test.d.ts +1 -0
- package/package.json +7 -4
- package/src/api/client.ts +39 -1
- package/src/components/ElementNode.tsx +11 -58
- package/src/components/ElementPanel.tsx +2 -2
- package/src/components/LayoutSection.tsx +68 -93
- package/src/components/ViewGridNode.tsx +1 -4
- package/src/components/ZUI/renderer.ts +166 -66
- package/src/components/ZUI/useZUIInteraction.ts +235 -81
- package/src/config/runtime-vscode.ts +6 -0
- package/src/config/runtime.ts +4 -0
- package/src/main.tsx +26 -14
- package/src/pages/ViewEditor/context.tsx +12 -4
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +172 -121
- package/src/pages/ViewEditor/hooks/useViewData.ts +455 -253
- package/src/pages/ViewEditor/index.tsx +45 -32
- package/src/shims/empty-node-module.ts +1 -0
- package/src/store/useStore.test.ts +272 -0
- package/src/store/useStore.ts +285 -0
|
@@ -11,7 +11,6 @@ import ReactFlow, {
|
|
|
11
11
|
PanOnScrollMode,
|
|
12
12
|
ReactFlowProvider,
|
|
13
13
|
useReactFlow,
|
|
14
|
-
applyNodeChanges,
|
|
15
14
|
} from 'reactflow'
|
|
16
15
|
import type { Edge as RFEdge, EdgeMarker as RFEdgeMarker, Node as RFNode, NodeChange } from 'reactflow'
|
|
17
16
|
import 'reactflow/dist/style.css'
|
|
@@ -74,8 +73,8 @@ import type { ExtensionToWebviewMessage } from '../../types/vscode-messages'
|
|
|
74
73
|
import { ViewEditorContext } from './context'
|
|
75
74
|
import { useViewData } from './hooks/useViewData'
|
|
76
75
|
import { useDrawingEngine } from './hooks/useDrawingEngine'
|
|
77
|
-
import { useCanvasInteractions } from './hooks/useCanvasInteractions'
|
|
78
|
-
import { sanitizeExportFilename, triggerDownload } from './utils'
|
|
76
|
+
import { applyNodeChangesWithStructuralSharing, useCanvasInteractions } from './hooks/useCanvasInteractions'
|
|
77
|
+
import { connectorToConnector, findClosestHandles, sanitizeExportFilename, triggerDownload } from './utils'
|
|
79
78
|
import { pickUnusedColor } from '../../components/ViewExplorer/utils'
|
|
80
79
|
|
|
81
80
|
import { EmptyCanvasState } from './components/EmptyCanvasState'
|
|
@@ -86,6 +85,7 @@ import { useCrossBranchContextSettings } from '../../crossBranch/settings'
|
|
|
86
85
|
import { removeConnectorGraphSnapshot, upsertConnectorGraphSnapshot, useWorkspaceGraphSnapshot } from '../../crossBranch/store'
|
|
87
86
|
import type { ProxyConnectorDetails } from '../../crossBranch/types'
|
|
88
87
|
import { useDemoRevealViewport, type ViewEditorDemoOptions } from '../../demo/viewEditor'
|
|
88
|
+
import { useStore } from '../../store/useStore'
|
|
89
89
|
|
|
90
90
|
const nodeTypes = {
|
|
91
91
|
elementNode: ElementNode,
|
|
@@ -99,6 +99,7 @@ const VIEW_EDITOR_EMPTY_EXTENT_RATIO = 0.75
|
|
|
99
99
|
const VIEW_EDITOR_PAN_MARGIN_RATIO = 0.25
|
|
100
100
|
const VIEW_EDITOR_PAN_MARGIN_MIN = 180
|
|
101
101
|
const VIEW_EDITOR_PAN_MARGIN_MAX = 720
|
|
102
|
+
const SNAP_GRID: [number, number] = [30, 30]
|
|
102
103
|
|
|
103
104
|
function alphaColor(color: string, opacity: number): string {
|
|
104
105
|
if (opacity >= 1) return color
|
|
@@ -222,11 +223,33 @@ function ViewEditorInner({
|
|
|
222
223
|
const [extrasOpen, setExtrasOpen] = useState(false)
|
|
223
224
|
const [isImporting, setIsImporting] = useState(false)
|
|
224
225
|
const [isExporting, setIsExporting] = useState(false)
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
226
|
+
const setViewEditorUi = useStore((state) => state.setViewEditorUi)
|
|
227
|
+
const snapToGrid = useStore((state) => state.snapToGrid)
|
|
228
|
+
const setStoreSnapToGrid = useStore((state) => state.setSnapToGrid)
|
|
229
|
+
const upsertStoreConnector = useStore((state) => state.upsertConnector)
|
|
230
|
+
const removeStoreConnector = useStore((state) => state.removeConnector)
|
|
231
|
+
const setSnapToGrid = useCallback((snap: boolean) => {
|
|
232
|
+
setStoreSnapToGrid(snap)
|
|
233
|
+
if (typeof window !== 'undefined') localStorage.setItem('diag:snapToGrid', String(snap))
|
|
234
|
+
}, [setStoreSnapToGrid])
|
|
235
|
+
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
if (typeof window === 'undefined') return
|
|
238
|
+
setStoreSnapToGrid(localStorage.getItem('diag:snapToGrid') === 'true')
|
|
239
|
+
}, [setStoreSnapToGrid])
|
|
240
|
+
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
setViewEditorUi({
|
|
243
|
+
viewId,
|
|
244
|
+
canEdit,
|
|
245
|
+
isOwner,
|
|
246
|
+
isFreePlan,
|
|
247
|
+
snapToGrid,
|
|
248
|
+
selectedElement,
|
|
249
|
+
selectedConnector: selectedEdge,
|
|
250
|
+
})
|
|
251
|
+
}, [canEdit, isFreePlan, isOwner, selectedEdge, selectedElement, setViewEditorUi, snapToGrid, viewId])
|
|
252
|
+
|
|
230
253
|
useEffect(() => { localStorage.setItem('diag:snapToGrid', String(snapToGrid)) }, [snapToGrid])
|
|
231
254
|
const [, setHoveredZoom] = useState<{ elementId: number | null; type: 'in' | 'out' | null } | null>(null)
|
|
232
255
|
const hoveredZoomRef = useRef<{ elementId: number | null; type: 'in' | 'out' | null } | null>(null)
|
|
@@ -613,7 +636,7 @@ function ViewEditorInner({
|
|
|
613
636
|
const targetNode = rfNodesRef.current.find((n) => n.id === String(targetElementId))
|
|
614
637
|
let finalSourceHandle = 'right'; let finalTargetHandle = 'left'
|
|
615
638
|
if (sourceNode && targetNode) {
|
|
616
|
-
const h =
|
|
639
|
+
const h = findClosestHandles(sourceNode, targetNode)
|
|
617
640
|
finalSourceHandle = h.sourceHandle; finalTargetHandle = h.targetHandle
|
|
618
641
|
}
|
|
619
642
|
try {
|
|
@@ -621,9 +644,9 @@ function ViewEditorInner({
|
|
|
621
644
|
source_element_id: sourceId, target_element_id: targetElementId,
|
|
622
645
|
source_handle: finalSourceHandle, target_handle: finalTargetHandle, direction: 'forward',
|
|
623
646
|
})
|
|
624
|
-
const connector =
|
|
647
|
+
const connector = connectorToConnector(newConnector)
|
|
625
648
|
upsertConnectorGraphSnapshot(connector)
|
|
626
|
-
|
|
649
|
+
upsertStoreConnector(connector)
|
|
627
650
|
} catch { /* intentionally empty */ }
|
|
628
651
|
},
|
|
629
652
|
existingElementIds, linksMapRef, parentLinksMapRef,
|
|
@@ -641,23 +664,11 @@ function ViewEditorInner({
|
|
|
641
664
|
handleElementDeleted, handleElementPermanentlyDeleted,
|
|
642
665
|
handleConnectorDeleted: useCallback((edgeId: number) => {
|
|
643
666
|
if (viewId != null) removeConnectorGraphSnapshot(viewId, edgeId)
|
|
644
|
-
|
|
645
|
-
}, [
|
|
667
|
+
removeStoreConnector(edgeId)
|
|
668
|
+
}, [removeStoreConnector, viewId]),
|
|
646
669
|
handleUpdateTags,
|
|
647
670
|
drawingCanvasRef,
|
|
648
671
|
snapToGrid,
|
|
649
|
-
onMoveStateChange: useCallback((moving: boolean) => {
|
|
650
|
-
setLiveContextNodes((nds) => {
|
|
651
|
-
let changed = false
|
|
652
|
-
const nextNodes = nds.map((node) => {
|
|
653
|
-
const currentMoving = Boolean((node.data as { isCanvasMoving?: boolean }).isCanvasMoving)
|
|
654
|
-
if (currentMoving === moving) return node
|
|
655
|
-
changed = true
|
|
656
|
-
return { ...node, data: { ...node.data, isCanvasMoving: moving } }
|
|
657
|
-
})
|
|
658
|
-
return changed ? nextNodes : nds
|
|
659
|
-
})
|
|
660
|
-
}, []),
|
|
661
672
|
})
|
|
662
673
|
|
|
663
674
|
// Wire stable placeholders to the real implementations from canvas hook
|
|
@@ -826,7 +837,7 @@ function ViewEditorInner({
|
|
|
826
837
|
const ctxChanges = changes.filter((c) => 'id' in c && contextNodeIdsRef.current.has((c as { id: string }).id))
|
|
827
838
|
const mainChanges = changes.filter((c) => !('id' in c) || !contextNodeIdsRef.current.has((c as { id: string }).id))
|
|
828
839
|
if (ctxChanges.length > 0) {
|
|
829
|
-
setLiveContextNodes((nds) =>
|
|
840
|
+
setLiveContextNodes((nds) => applyNodeChangesWithStructuralSharing(ctxChanges, nds))
|
|
830
841
|
}
|
|
831
842
|
if (mainChanges.length > 0) {
|
|
832
843
|
canvasOnNodesChange(mainChanges)
|
|
@@ -1010,12 +1021,12 @@ function ViewEditorInner({
|
|
|
1010
1021
|
const handleOpenExport = useCallback(() => exportModal.onOpen(), [exportModal])
|
|
1011
1022
|
const handleConnectorSave = useCallback((updated: Connector) => {
|
|
1012
1023
|
upsertConnectorGraphSnapshot(updated)
|
|
1013
|
-
|
|
1014
|
-
}, [
|
|
1024
|
+
upsertStoreConnector(updated)
|
|
1025
|
+
}, [upsertStoreConnector])
|
|
1015
1026
|
const handleConnectorDeleteInPanel = useCallback((edgeId: number) => {
|
|
1016
1027
|
if (viewId != null) removeConnectorGraphSnapshot(viewId, edgeId)
|
|
1017
|
-
|
|
1018
|
-
}, [
|
|
1028
|
+
removeStoreConnector(edgeId)
|
|
1029
|
+
}, [removeStoreConnector, viewId])
|
|
1019
1030
|
const handleViewSave = useCallback((updated: ViewTreeNode) => setView(updated), [setView])
|
|
1020
1031
|
|
|
1021
1032
|
// ── Library helpers ────────────────────────────────────────────────────────
|
|
@@ -1224,8 +1235,10 @@ function ViewEditorInner({
|
|
|
1224
1235
|
nodesDraggable={canEdit} connectionMode={ConnectionMode.Loose} connectionRadius={25}
|
|
1225
1236
|
edgesUpdatable={canEdit} reconnectRadius={0}
|
|
1226
1237
|
snapToGrid={snapToGrid}
|
|
1227
|
-
snapGrid={
|
|
1238
|
+
snapGrid={SNAP_GRID}
|
|
1228
1239
|
deleteKeyCode={null}
|
|
1240
|
+
onlyRenderVisibleElements
|
|
1241
|
+
autoPanOnNodeDrag={false}
|
|
1229
1242
|
panOnDrag={!drawingMode}
|
|
1230
1243
|
panOnScroll={!isMobileLayout} panOnScrollSpeed={1.2} panOnScrollMode={PanOnScrollMode.Free}
|
|
1231
1244
|
zoomOnScroll={false} zoomOnPinch
|
|
@@ -1308,7 +1321,7 @@ function ViewEditorInner({
|
|
|
1308
1321
|
try {
|
|
1309
1322
|
await api.workspace.connectors.delete('', edgeId)
|
|
1310
1323
|
removeConnectorGraphSnapshot(viewId, edgeId)
|
|
1311
|
-
|
|
1324
|
+
removeStoreConnector(edgeId)
|
|
1312
1325
|
} catch { /* intentionally empty */ }
|
|
1313
1326
|
}}
|
|
1314
1327
|
/>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default {}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import type { Connector, LibraryElement, PlacedElement, ViewTreeNode } from '../types'
|
|
3
|
+
import {
|
|
4
|
+
buildViewContentLinks,
|
|
5
|
+
canvasSelectors,
|
|
6
|
+
emptyViewEditorUiState,
|
|
7
|
+
findViewByOwner,
|
|
8
|
+
findViewPath,
|
|
9
|
+
mergeSavedElementIntoPlacements,
|
|
10
|
+
removeConnectorFromList,
|
|
11
|
+
removePlacedElement,
|
|
12
|
+
selectConnectorById,
|
|
13
|
+
selectElementById,
|
|
14
|
+
selectExistingElementIds,
|
|
15
|
+
updatePlacedElementPosition,
|
|
16
|
+
upsertConnectorInList,
|
|
17
|
+
useStore,
|
|
18
|
+
} from './useStore'
|
|
19
|
+
|
|
20
|
+
const tree: ViewTreeNode[] = [{
|
|
21
|
+
id: 1,
|
|
22
|
+
owner_element_id: null,
|
|
23
|
+
name: 'Root',
|
|
24
|
+
description: null,
|
|
25
|
+
level_label: null,
|
|
26
|
+
level: 0,
|
|
27
|
+
depth: 0,
|
|
28
|
+
created_at: '2024-01-01',
|
|
29
|
+
updated_at: '2024-01-01',
|
|
30
|
+
parent_view_id: null,
|
|
31
|
+
children: [{
|
|
32
|
+
id: 2,
|
|
33
|
+
owner_element_id: 10,
|
|
34
|
+
name: 'Child',
|
|
35
|
+
description: null,
|
|
36
|
+
level_label: null,
|
|
37
|
+
level: 1,
|
|
38
|
+
depth: 1,
|
|
39
|
+
created_at: '2024-01-01',
|
|
40
|
+
updated_at: '2024-01-01',
|
|
41
|
+
parent_view_id: 1,
|
|
42
|
+
children: [],
|
|
43
|
+
}],
|
|
44
|
+
}]
|
|
45
|
+
|
|
46
|
+
const element = (element_id: number, x = 0, y = 0): PlacedElement => ({
|
|
47
|
+
id: element_id + 100,
|
|
48
|
+
view_id: 1,
|
|
49
|
+
element_id,
|
|
50
|
+
position_x: x,
|
|
51
|
+
position_y: y,
|
|
52
|
+
name: `Element ${element_id}`,
|
|
53
|
+
description: null,
|
|
54
|
+
kind: null,
|
|
55
|
+
technology: null,
|
|
56
|
+
url: null,
|
|
57
|
+
logo_url: null,
|
|
58
|
+
technology_connectors: [],
|
|
59
|
+
tags: [],
|
|
60
|
+
has_view: false,
|
|
61
|
+
view_label: null,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const connector = (id: number): Connector => ({
|
|
65
|
+
id,
|
|
66
|
+
view_id: 1,
|
|
67
|
+
source_element_id: 10,
|
|
68
|
+
target_element_id: 20,
|
|
69
|
+
label: null,
|
|
70
|
+
description: null,
|
|
71
|
+
relationship: null,
|
|
72
|
+
direction: 'forward',
|
|
73
|
+
style: 'bezier',
|
|
74
|
+
url: null,
|
|
75
|
+
source_handle: 'right',
|
|
76
|
+
target_handle: 'left',
|
|
77
|
+
created_at: '2024-01-01',
|
|
78
|
+
updated_at: '2024-01-01',
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const libraryElement = (id: number): LibraryElement => ({
|
|
82
|
+
id,
|
|
83
|
+
name: 'Saved',
|
|
84
|
+
kind: 'service',
|
|
85
|
+
description: 'Updated',
|
|
86
|
+
technology: 'TypeScript',
|
|
87
|
+
url: 'https://example.com',
|
|
88
|
+
logo_url: 'logo.svg',
|
|
89
|
+
technology_connectors: [{ type: 'custom', label: 'TS' }],
|
|
90
|
+
tags: ['api'],
|
|
91
|
+
repo: 'repo',
|
|
92
|
+
branch: 'main',
|
|
93
|
+
file_path: 'src/index.ts',
|
|
94
|
+
language: 'ts',
|
|
95
|
+
created_at: '2024-01-01',
|
|
96
|
+
updated_at: '2024-01-02',
|
|
97
|
+
has_view: true,
|
|
98
|
+
view_label: 'View',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
useStore.setState({
|
|
103
|
+
...emptyViewEditorUiState,
|
|
104
|
+
view: undefined,
|
|
105
|
+
viewElements: [],
|
|
106
|
+
connectors: [],
|
|
107
|
+
nodes: [],
|
|
108
|
+
edges: [],
|
|
109
|
+
linksMap: {},
|
|
110
|
+
parentLinksMap: {},
|
|
111
|
+
incomingLinks: [],
|
|
112
|
+
treeData: [],
|
|
113
|
+
allElements: [],
|
|
114
|
+
libraryRefresh: 0,
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('pure view helpers', () => {
|
|
119
|
+
it('finds views by owner and path', () => {
|
|
120
|
+
expect(findViewByOwner(tree, 10)?.id).toBe(2)
|
|
121
|
+
expect(findViewByOwner(tree, 999)).toBeNull()
|
|
122
|
+
expect(findViewPath(tree, 2)?.map((view) => view.id)).toEqual([1, 2])
|
|
123
|
+
expect(findViewPath(tree, 999)).toBeNull()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('builds child, parent, and incoming links for a view', () => {
|
|
127
|
+
const result = buildViewContentLinks(tree, 1, [element(10), element(20)])
|
|
128
|
+
expect(result.linksMap[10][0]).toMatchObject({ to_view_id: 2, relation_type: 'child' })
|
|
129
|
+
expect(result.parentLinksMap).toEqual({})
|
|
130
|
+
expect(result.incomingLinks).toEqual([])
|
|
131
|
+
|
|
132
|
+
const childResult = buildViewContentLinks(tree, 2, [element(20)])
|
|
133
|
+
expect(childResult.parentLinksMap[20][0]).toMatchObject({ from_view_id: 1, relation_type: 'parent' })
|
|
134
|
+
expect(childResult.incomingLinks[0]).toMatchObject({ element_id: 10, from_view_id: 1, to_view_id: 2 })
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('updates placement positions structurally', () => {
|
|
138
|
+
const elements = [element(10, 1, 2), element(20, 3, 4)]
|
|
139
|
+
expect(updatePlacedElementPosition(elements, 10, 1, 2)).toBe(elements)
|
|
140
|
+
const next = updatePlacedElementPosition(elements, 10, 9, 8)
|
|
141
|
+
expect(next).not.toBe(elements)
|
|
142
|
+
expect(next[0]).toMatchObject({ position_x: 9, position_y: 8 })
|
|
143
|
+
expect(next[1]).toBe(elements[1])
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('removes placements and merges saved element fields', () => {
|
|
147
|
+
const elements = [element(10), element(20)]
|
|
148
|
+
expect(removePlacedElement(elements, 10).map((item) => item.element_id)).toEqual([20])
|
|
149
|
+
const merged = mergeSavedElementIntoPlacements(elements, libraryElement(10))
|
|
150
|
+
expect(merged[0]).toMatchObject({ name: 'Saved', kind: 'service', repo: 'repo', tags: ['api'] })
|
|
151
|
+
expect(merged[1]).toBe(elements[1])
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('upserts and removes connectors', () => {
|
|
155
|
+
const first = connector(1)
|
|
156
|
+
const second = { ...connector(2), label: 'two' }
|
|
157
|
+
expect(upsertConnectorInList([], first)).toEqual([first])
|
|
158
|
+
expect(upsertConnectorInList([first], { ...first, label: 'updated' })[0].label).toBe('updated')
|
|
159
|
+
expect(upsertConnectorInList([first], second)).toEqual([first, second])
|
|
160
|
+
expect(removeConnectorFromList([first, second], 1)).toEqual([second])
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('selects existing element ids and entities by id', () => {
|
|
164
|
+
const state = { viewElements: [element(10), element(20)], connectors: [connector(1)] }
|
|
165
|
+
expect(Array.from(selectExistingElementIds(state))).toEqual([10, 20])
|
|
166
|
+
expect(selectElementById(state, 20)?.name).toBe('Element 20')
|
|
167
|
+
expect(selectConnectorById(state, 1)?.source_element_id).toBe(10)
|
|
168
|
+
expect(canvasSelectors.existingElementIds(state).has(10)).toBe(true)
|
|
169
|
+
expect(canvasSelectors.elementById(state, 10)?.id).toBe(110)
|
|
170
|
+
expect(canvasSelectors.connectorById(state, 1)?.id).toBe(1)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
describe('zustand canvas store', () => {
|
|
175
|
+
it('updates every scalar ui action', () => {
|
|
176
|
+
const selectedElement = libraryElement(10)
|
|
177
|
+
const selectedConnector = connector(1)
|
|
178
|
+
useStore.getState().setViewEditorUi({ viewId: 1, canEdit: true, isOwner: true, isFreePlan: true })
|
|
179
|
+
useStore.getState().setSnapToGrid(false)
|
|
180
|
+
useStore.getState().setSelectedElement(selectedElement)
|
|
181
|
+
useStore.getState().setSelectedConnector(selectedConnector)
|
|
182
|
+
|
|
183
|
+
expect(useStore.getState()).toMatchObject({
|
|
184
|
+
viewId: 1,
|
|
185
|
+
canEdit: true,
|
|
186
|
+
isOwner: true,
|
|
187
|
+
isFreePlan: true,
|
|
188
|
+
snapToGrid: false,
|
|
189
|
+
selectedElement,
|
|
190
|
+
selectedConnector,
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('sets every core collection with values and updater functions', () => {
|
|
195
|
+
useStore.getState().setView(tree[0])
|
|
196
|
+
useStore.getState().setViewElements([element(10)])
|
|
197
|
+
useStore.getState().setViewElements((previous) => [...previous, element(20)])
|
|
198
|
+
useStore.getState().setConnectors([connector(1)])
|
|
199
|
+
useStore.getState().setConnectors((previous) => [...previous, connector(2)])
|
|
200
|
+
useStore.getState().setNodes([{ id: '10', position: { x: 0, y: 0 }, data: {} }])
|
|
201
|
+
useStore.getState().setNodes((previous) => [...previous, { id: '20', position: { x: 1, y: 1 }, data: {} }])
|
|
202
|
+
useStore.getState().setEdges([{ id: '1', source: '10', target: '20' }])
|
|
203
|
+
useStore.getState().setEdges((previous) => [...previous, { id: '2', source: '20', target: '10' }])
|
|
204
|
+
useStore.getState().setLinksMap({ 10: [] })
|
|
205
|
+
useStore.getState().setLinksMap((previous) => ({ ...previous, 20: [] }))
|
|
206
|
+
useStore.getState().setParentLinksMap({ 10: [] })
|
|
207
|
+
useStore.getState().setParentLinksMap((previous) => ({ ...previous, 20: [] }))
|
|
208
|
+
useStore.getState().setIncomingLinks([{ id: 1, element_id: 10, element_name: 'E', from_view_id: 1, from_view_name: 'Root', to_view_id: 2 }])
|
|
209
|
+
useStore.getState().setTreeData(tree)
|
|
210
|
+
useStore.getState().setAllElements([libraryElement(10)])
|
|
211
|
+
useStore.getState().setLibraryRefresh((previous) => previous + 1)
|
|
212
|
+
|
|
213
|
+
const state = useStore.getState()
|
|
214
|
+
expect(state.view?.id).toBe(1)
|
|
215
|
+
expect(state.viewElements).toHaveLength(2)
|
|
216
|
+
expect(state.connectors).toHaveLength(2)
|
|
217
|
+
expect(state.nodes).toHaveLength(2)
|
|
218
|
+
expect(state.edges).toHaveLength(2)
|
|
219
|
+
expect(Object.keys(state.linksMap)).toEqual(['10', '20'])
|
|
220
|
+
expect(Object.keys(state.parentLinksMap)).toEqual(['10', '20'])
|
|
221
|
+
expect(state.incomingLinks).toHaveLength(1)
|
|
222
|
+
expect(state.treeData).toBe(tree)
|
|
223
|
+
expect(state.allElements).toHaveLength(1)
|
|
224
|
+
expect(state.libraryRefresh).toBe(1)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('hydrates and resets canvas data', () => {
|
|
228
|
+
const links = buildViewContentLinks(tree, 1, [element(10)])
|
|
229
|
+
useStore.getState().setNodes([{ id: 'stale', position: { x: 0, y: 0 }, data: {} }])
|
|
230
|
+
useStore.getState().setEdges([{ id: 'stale', source: '1', target: '2' }])
|
|
231
|
+
useStore.getState().hydrateViewContent({
|
|
232
|
+
view: tree[0],
|
|
233
|
+
viewElements: [element(10)],
|
|
234
|
+
connectors: [connector(1)],
|
|
235
|
+
treeData: tree,
|
|
236
|
+
...links,
|
|
237
|
+
})
|
|
238
|
+
expect(useStore.getState()).toMatchObject({
|
|
239
|
+
view: tree[0],
|
|
240
|
+
viewElements: [element(10)],
|
|
241
|
+
connectors: [connector(1)],
|
|
242
|
+
treeData: tree,
|
|
243
|
+
})
|
|
244
|
+
useStore.getState().resetCanvas()
|
|
245
|
+
expect(useStore.getState().nodes).toEqual([])
|
|
246
|
+
expect(useStore.getState().edges).toEqual([])
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('runs placement, connector, and deletion actions', () => {
|
|
250
|
+
useStore.getState().setViewElements([element(10), element(20)])
|
|
251
|
+
useStore.getState().setConnectors([connector(1)])
|
|
252
|
+
useStore.getState().updateElementPosition(10, 50, 60)
|
|
253
|
+
expect(useStore.getState().viewElements[0]).toMatchObject({ position_x: 50, position_y: 60 })
|
|
254
|
+
|
|
255
|
+
useStore.getState().removeElementPlacement(10)
|
|
256
|
+
expect(useStore.getState().viewElements.map((item) => item.element_id)).toEqual([20])
|
|
257
|
+
|
|
258
|
+
useStore.getState().setViewElements([element(10)])
|
|
259
|
+
useStore.getState().mergeSavedElement(libraryElement(10))
|
|
260
|
+
expect(useStore.getState().viewElements[0].name).toBe('Saved')
|
|
261
|
+
expect(useStore.getState().libraryRefresh).toBe(1)
|
|
262
|
+
|
|
263
|
+
useStore.getState().removeElementEverywhere(10)
|
|
264
|
+
expect(useStore.getState().viewElements).toEqual([])
|
|
265
|
+
expect(useStore.getState().libraryRefresh).toBe(2)
|
|
266
|
+
|
|
267
|
+
useStore.getState().upsertConnector(connector(2))
|
|
268
|
+
useStore.getState().replaceConnector({ ...connector(2), label: 'updated' })
|
|
269
|
+
useStore.getState().removeConnector(1)
|
|
270
|
+
expect(useStore.getState().connectors).toEqual([{ ...connector(2), label: 'updated' }])
|
|
271
|
+
})
|
|
272
|
+
})
|