@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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
import type { Edge as RFEdge, Node as RFNode } from 'reactflow'
|
|
3
|
+
import type {
|
|
4
|
+
Connector,
|
|
5
|
+
IncomingViewConnector,
|
|
6
|
+
LibraryElement,
|
|
7
|
+
PlacedElement,
|
|
8
|
+
ViewConnector,
|
|
9
|
+
ViewTreeNode,
|
|
10
|
+
} from '../types'
|
|
11
|
+
|
|
12
|
+
export type StoreSetter<T> = T | ((previous: T) => T)
|
|
13
|
+
|
|
14
|
+
export type ViewEditorUiState = {
|
|
15
|
+
viewId: number | null
|
|
16
|
+
canEdit: boolean
|
|
17
|
+
isOwner: boolean
|
|
18
|
+
isFreePlan: boolean
|
|
19
|
+
snapToGrid: boolean
|
|
20
|
+
selectedElement: LibraryElement | null
|
|
21
|
+
selectedConnector: Connector | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ViewContentLinks = {
|
|
25
|
+
linksMap: Record<number, ViewConnector[]>
|
|
26
|
+
parentLinksMap: Record<number, ViewConnector[]>
|
|
27
|
+
incomingLinks: IncomingViewConnector[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ViewContentPayload = ViewContentLinks & {
|
|
31
|
+
view: ViewTreeNode | null
|
|
32
|
+
viewElements: PlacedElement[]
|
|
33
|
+
connectors: Connector[]
|
|
34
|
+
treeData: ViewTreeNode[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type CanvasStoreState = ViewEditorUiState & {
|
|
38
|
+
view: ViewTreeNode | null | undefined
|
|
39
|
+
viewElements: PlacedElement[]
|
|
40
|
+
connectors: Connector[]
|
|
41
|
+
nodes: RFNode[]
|
|
42
|
+
edges: RFEdge[]
|
|
43
|
+
linksMap: Record<number, ViewConnector[]>
|
|
44
|
+
parentLinksMap: Record<number, ViewConnector[]>
|
|
45
|
+
incomingLinks: IncomingViewConnector[]
|
|
46
|
+
treeData: ViewTreeNode[]
|
|
47
|
+
allElements: LibraryElement[]
|
|
48
|
+
libraryRefresh: number
|
|
49
|
+
|
|
50
|
+
setViewEditorUi: (patch: Partial<ViewEditorUiState>) => void
|
|
51
|
+
setSnapToGrid: (snapToGrid: boolean) => void
|
|
52
|
+
setSelectedElement: (selectedElement: LibraryElement | null) => void
|
|
53
|
+
setSelectedConnector: (selectedConnector: Connector | null) => void
|
|
54
|
+
setView: (view: ViewTreeNode | null | undefined) => void
|
|
55
|
+
setViewElements: (next: StoreSetter<PlacedElement[]>) => void
|
|
56
|
+
setConnectors: (next: StoreSetter<Connector[]>) => void
|
|
57
|
+
setNodes: (next: StoreSetter<RFNode[]>) => void
|
|
58
|
+
setEdges: (next: StoreSetter<RFEdge[]>) => void
|
|
59
|
+
setLinksMap: (next: StoreSetter<Record<number, ViewConnector[]>>) => void
|
|
60
|
+
setParentLinksMap: (next: StoreSetter<Record<number, ViewConnector[]>>) => void
|
|
61
|
+
setIncomingLinks: (next: StoreSetter<IncomingViewConnector[]>) => void
|
|
62
|
+
setTreeData: (next: StoreSetter<ViewTreeNode[]>) => void
|
|
63
|
+
setAllElements: (next: StoreSetter<LibraryElement[]>) => void
|
|
64
|
+
setLibraryRefresh: (next: StoreSetter<number>) => void
|
|
65
|
+
resetCanvas: () => void
|
|
66
|
+
hydrateViewContent: (payload: ViewContentPayload) => void
|
|
67
|
+
updateElementPosition: (elementId: number, x: number, y: number) => void
|
|
68
|
+
removeElementPlacement: (elementId: number) => void
|
|
69
|
+
removeElementEverywhere: (elementId: number) => void
|
|
70
|
+
mergeSavedElement: (saved: LibraryElement) => void
|
|
71
|
+
upsertConnector: (connector: Connector) => void
|
|
72
|
+
replaceConnector: (connector: Connector) => void
|
|
73
|
+
removeConnector: (connectorId: number) => void
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const emptyViewEditorUiState: ViewEditorUiState = {
|
|
77
|
+
viewId: null,
|
|
78
|
+
canEdit: false,
|
|
79
|
+
isOwner: false,
|
|
80
|
+
isFreePlan: false,
|
|
81
|
+
snapToGrid: false,
|
|
82
|
+
selectedElement: null,
|
|
83
|
+
selectedConnector: null,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveSetter<T>(next: StoreSetter<T>, previous: T): T {
|
|
87
|
+
return typeof next === 'function' ? (next as (previous: T) => T)(previous) : next
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function findViewByOwner(nodes: ViewTreeNode[], elementId: number): ViewTreeNode | null {
|
|
91
|
+
for (const node of nodes) {
|
|
92
|
+
if (node.owner_element_id !== null && Number(node.owner_element_id) === Number(elementId)) return node
|
|
93
|
+
const found = findViewByOwner(node.children, elementId)
|
|
94
|
+
if (found) return found
|
|
95
|
+
}
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function findViewPath(nodes: ViewTreeNode[], targetId: number, path: ViewTreeNode[] = []): ViewTreeNode[] | null {
|
|
100
|
+
for (const node of nodes) {
|
|
101
|
+
if (node.id === targetId) return [...path, node]
|
|
102
|
+
const found = findViewPath(node.children, targetId, [...path, node])
|
|
103
|
+
if (found) return found
|
|
104
|
+
}
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function buildViewContentLinks(tree: ViewTreeNode[], viewId: number, viewElements: PlacedElement[]): ViewContentLinks {
|
|
109
|
+
const linksMap: Record<number, ViewConnector[]> = {}
|
|
110
|
+
const parentLinksMap: Record<number, ViewConnector[]> = {}
|
|
111
|
+
|
|
112
|
+
const viewPath = findViewPath(tree, viewId)
|
|
113
|
+
const parentView = viewPath && viewPath.length > 1 ? viewPath[viewPath.length - 2] : null
|
|
114
|
+
const currentViewInTree = viewPath ? viewPath[viewPath.length - 1] : null
|
|
115
|
+
|
|
116
|
+
const incomingLinks: IncomingViewConnector[] = []
|
|
117
|
+
if (parentView && currentViewInTree?.owner_element_id) {
|
|
118
|
+
incomingLinks.push({
|
|
119
|
+
id: 0,
|
|
120
|
+
element_id: currentViewInTree.owner_element_id,
|
|
121
|
+
element_name: 'Parent',
|
|
122
|
+
from_view_id: parentView.id,
|
|
123
|
+
from_view_name: parentView.name,
|
|
124
|
+
to_view_id: viewId,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const element of viewElements) {
|
|
129
|
+
const childView = findViewByOwner(tree, element.element_id)
|
|
130
|
+
if (childView) {
|
|
131
|
+
linksMap[element.element_id] = [{
|
|
132
|
+
id: 0,
|
|
133
|
+
element_id: element.element_id,
|
|
134
|
+
from_view_id: viewId,
|
|
135
|
+
to_view_id: childView.id,
|
|
136
|
+
to_view_name: childView.name,
|
|
137
|
+
relation_type: 'child',
|
|
138
|
+
}]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (parentView) {
|
|
142
|
+
parentLinksMap[element.element_id] = [{
|
|
143
|
+
id: 0,
|
|
144
|
+
element_id: element.element_id,
|
|
145
|
+
from_view_id: parentView.id,
|
|
146
|
+
to_view_id: parentView.id,
|
|
147
|
+
to_view_name: parentView.name,
|
|
148
|
+
relation_type: 'parent',
|
|
149
|
+
}]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { linksMap, parentLinksMap, incomingLinks }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function selectExistingElementIds(state: Pick<CanvasStoreState, 'viewElements'>): Set<number> {
|
|
157
|
+
return new Set(state.viewElements.map((element) => element.element_id))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function selectElementById(state: Pick<CanvasStoreState, 'viewElements'>, elementId: number): PlacedElement | undefined {
|
|
161
|
+
return state.viewElements.find((element) => element.element_id === elementId)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function selectConnectorById(state: Pick<CanvasStoreState, 'connectors'>, connectorId: number): Connector | undefined {
|
|
165
|
+
return state.connectors.find((connector) => connector.id === connectorId)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function updatePlacedElementPosition(elements: PlacedElement[], elementId: number, x: number, y: number): PlacedElement[] {
|
|
169
|
+
let changed = false
|
|
170
|
+
const next = elements.map((element) => {
|
|
171
|
+
if (element.element_id !== elementId) return element
|
|
172
|
+
if (element.position_x === x && element.position_y === y) return element
|
|
173
|
+
changed = true
|
|
174
|
+
return { ...element, position_x: x, position_y: y }
|
|
175
|
+
})
|
|
176
|
+
return changed ? next : elements
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function removePlacedElement(elements: PlacedElement[], elementId: number): PlacedElement[] {
|
|
180
|
+
return elements.filter((element) => element.element_id !== elementId)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function mergeSavedElementIntoPlacements(elements: PlacedElement[], saved: LibraryElement): PlacedElement[] {
|
|
184
|
+
return elements.map((element) =>
|
|
185
|
+
element.element_id === saved.id
|
|
186
|
+
? {
|
|
187
|
+
...element,
|
|
188
|
+
name: saved.name,
|
|
189
|
+
description: saved.description,
|
|
190
|
+
kind: saved.kind,
|
|
191
|
+
technology: saved.technology,
|
|
192
|
+
url: saved.url,
|
|
193
|
+
logo_url: saved.logo_url,
|
|
194
|
+
technology_connectors: saved.technology_connectors,
|
|
195
|
+
tags: saved.tags,
|
|
196
|
+
repo: saved.repo,
|
|
197
|
+
branch: saved.branch,
|
|
198
|
+
file_path: saved.file_path,
|
|
199
|
+
language: saved.language,
|
|
200
|
+
}
|
|
201
|
+
: element,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function upsertConnectorInList(connectors: Connector[], connector: Connector): Connector[] {
|
|
206
|
+
const index = connectors.findIndex((candidate) => candidate.id === connector.id)
|
|
207
|
+
if (index === -1) return [...connectors, connector]
|
|
208
|
+
const next = connectors.slice()
|
|
209
|
+
next[index] = connector
|
|
210
|
+
return next
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function removeConnectorFromList(connectors: Connector[], connectorId: number): Connector[] {
|
|
214
|
+
return connectors.filter((connector) => connector.id !== connectorId)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const useStore = create<CanvasStoreState>((set) => ({
|
|
218
|
+
...emptyViewEditorUiState,
|
|
219
|
+
view: undefined,
|
|
220
|
+
viewElements: [],
|
|
221
|
+
connectors: [],
|
|
222
|
+
nodes: [],
|
|
223
|
+
edges: [],
|
|
224
|
+
linksMap: {},
|
|
225
|
+
parentLinksMap: {},
|
|
226
|
+
incomingLinks: [],
|
|
227
|
+
treeData: [],
|
|
228
|
+
allElements: [],
|
|
229
|
+
libraryRefresh: 0,
|
|
230
|
+
|
|
231
|
+
setViewEditorUi: (patch) => set((state) => ({ ...state, ...patch })),
|
|
232
|
+
setSnapToGrid: (snapToGrid) => set({ snapToGrid }),
|
|
233
|
+
setSelectedElement: (selectedElement) => set({ selectedElement }),
|
|
234
|
+
setSelectedConnector: (selectedConnector) => set({ selectedConnector }),
|
|
235
|
+
setView: (view) => set({ view }),
|
|
236
|
+
setViewElements: (next) => set((state) => ({ viewElements: resolveSetter(next, state.viewElements) })),
|
|
237
|
+
setConnectors: (next) => set((state) => ({ connectors: resolveSetter(next, state.connectors) })),
|
|
238
|
+
setNodes: (next) => set((state) => ({ nodes: resolveSetter(next, state.nodes) })),
|
|
239
|
+
setEdges: (next) => set((state) => ({ edges: resolveSetter(next, state.edges) })),
|
|
240
|
+
setLinksMap: (next) => set((state) => ({ linksMap: resolveSetter(next, state.linksMap) })),
|
|
241
|
+
setParentLinksMap: (next) => set((state) => ({ parentLinksMap: resolveSetter(next, state.parentLinksMap) })),
|
|
242
|
+
setIncomingLinks: (next) => set((state) => ({ incomingLinks: resolveSetter(next, state.incomingLinks) })),
|
|
243
|
+
setTreeData: (next) => set((state) => ({ treeData: resolveSetter(next, state.treeData) })),
|
|
244
|
+
setAllElements: (next) => set((state) => ({ allElements: resolveSetter(next, state.allElements) })),
|
|
245
|
+
setLibraryRefresh: (next) => set((state) => ({ libraryRefresh: resolveSetter(next, state.libraryRefresh) })),
|
|
246
|
+
resetCanvas: () => set({ nodes: [], edges: [] }),
|
|
247
|
+
hydrateViewContent: (payload) => set({
|
|
248
|
+
view: payload.view,
|
|
249
|
+
viewElements: payload.viewElements,
|
|
250
|
+
connectors: payload.connectors,
|
|
251
|
+
linksMap: payload.linksMap,
|
|
252
|
+
parentLinksMap: payload.parentLinksMap,
|
|
253
|
+
incomingLinks: payload.incomingLinks,
|
|
254
|
+
treeData: payload.treeData,
|
|
255
|
+
}),
|
|
256
|
+
updateElementPosition: (elementId, x, y) => set((state) => ({
|
|
257
|
+
viewElements: updatePlacedElementPosition(state.viewElements, elementId, x, y),
|
|
258
|
+
})),
|
|
259
|
+
removeElementPlacement: (elementId) => set((state) => ({
|
|
260
|
+
viewElements: removePlacedElement(state.viewElements, elementId),
|
|
261
|
+
})),
|
|
262
|
+
removeElementEverywhere: (elementId) => set((state) => ({
|
|
263
|
+
viewElements: removePlacedElement(state.viewElements, elementId),
|
|
264
|
+
libraryRefresh: state.libraryRefresh + 1,
|
|
265
|
+
})),
|
|
266
|
+
mergeSavedElement: (saved) => set((state) => ({
|
|
267
|
+
viewElements: mergeSavedElementIntoPlacements(state.viewElements, saved),
|
|
268
|
+
libraryRefresh: state.libraryRefresh + 1,
|
|
269
|
+
})),
|
|
270
|
+
upsertConnector: (connector) => set((state) => ({
|
|
271
|
+
connectors: upsertConnectorInList(state.connectors, connector),
|
|
272
|
+
})),
|
|
273
|
+
replaceConnector: (connector) => set((state) => ({
|
|
274
|
+
connectors: upsertConnectorInList(state.connectors, connector),
|
|
275
|
+
})),
|
|
276
|
+
removeConnector: (connectorId) => set((state) => ({
|
|
277
|
+
connectors: removeConnectorFromList(state.connectors, connectorId),
|
|
278
|
+
})),
|
|
279
|
+
}))
|
|
280
|
+
|
|
281
|
+
export const canvasSelectors = {
|
|
282
|
+
existingElementIds: selectExistingElementIds,
|
|
283
|
+
elementById: selectElementById,
|
|
284
|
+
connectorById: selectConnectorById,
|
|
285
|
+
}
|