@likec4/diagram 1.1.0 → 1.2.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/KeepAspectRatioContainer.d.ts +1 -1
- package/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.props.d.ts +22 -12
- package/dist/StaticLikeC4Diagram.d.ts +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +2209 -706
- package/dist/mantine/EnsureMantine.d.ts +1 -1
- package/dist/state/DiagramContext.d.ts +2143 -19
- package/dist/state/WhenInitialized.d.ts +1 -1
- package/dist/state/diagramStore.d.ts +2152 -19
- package/dist/state/useDiagramStore.d.ts +2142 -18
- package/dist/style.css +256 -118
- package/dist/theme.css.d.ts +1 -0
- package/dist/ui/DiagramTitlePanel.css.d.ts +4 -0
- package/dist/ui/DiagramTitlePanel.d.ts +1 -0
- package/dist/ui/DynamicViewWalkthrough.css.d.ts +3 -0
- package/dist/ui/DynamicViewWalkthrough.d.ts +1 -0
- package/dist/ui/OptionsPanel.d.ts +1 -3
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/options/NodeOptions.d.ts +1 -2
- package/dist/xyflow/FitviewOnDiagramChange.d.ts +4 -2
- package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +1 -0
- package/dist/xyflow/SyncWithDiagram.d.ts +1 -2
- package/dist/xyflow/XYFlow.d.ts +4 -7
- package/dist/xyflow/XYFlowBackground.d.ts +5 -4
- package/dist/xyflow/XYFlowEvents.d.ts +84 -84
- package/dist/xyflow/XYFlowInner.d.ts +11 -0
- package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
- package/dist/xyflow/edges/RelationshipEdge.d.ts +14 -14
- package/dist/xyflow/edges/edges.css.d.ts +5 -2
- package/dist/xyflow/edges/utils.d.ts +2 -0
- package/dist/xyflow/hooks/useXYFlow.d.ts +126 -126
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
- package/dist/xyflow/nodes/element/ElementIcon.d.ts +1 -1
- package/dist/xyflow/nodes/element/ElementLink.d.ts +1 -1
- package/dist/xyflow/nodes/element/ElementNode.d.ts +1 -1
- package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +2 -2
- package/dist/xyflow/nodes/element/element.css.d.ts +2 -1
- package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +1 -2
- package/dist/xyflow/types.d.ts +18 -18
- package/package.json +21 -16
- package/src/KeepAspectRatioContainer.tsx +1 -1
- package/src/LikeC4Diagram.css.ts +4 -1
- package/src/LikeC4Diagram.props.ts +24 -14
- package/src/LikeC4Diagram.tsx +18 -9
- package/src/StaticLikeC4Diagram.tsx +2 -0
- package/src/globals.css.ts +7 -1
- package/src/index.ts +0 -3
- package/src/state/DiagramContext.tsx +21 -12
- package/src/state/diagramStore.ts +275 -98
- package/src/theme.css.ts +1 -0
- package/src/ui/DiagramTitlePanel.css.ts +33 -0
- package/src/ui/DiagramTitlePanel.tsx +138 -0
- package/src/ui/DynamicViewWalkthrough.css.ts +46 -0
- package/src/ui/DynamicViewWalkthrough.tsx +86 -0
- package/src/ui/OptionsPanel.tsx +7 -24
- package/src/ui/index.ts +1 -0
- package/src/ui/options/NodeOptions.tsx +103 -27
- package/src/xyflow/FitviewOnDiagramChange.tsx +89 -69
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +75 -0
- package/src/xyflow/SyncWithDiagram.tsx +55 -101
- package/src/xyflow/XYFlow.tsx +52 -32
- package/src/xyflow/XYFlowBackground.tsx +7 -5
- package/src/xyflow/XYFlowEvents.tsx +150 -104
- package/src/xyflow/XYFlowInner.tsx +50 -0
- package/src/xyflow/diagram-to-xyflow.ts +16 -21
- package/src/xyflow/edges/RelationshipEdge.tsx +102 -70
- package/src/xyflow/edges/edges.css.ts +128 -53
- package/src/xyflow/edges/utils.ts +31 -0
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +20 -12
- package/src/xyflow/nodes/compound/CompoundNode.tsx +6 -3
- package/src/xyflow/nodes/element/ElementLink.tsx +13 -3
- package/src/xyflow/nodes/element/ElementNode.tsx +24 -19
- package/src/xyflow/nodes/element/element.css.ts +10 -2
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -0
- package/src/xyflow/types.ts +7 -8
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type {
|
|
2
|
+
BorderStyle,
|
|
3
|
+
DiagramEdge,
|
|
4
|
+
DiagramNode,
|
|
5
|
+
DiagramView,
|
|
6
|
+
ElementShape,
|
|
7
|
+
Fqn,
|
|
8
|
+
NonEmptyArray,
|
|
9
|
+
ThemeColor,
|
|
10
|
+
ViewID
|
|
11
|
+
} from '@likec4/core'
|
|
12
|
+
import { invariant, nonexhaustive, StepEdgeId } from '@likec4/core'
|
|
13
|
+
import { getViewportForBounds, type XYPosition } from '@xyflow/react'
|
|
4
14
|
import { getNodeDimensions } from '@xyflow/system'
|
|
5
15
|
import { DEV } from 'esm-env'
|
|
6
|
-
import { shallowEqual } from 'fast-equals'
|
|
16
|
+
import { deepEqual, shallowEqual } from 'fast-equals'
|
|
7
17
|
import type { MouseEvent as ReactMouseEvent } from 'react'
|
|
18
|
+
import { entries, filter, map, prop } from 'remeda'
|
|
8
19
|
import type { Exact, Except, RequiredKeysOf, Simplify } from 'type-fest'
|
|
9
20
|
import { devtools, subscribeWithSelector } from 'zustand/middleware'
|
|
10
21
|
import { shallow } from 'zustand/shallow'
|
|
11
22
|
import { createWithEqualityFn } from 'zustand/traditional'
|
|
12
23
|
import type { Change, DiagramNodeWithNavigate, LikeC4DiagramEventHandlers } from '../LikeC4Diagram.props'
|
|
13
24
|
import { MinZoom } from '../xyflow/const'
|
|
14
|
-
import type {
|
|
25
|
+
import type { XYStoreApi } from '../xyflow/hooks'
|
|
26
|
+
import type { XYFlowEdge, XYFlowInstance, XYFlowNode } from '../xyflow/types'
|
|
15
27
|
|
|
16
28
|
export type DiagramStore = {
|
|
17
29
|
// Incoming props
|
|
@@ -28,6 +40,10 @@ export type DiagramStore = {
|
|
|
28
40
|
// Internal state
|
|
29
41
|
xyflow: XYFlowInstance
|
|
30
42
|
initialized: boolean
|
|
43
|
+
xyflowSynced: boolean
|
|
44
|
+
|
|
45
|
+
// If Dynamic View
|
|
46
|
+
activeDynamicViewStep: number | null
|
|
31
47
|
|
|
32
48
|
// This is XYFlow id's
|
|
33
49
|
lastClickedNodeId: string | null
|
|
@@ -36,6 +52,9 @@ export type DiagramStore = {
|
|
|
36
52
|
hoveredNodeId: string | null
|
|
37
53
|
hoveredEdgeId: string | null
|
|
38
54
|
|
|
55
|
+
// id's of nodes / edges that
|
|
56
|
+
dimmed: ReadonlySet<string>
|
|
57
|
+
|
|
39
58
|
// Stack of previous distinct views (unique id's)
|
|
40
59
|
previousViews: DiagramView[]
|
|
41
60
|
|
|
@@ -52,6 +71,8 @@ export type DiagramStore = {
|
|
|
52
71
|
viewportChanged: boolean
|
|
53
72
|
} & Required<LikeC4DiagramEventHandlers>
|
|
54
73
|
|
|
74
|
+
const StringSet = Set<string>
|
|
75
|
+
|
|
55
76
|
export type DiagramInitialState = // Required properties
|
|
56
77
|
& Pick<
|
|
57
78
|
DiagramStore,
|
|
@@ -75,31 +96,43 @@ export type DiagramInitialState = // Required properties
|
|
|
75
96
|
& LikeC4DiagramEventHandlers
|
|
76
97
|
|
|
77
98
|
interface DiagramStoreActions {
|
|
99
|
+
isDynamicView: () => boolean
|
|
100
|
+
|
|
78
101
|
updateView: (view: DiagramView) => void
|
|
79
102
|
|
|
103
|
+
focusOnNode: (nodeId: string) => void
|
|
104
|
+
|
|
80
105
|
setHoveredNode: (nodeId: string | null) => void
|
|
81
106
|
setHoveredEdge: (edgeId: string | null) => void
|
|
82
107
|
|
|
83
108
|
setLastClickedNode: (nodeId: string | null) => void
|
|
84
109
|
setLastClickedEdge: (edgeId: string | null) => void
|
|
85
110
|
|
|
111
|
+
resetLastClicked: () => void
|
|
112
|
+
|
|
86
113
|
getElement(id: Fqn): DiagramNode | null
|
|
87
114
|
triggerOnChange: (changes: NonEmptyArray<Change>) => void
|
|
88
115
|
triggerOnNavigateTo: (xynodeId: string, event: ReactMouseEvent) => void
|
|
89
|
-
fitDiagram: (
|
|
116
|
+
fitDiagram: (xyStore: XYStoreApi) => void
|
|
117
|
+
|
|
118
|
+
nextDynamicStep: (increment?: number) => void
|
|
119
|
+
stopDynamicView: () => void
|
|
90
120
|
}
|
|
91
121
|
|
|
92
122
|
export type DiagramState = Simplify<DiagramStore & DiagramStoreActions>
|
|
93
123
|
|
|
94
124
|
const DEFAULT_PROPS: Except<DiagramStore, RequiredKeysOf<DiagramInitialState> | 'xyflow'> = {
|
|
95
125
|
initialized: false,
|
|
126
|
+
xyflowSynced: false,
|
|
96
127
|
previousViews: [],
|
|
97
128
|
viewportChanged: false,
|
|
129
|
+
activeDynamicViewStep: null,
|
|
98
130
|
focusedNodeId: null,
|
|
99
131
|
hoveredNodeId: null,
|
|
100
132
|
hoveredEdgeId: null,
|
|
101
133
|
lastClickedNodeId: null,
|
|
102
134
|
lastClickedEdgeId: null,
|
|
135
|
+
dimmed: new StringSet(),
|
|
103
136
|
lastOnNavigate: null,
|
|
104
137
|
onChange: null,
|
|
105
138
|
onNavigateTo: null,
|
|
@@ -118,7 +151,7 @@ const noReplace = false
|
|
|
118
151
|
|
|
119
152
|
let storeDevId = 1
|
|
120
153
|
|
|
121
|
-
const
|
|
154
|
+
const EmptyStringSet: ReadonlySet<string> = new StringSet()
|
|
122
155
|
|
|
123
156
|
export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props: T) {
|
|
124
157
|
return createWithEqualityFn<
|
|
@@ -134,78 +167,139 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
134
167
|
...DEFAULT_PROPS,
|
|
135
168
|
...(props as CreateDiagramStore),
|
|
136
169
|
|
|
137
|
-
|
|
170
|
+
isDynamicView: () => {
|
|
171
|
+
return get().view?.__ === 'dynamic'
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
updateView: (nextView) => {
|
|
138
175
|
let {
|
|
139
|
-
|
|
176
|
+
dimmed,
|
|
177
|
+
xyflowSynced,
|
|
178
|
+
view: current,
|
|
140
179
|
lastOnNavigate,
|
|
141
180
|
previousViews,
|
|
142
181
|
focusedNodeId,
|
|
143
182
|
lastClickedNodeId,
|
|
144
183
|
lastClickedEdgeId,
|
|
184
|
+
activeDynamicViewStep,
|
|
145
185
|
hoveredEdgeId,
|
|
146
186
|
hoveredNodeId
|
|
147
187
|
} = get()
|
|
148
188
|
|
|
149
|
-
if (shallowEqual(
|
|
189
|
+
if (shallowEqual(current, nextView)) {
|
|
190
|
+
if (!xyflowSynced) {
|
|
191
|
+
set({ xyflowSynced: true }, noReplace, 'updateView: xyflow synced')
|
|
192
|
+
}
|
|
150
193
|
DEV && console.debug('store: skip updateView')
|
|
151
194
|
return
|
|
152
195
|
}
|
|
153
196
|
|
|
154
|
-
|
|
155
|
-
if (lastOnNavigate && lastOnNavigate.toView !== view.id && lastOnNavigate.fromView !== view.id) {
|
|
156
|
-
lastOnNavigate = null
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const isSameView = currentView.id === view.id
|
|
197
|
+
const isSameView = current.id === nextView.id
|
|
160
198
|
|
|
161
199
|
if (isSameView) {
|
|
200
|
+
const nodeIds = new StringSet(nextView.nodes.map((n) => n.id))
|
|
201
|
+
const edgeIds = new StringSet(nextView.edges.map((e) => e.id))
|
|
162
202
|
// Reset clicked/hovered node/edge if the node/edge is not in the new view
|
|
163
|
-
if (lastClickedNodeId && !
|
|
203
|
+
if (lastClickedNodeId && !nodeIds.has(lastClickedNodeId)) {
|
|
164
204
|
lastClickedNodeId = null
|
|
165
205
|
}
|
|
166
|
-
if (hoveredNodeId && !
|
|
206
|
+
if (hoveredNodeId && !nodeIds.has(hoveredNodeId)) {
|
|
167
207
|
hoveredNodeId = null
|
|
168
208
|
}
|
|
169
|
-
if (focusedNodeId && !
|
|
209
|
+
if (focusedNodeId && !nodeIds.has(focusedNodeId)) {
|
|
170
210
|
focusedNodeId = null
|
|
171
211
|
}
|
|
172
|
-
if (lastClickedEdgeId && !
|
|
212
|
+
if (lastClickedEdgeId && !edgeIds.has(lastClickedEdgeId)) {
|
|
173
213
|
lastClickedEdgeId = null
|
|
174
214
|
}
|
|
175
|
-
if (hoveredEdgeId && !
|
|
215
|
+
if (hoveredEdgeId && !edgeIds.has(hoveredEdgeId)) {
|
|
176
216
|
hoveredEdgeId = null
|
|
177
217
|
}
|
|
218
|
+
if (activeDynamicViewStep && !edgeIds.has(StepEdgeId(activeDynamicViewStep))) {
|
|
219
|
+
activeDynamicViewStep = null
|
|
220
|
+
}
|
|
221
|
+
xyflowSynced = deepEqual(current.nodes, nextView.nodes) && deepEqual(current.edges, nextView.edges)
|
|
222
|
+
if (!xyflowSynced) {
|
|
223
|
+
let nextDimmed = new StringSet([...dimmed].filter(id => nodeIds.has(id) || edgeIds.has(id)))
|
|
224
|
+
if (nextDimmed.size !== dimmed.size) {
|
|
225
|
+
dimmed = nextDimmed
|
|
226
|
+
}
|
|
227
|
+
}
|
|
178
228
|
} else {
|
|
229
|
+
// Reset lastOnNavigate if the view is not the source or target view
|
|
230
|
+
if (lastOnNavigate) {
|
|
231
|
+
if (lastOnNavigate.toView !== nextView.id && lastOnNavigate.fromView !== nextView.id) {
|
|
232
|
+
lastOnNavigate = null
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
179
236
|
// Reset hovered / clicked node/edge if the view is different
|
|
180
237
|
lastClickedEdgeId = null
|
|
181
238
|
lastClickedNodeId = null
|
|
182
239
|
hoveredEdgeId = null
|
|
183
240
|
hoveredNodeId = null
|
|
184
241
|
focusedNodeId = null
|
|
242
|
+
activeDynamicViewStep = null
|
|
243
|
+
dimmed = EmptyStringSet
|
|
185
244
|
|
|
186
245
|
// Update history stack (back button not implemented yet)
|
|
187
246
|
previousViews = [
|
|
188
|
-
|
|
189
|
-
...previousViews.filter((v) => v.id !==
|
|
247
|
+
current,
|
|
248
|
+
...previousViews.filter((v) => v.id !== nextView.id && v.id !== current.id)
|
|
190
249
|
]
|
|
250
|
+
xyflowSynced = false
|
|
191
251
|
}
|
|
192
252
|
|
|
193
253
|
set(
|
|
194
254
|
{
|
|
195
|
-
view,
|
|
255
|
+
view: nextView,
|
|
256
|
+
activeDynamicViewStep,
|
|
257
|
+
xyflowSynced,
|
|
196
258
|
lastOnNavigate,
|
|
197
259
|
previousViews,
|
|
198
260
|
lastClickedNodeId,
|
|
199
261
|
lastClickedEdgeId,
|
|
200
262
|
focusedNodeId,
|
|
201
263
|
hoveredEdgeId,
|
|
202
|
-
hoveredNodeId
|
|
264
|
+
hoveredNodeId,
|
|
265
|
+
dimmed
|
|
203
266
|
},
|
|
204
267
|
noReplace,
|
|
205
268
|
'update-view'
|
|
206
269
|
)
|
|
207
270
|
},
|
|
208
271
|
|
|
272
|
+
focusOnNode: (nodeId) => {
|
|
273
|
+
const { focusedNodeId, view } = get()
|
|
274
|
+
if (nodeId !== focusedNodeId) {
|
|
275
|
+
const notDimmed = new StringSet([nodeId])
|
|
276
|
+
const dimmed = new StringSet()
|
|
277
|
+
for (const edge of view.edges) {
|
|
278
|
+
if (edge.source === nodeId || edge.target === nodeId) {
|
|
279
|
+
notDimmed.add(edge.source)
|
|
280
|
+
notDimmed.add(edge.target)
|
|
281
|
+
} else {
|
|
282
|
+
dimmed.add(edge.id)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
for (const node of view.nodes) {
|
|
286
|
+
if (notDimmed.has(node.id)) {
|
|
287
|
+
continue
|
|
288
|
+
}
|
|
289
|
+
dimmed.add(node.id)
|
|
290
|
+
}
|
|
291
|
+
set(
|
|
292
|
+
{
|
|
293
|
+
activeDynamicViewStep: null,
|
|
294
|
+
focusedNodeId: nodeId,
|
|
295
|
+
dimmed
|
|
296
|
+
},
|
|
297
|
+
noReplace,
|
|
298
|
+
`focus on node: ${nodeId}`
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
|
|
209
303
|
setHoveredNode: (nodeId) => {
|
|
210
304
|
if (nodeId !== get().hoveredNodeId) {
|
|
211
305
|
set({ hoveredNodeId: nodeId }, noReplace, nodeId ? 'setHoveredNode' : 'unsetHoveredNode')
|
|
@@ -230,88 +324,115 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
230
324
|
}
|
|
231
325
|
},
|
|
232
326
|
|
|
327
|
+
resetLastClicked: () => {
|
|
328
|
+
let {
|
|
329
|
+
focusedNodeId,
|
|
330
|
+
lastClickedNodeId,
|
|
331
|
+
lastClickedEdgeId
|
|
332
|
+
} = get()
|
|
333
|
+
if (focusedNodeId || lastClickedNodeId || lastClickedEdgeId) {
|
|
334
|
+
set(
|
|
335
|
+
{
|
|
336
|
+
focusedNodeId: null,
|
|
337
|
+
lastClickedNodeId: null,
|
|
338
|
+
lastClickedEdgeId: null,
|
|
339
|
+
dimmed: EmptyStringSet
|
|
340
|
+
},
|
|
341
|
+
noReplace,
|
|
342
|
+
'resetLastClicked'
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
|
|
233
347
|
getElement: (fqn) => {
|
|
234
348
|
const { view } = get()
|
|
235
349
|
return view.nodes.find(({ id }) => id === fqn) ?? null
|
|
236
350
|
},
|
|
237
351
|
|
|
238
352
|
triggerOnChange: (changes: NonEmptyArray<Change>) => {
|
|
353
|
+
if (DEV) {
|
|
354
|
+
console.debug('triggerOnChange', changes)
|
|
355
|
+
}
|
|
239
356
|
const newShapes = new Map<Fqn, ElementShape>()
|
|
240
357
|
const newColor = new Map<Fqn, ThemeColor>()
|
|
358
|
+
const newOpacity = new Map<Fqn, number>()
|
|
359
|
+
const newBorder = new Map<Fqn, BorderStyle>()
|
|
241
360
|
for (const change of changes) {
|
|
242
|
-
if (change.op
|
|
243
|
-
|
|
244
|
-
newShapes.set(target, change.shape)
|
|
245
|
-
}
|
|
246
|
-
continue
|
|
361
|
+
if (change.op !== 'change-element-style') {
|
|
362
|
+
nonexhaustive(change.op)
|
|
247
363
|
}
|
|
248
|
-
|
|
249
|
-
for (const
|
|
250
|
-
|
|
364
|
+
for (const target of change.targets) {
|
|
365
|
+
for (const [key, value] of entries.strict(change.style)) {
|
|
366
|
+
switch (key) {
|
|
367
|
+
case 'shape':
|
|
368
|
+
newShapes.set(target, value)
|
|
369
|
+
break
|
|
370
|
+
case 'color':
|
|
371
|
+
newColor.set(target, value)
|
|
372
|
+
break
|
|
373
|
+
case 'opacity':
|
|
374
|
+
newOpacity.set(target, value)
|
|
375
|
+
break
|
|
376
|
+
case 'border':
|
|
377
|
+
newBorder.set(target, value)
|
|
378
|
+
break
|
|
379
|
+
default:
|
|
380
|
+
nonexhaustive(key)
|
|
381
|
+
}
|
|
251
382
|
}
|
|
252
|
-
continue
|
|
253
383
|
}
|
|
254
|
-
nonexhaustive(change)
|
|
255
384
|
}
|
|
256
|
-
|
|
257
|
-
// const { view, updateView } = get()
|
|
258
|
-
// let hasChanges = false
|
|
259
|
-
// const nextNodes = view.nodes.map((node) => {
|
|
260
|
-
// const shape = newShapes.get(node.id)
|
|
261
|
-
// if (shape && shape !== node.shape) {
|
|
262
|
-
// hasChanges = true
|
|
263
|
-
// node = {
|
|
264
|
-
// ...node,
|
|
265
|
-
// shape
|
|
266
|
-
// }
|
|
267
|
-
// }
|
|
268
|
-
// const color = newColor.get(node.id)
|
|
269
|
-
// if (color && color !== node.color) {
|
|
270
|
-
// hasChanges = true
|
|
271
|
-
// node = {
|
|
272
|
-
// ...node,
|
|
273
|
-
// color
|
|
274
|
-
// }
|
|
275
|
-
// }
|
|
276
|
-
// return node
|
|
277
|
-
// })
|
|
278
|
-
// if (hasChanges) {
|
|
279
|
-
// updateView({
|
|
280
|
-
// ...view,
|
|
281
|
-
// nodes: nextNodes
|
|
282
|
-
// })
|
|
283
|
-
// }
|
|
385
|
+
|
|
284
386
|
get().xyflow.setNodes(nodes =>
|
|
285
387
|
nodes.map(node => {
|
|
286
|
-
let
|
|
287
|
-
const shape = newShapes.get(
|
|
288
|
-
if (shape && shape !==
|
|
289
|
-
|
|
290
|
-
...
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
388
|
+
let element = node.data.element
|
|
389
|
+
const shape = newShapes.get(element.id)
|
|
390
|
+
if (shape && shape !== element.shape) {
|
|
391
|
+
element = {
|
|
392
|
+
...element,
|
|
393
|
+
shape
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const color = newColor.get(element.id)
|
|
397
|
+
if (color && color !== element.color) {
|
|
398
|
+
element = {
|
|
399
|
+
...element,
|
|
400
|
+
color
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const opacity = newOpacity.get(element.id)
|
|
405
|
+
if (!!opacity && opacity !== element.style?.opacity) {
|
|
406
|
+
element = {
|
|
407
|
+
...element,
|
|
408
|
+
style: {
|
|
409
|
+
...element.style,
|
|
410
|
+
opacity
|
|
297
411
|
}
|
|
298
412
|
}
|
|
299
|
-
data = node.data
|
|
300
413
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
element
|
|
308
|
-
|
|
309
|
-
color
|
|
310
|
-
}
|
|
414
|
+
|
|
415
|
+
const border = newBorder.get(element.id)
|
|
416
|
+
if (border && border !== element.style?.border) {
|
|
417
|
+
element = {
|
|
418
|
+
...element,
|
|
419
|
+
style: {
|
|
420
|
+
...element.style,
|
|
421
|
+
border
|
|
311
422
|
}
|
|
312
423
|
}
|
|
313
424
|
}
|
|
314
|
-
|
|
425
|
+
|
|
426
|
+
if (element === node.data.element) {
|
|
427
|
+
return node
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
...node,
|
|
431
|
+
data: {
|
|
432
|
+
...node.data,
|
|
433
|
+
element
|
|
434
|
+
}
|
|
435
|
+
}
|
|
315
436
|
})
|
|
316
437
|
)
|
|
317
438
|
get().onChange?.({ changes })
|
|
@@ -348,22 +469,78 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
348
469
|
)
|
|
349
470
|
},
|
|
350
471
|
|
|
351
|
-
fitDiagram: (
|
|
352
|
-
const {
|
|
472
|
+
fitDiagram: (xyStore) => {
|
|
473
|
+
const { width, height, panZoom } = xyStore.getState()
|
|
474
|
+
const { fitViewPadding, view, focusedNodeId } = get()
|
|
475
|
+
const bounds = {
|
|
476
|
+
x: 0,
|
|
477
|
+
y: 0,
|
|
478
|
+
width: view.width,
|
|
479
|
+
height: view.height
|
|
480
|
+
}
|
|
481
|
+
const viewport = getViewportForBounds(bounds, width, height, MinZoom, 1, fitViewPadding)
|
|
482
|
+
panZoom?.setViewport(viewport, { duration: 400 })
|
|
483
|
+
if (!!focusedNodeId) {
|
|
484
|
+
set(
|
|
485
|
+
{
|
|
486
|
+
focusedNodeId: null,
|
|
487
|
+
dimmed: EmptyStringSet
|
|
488
|
+
},
|
|
489
|
+
noReplace,
|
|
490
|
+
'unfocus'
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
nextDynamicStep: (increment = 1) => {
|
|
496
|
+
const { view, activeDynamicViewStep, xyflow, fitViewPadding } = get()
|
|
497
|
+
invariant(view.__ === 'dynamic', 'view is not dynamic')
|
|
498
|
+
const nextStep = (activeDynamicViewStep ?? 0) + increment
|
|
499
|
+
const edgeId = StepEdgeId(nextStep)
|
|
500
|
+
const dimmed = new StringSet()
|
|
501
|
+
let edge: XYFlowEdge | null = null
|
|
502
|
+
for (const e of xyflow.getEdges()) {
|
|
503
|
+
if (e.id === edgeId) {
|
|
504
|
+
edge = e
|
|
505
|
+
continue
|
|
506
|
+
}
|
|
507
|
+
dimmed.add(e.id)
|
|
508
|
+
}
|
|
509
|
+
invariant(!!edge, `edge not found: ${edgeId}`)
|
|
510
|
+
const selected = [] as XYFlowNode[]
|
|
511
|
+
for (const n of xyflow.getNodes()) {
|
|
512
|
+
if (n.id === edge.source || n.id === edge.target) {
|
|
513
|
+
selected.push(n)
|
|
514
|
+
continue
|
|
515
|
+
}
|
|
516
|
+
dimmed.add(n.id)
|
|
517
|
+
}
|
|
353
518
|
xyflow.fitView({
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
padding: centerNode ? 0 : fitViewPadding,
|
|
519
|
+
duration: 400,
|
|
520
|
+
maxZoom: 1,
|
|
357
521
|
minZoom: MinZoom,
|
|
522
|
+
padding: fitViewPadding,
|
|
523
|
+
nodes: selected
|
|
524
|
+
})
|
|
525
|
+
set({
|
|
526
|
+
focusedNodeId: null,
|
|
527
|
+
activeDynamicViewStep: nextStep,
|
|
528
|
+
dimmed
|
|
529
|
+
})
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
stopDynamicView: () => {
|
|
533
|
+
const { xyflow, fitViewPadding } = get()
|
|
534
|
+
set({
|
|
535
|
+
activeDynamicViewStep: null,
|
|
536
|
+
dimmed: EmptyStringSet
|
|
537
|
+
})
|
|
538
|
+
xyflow.fitView({
|
|
539
|
+
duration: 400,
|
|
358
540
|
maxZoom: 1,
|
|
359
|
-
|
|
541
|
+
minZoom: MinZoom,
|
|
542
|
+
padding: fitViewPadding
|
|
360
543
|
})
|
|
361
|
-
if (centerNode && focusedNodeId !== centerNode.id) {
|
|
362
|
-
set({ focusedNodeId: centerNode.id }, noReplace, 'focus node')
|
|
363
|
-
}
|
|
364
|
-
if (!centerNode && !!focusedNodeId) {
|
|
365
|
-
set({ focusedNodeId: null }, noReplace, 'unfocus')
|
|
366
|
-
}
|
|
367
544
|
}
|
|
368
545
|
}),
|
|
369
546
|
{
|
package/src/theme.css.ts
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { globalStyle, style } from '@vanilla-extract/css'
|
|
2
|
+
import { mantine } from '../mantine.css'
|
|
3
|
+
|
|
4
|
+
export const container = style({
|
|
5
|
+
bottom: 0,
|
|
6
|
+
left: 0,
|
|
7
|
+
padding: 8,
|
|
8
|
+
margin: 0
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export const paper = style({
|
|
12
|
+
cursor: 'default',
|
|
13
|
+
minWidth: 100,
|
|
14
|
+
maxWidth: 'calc(100vw - 16px)',
|
|
15
|
+
backgroundColor: `color-mix(in srgb, ${mantine.colors.body}, transparent 20%)`,
|
|
16
|
+
WebkitBackdropFilter: 'blur(8px)',
|
|
17
|
+
backdropFilter: 'blur(8px)',
|
|
18
|
+
'@media': {
|
|
19
|
+
[mantine.largerThan('md')]: {
|
|
20
|
+
maxWidth: '45vw'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export const title = style({})
|
|
26
|
+
|
|
27
|
+
export const description = style({
|
|
28
|
+
whiteSpaceCollapse: 'preserve-breaks',
|
|
29
|
+
color: mantine.colors.gray[7]
|
|
30
|
+
})
|
|
31
|
+
globalStyle(`:where([data-mantine-color-scheme="dark"]) ${description}`, {
|
|
32
|
+
color: mantine.colors.gray[5]
|
|
33
|
+
})
|