@likec4/diagram 1.39.4 → 1.40.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.
Files changed (123) hide show
  1. package/bundle/{chunk-CW_KClZe.js → chunk-CDr-4Ymp.js} +936 -903
  2. package/bundle/custom.js +61 -60
  3. package/bundle/index.js +2667 -539
  4. package/dist/LikeC4Diagram.d.ts +1 -1
  5. package/dist/LikeC4Diagram.js +7 -4
  6. package/dist/LikeC4Diagram.props.d.ts +6 -1
  7. package/dist/base/const.d.ts +9 -0
  8. package/dist/base/const.js +10 -1
  9. package/dist/base/index.d.ts +1 -0
  10. package/dist/base/primitives/edge/EdgeLabel.js +2 -1
  11. package/dist/base/primitives/edge/EdgeLabelContainer.js +10 -4
  12. package/dist/base/primitives/edge/EdgePath.js +17 -8
  13. package/dist/base/primitives/edge/edge.css.d.ts +0 -1
  14. package/dist/base/primitives/edge/edge.css.js +6 -14
  15. package/dist/base/primitives/element/ElementActionButtons.js +1 -3
  16. package/dist/base/primitives/element/ElementNodeContainer.d.ts +0 -6
  17. package/dist/base/primitives/element/ElementNodeContainer.js +3 -26
  18. package/dist/base/primitives/element/ElementTitle.js +2 -2
  19. package/dist/base/types.d.ts +3 -3
  20. package/dist/bundle/LikeC4View.js +4 -12
  21. package/dist/bundle/LikeC4View.props.d.ts +12 -3
  22. package/dist/bundle/custom.js +1 -1
  23. package/dist/custom/customNodes.d.ts +6 -0
  24. package/dist/custom/customNodes.js +2 -1
  25. package/dist/custom/index.js +1 -1
  26. package/dist/hooks/useDiagram.d.ts +5 -1
  27. package/dist/hooks/useDiagram.js +3 -0
  28. package/dist/hooks/useXYFlow.d.ts +4 -439
  29. package/dist/likec4diagram/DiagramUI.js +1 -1
  30. package/dist/likec4diagram/DiagramXYFlow.js +8 -4
  31. package/dist/likec4diagram/convert-to-xyflow.d.ts +14 -0
  32. package/dist/likec4diagram/convert-to-xyflow.js +29 -0
  33. package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +3 -3
  34. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +0 -1
  35. package/dist/likec4diagram/custom/edges/SequenceStepEdge.d.ts +3 -0
  36. package/dist/likec4diagram/custom/edges/SequenceStepEdge.js +71 -0
  37. package/dist/likec4diagram/custom/index.js +7 -2
  38. package/dist/likec4diagram/custom/nodes/ElementActions.d.ts +2 -1
  39. package/dist/likec4diagram/custom/nodes/SequenceActorNode.d.ts +4 -0
  40. package/dist/likec4diagram/custom/nodes/SequenceActorNode.js +157 -0
  41. package/dist/likec4diagram/custom/nodes/index.d.ts +1 -0
  42. package/dist/likec4diagram/custom/nodes/nodes.d.ts +1 -2
  43. package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +31 -27
  44. package/dist/likec4diagram/types.d.ts +41 -7
  45. package/dist/likec4diagram/xyflow-diagram/diagram-view.d.ts +11 -0
  46. package/dist/likec4diagram/{useViewToNodesEdges.js → xyflow-diagram/diagram-view.js} +4 -13
  47. package/dist/likec4diagram/xyflow-sequence/_types.d.ts +41 -0
  48. package/dist/likec4diagram/xyflow-sequence/const.d.ts +17 -0
  49. package/dist/likec4diagram/xyflow-sequence/const.js +16 -0
  50. package/dist/likec4diagram/xyflow-sequence/layouter.d.ts +46 -0
  51. package/dist/likec4diagram/xyflow-sequence/layouter.js +408 -0
  52. package/dist/likec4diagram/xyflow-sequence/sequence-view.d.ts +7 -0
  53. package/dist/likec4diagram/xyflow-sequence/sequence-view.js +273 -0
  54. package/dist/likec4diagram/xyflow-sequence/utils.d.ts +14 -0
  55. package/dist/likec4diagram/xyflow-sequence/utils.js +92 -0
  56. package/dist/navigationpanel/NavigationPanelControls.js +2 -2
  57. package/dist/navigationpanel/controls/ViewDetailsCard.js +5 -2
  58. package/dist/navigationpanel/dropdown/ProjectsMenu.js +53 -44
  59. package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.d.ts +1 -2
  60. package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.js +10 -41
  61. package/dist/navigationpanel/walkthrough/DynamicViewControls.d.ts +292 -0
  62. package/dist/navigationpanel/walkthrough/DynamicViewControls.js +119 -0
  63. package/dist/navigationpanel/walkthrough/index.d.ts +2 -1
  64. package/dist/overlays/relationship-details/RelationshipDetails.js +4 -1
  65. package/dist/state/DiagramActorProvider.d.ts +3 -2
  66. package/dist/state/DiagramActorProvider.js +19 -11
  67. package/dist/state/assign.js +11 -4
  68. package/dist/state/diagram-machine.d.ts +224 -194
  69. package/dist/state/diagram-machine.js +170 -63
  70. package/dist/state/utils.d.ts +7 -1
  71. package/dist/state/utils.js +67 -7
  72. package/dist/styles-min.css +1 -1
  73. package/dist/styles.css +1 -1
  74. package/dist/styles.css2.js +1 -1
  75. package/package.json +8 -7
  76. package/panda.buildinfo.json +30 -9
  77. package/src/LikeC4Diagram.props.ts +14 -1
  78. package/src/LikeC4Diagram.tsx +7 -4
  79. package/src/base/const.ts +12 -0
  80. package/src/base/index.ts +2 -0
  81. package/src/base/primitives/edge/EdgeLabel.tsx +23 -19
  82. package/src/base/primitives/edge/EdgeLabelContainer.tsx +12 -7
  83. package/src/base/primitives/edge/EdgePath.tsx +25 -14
  84. package/src/base/primitives/edge/edge.css.ts +5 -14
  85. package/src/base/primitives/element/ElementActionButtons.tsx +1 -3
  86. package/src/base/primitives/element/ElementNodeContainer.tsx +3 -30
  87. package/src/base/primitives/element/ElementTitle.tsx +2 -3
  88. package/src/base/types.ts +7 -5
  89. package/src/bundle/LikeC4View.props.ts +14 -3
  90. package/src/bundle/LikeC4View.tsx +4 -14
  91. package/src/bundle/ReactLikeC4.tsx +1 -2
  92. package/src/custom/customNodes.ts +7 -0
  93. package/src/hooks/useDiagram.ts +10 -0
  94. package/src/likec4diagram/DiagramUI.tsx +1 -1
  95. package/src/likec4diagram/DiagramXYFlow.tsx +10 -5
  96. package/src/likec4diagram/convert-to-xyflow.ts +45 -0
  97. package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +0 -1
  98. package/src/likec4diagram/custom/edges/SequenceStepEdge.tsx +65 -0
  99. package/src/likec4diagram/custom/index.ts +7 -1
  100. package/src/likec4diagram/custom/nodes/ElementActions.tsx +4 -2
  101. package/src/likec4diagram/custom/nodes/SequenceActorNode.tsx +175 -0
  102. package/src/likec4diagram/custom/nodes/index.ts +1 -0
  103. package/src/likec4diagram/custom/nodes/nodes.tsx +1 -1
  104. package/src/likec4diagram/relationship-popover/RelationshipPopover.tsx +36 -30
  105. package/src/likec4diagram/types.ts +96 -13
  106. package/src/likec4diagram/{useViewToNodesEdges.ts → xyflow-diagram/diagram-view.ts} +7 -28
  107. package/src/likec4diagram/xyflow-sequence/_types.ts +45 -0
  108. package/src/likec4diagram/xyflow-sequence/const.ts +37 -0
  109. package/src/likec4diagram/xyflow-sequence/layouter.ts +567 -0
  110. package/src/likec4diagram/xyflow-sequence/sequence-view.ts +337 -0
  111. package/src/likec4diagram/xyflow-sequence/utils.ts +112 -0
  112. package/src/navigationpanel/NavigationPanelControls.tsx +2 -2
  113. package/src/navigationpanel/controls/ViewDetailsCard.tsx +5 -2
  114. package/src/navigationpanel/dropdown/ProjectsMenu.tsx +5 -1
  115. package/src/navigationpanel/walkthrough/ActiveWalkthroughControls.tsx +5 -40
  116. package/src/navigationpanel/walkthrough/DynamicViewControls.tsx +125 -0
  117. package/src/navigationpanel/walkthrough/index.tsx +2 -1
  118. package/src/overlays/relationship-details/RelationshipDetails.tsx +5 -2
  119. package/src/state/DiagramActorProvider.tsx +21 -10
  120. package/src/state/assign.ts +18 -5
  121. package/src/state/diagram-machine.ts +207 -75
  122. package/src/state/utils.ts +81 -7
  123. package/dist/likec4diagram/useViewToNodesEdges.d.ts +0 -12
@@ -0,0 +1,337 @@
1
+ import {
2
+ type BBox,
3
+ type DiagramNode,
4
+ type LayoutedDynamicView,
5
+ type NodeId,
6
+ getParallelStepsPrefix,
7
+ isStepEdgeId,
8
+ RichText,
9
+ } from '@likec4/core/types'
10
+ import { DefaultMap, invariant, nonNullable } from '@likec4/core/utils'
11
+ import { hasAtLeast } from 'remeda'
12
+ import type { Types } from '../types'
13
+ import type { Step } from './_types'
14
+ import {
15
+ CONTINUOUS_OFFSET,
16
+ SeqParallelAreaColor,
17
+ SeqZIndex,
18
+ } from './const'
19
+ import { SequenceViewLayouter } from './layouter'
20
+ import { buildCompounds } from './utils'
21
+
22
+ type Port = {
23
+ step: Step
24
+ row: number
25
+ type: 'source' | 'target'
26
+ position: 'left' | 'right' | 'top' | 'bottom'
27
+ }
28
+
29
+ export function sequenceViewToXY(view: LayoutedDynamicView): {
30
+ bounds: BBox
31
+ xynodes: Array<Types.SequenceActorNode | Types.SequenceParallelArea | Types.ViewGroupNode>
32
+ xyedges: Array<Types.SequenceStepEdge>
33
+ } {
34
+ const actors = [] as Array<DiagramNode>
35
+ const actorPorts = new DefaultMap<DiagramNode, Port[]>(() => [])
36
+
37
+ const steps = [] as Array<Step>
38
+
39
+ const getNode = (id: string) => nonNullable(view.nodes.find(n => n.id === id))
40
+ const parentsLookup: DefaultMap<DiagramNode, DiagramNode[]> = new DefaultMap(
41
+ key => {
42
+ const parent = key.parent ? getNode(key.parent) : null
43
+ if (parent) {
44
+ return [parent, ...parentsLookup.get(parent)]
45
+ }
46
+ return []
47
+ },
48
+ )
49
+
50
+ const addActor = (...[source, target]: [DiagramNode, DiagramNode]) => {
51
+ // source actor not yet added
52
+ if (!actors.includes(source)) {
53
+ const indexOfTarget = actors.indexOf(target)
54
+ if (indexOfTarget > 0) {
55
+ actors.splice(indexOfTarget, 0, source)
56
+ } else {
57
+ actors.push(source)
58
+ }
59
+ }
60
+ if (!actors.includes(target)) {
61
+ actors.push(target)
62
+ }
63
+ }
64
+
65
+ let row = 0
66
+
67
+ for (const edge of view.edges.filter(e => isStepEdgeId(e.id))) {
68
+ const prevStep = steps.at(-1)
69
+ const source = getNode(edge.source)
70
+ const target = getNode(edge.target)
71
+
72
+ if (source.children.length || target.children.length) {
73
+ console.error('Sequence view does not support nested actors')
74
+ continue
75
+ }
76
+
77
+ let sourceColumn = actors.indexOf(source)
78
+ let targetColumn = actors.indexOf(target)
79
+
80
+ const alreadyAdded = sourceColumn >= 0 && targetColumn >= 0
81
+
82
+ if (!alreadyAdded) {
83
+ if (edge.dir === 'back') {
84
+ addActor(target, source)
85
+ } else {
86
+ addActor(source, target)
87
+ }
88
+ sourceColumn = actors.indexOf(source)
89
+ targetColumn = actors.indexOf(target)
90
+ }
91
+
92
+ const isSelfLoop = source === target
93
+ const isBack = sourceColumn > targetColumn
94
+ const parallelPrefix = getParallelStepsPrefix(edge.id)
95
+
96
+ let isContinuing = false
97
+ if (prevStep && prevStep.target == source && prevStep.parallelPrefix === parallelPrefix) {
98
+ isContinuing = prevStep.isSelfLoop !== isSelfLoop || prevStep.isBack === isBack
99
+ }
100
+
101
+ if (!isContinuing) {
102
+ row++
103
+ }
104
+
105
+ const step: Step = {
106
+ id: edge.id,
107
+ from: {
108
+ column: sourceColumn,
109
+ row,
110
+ },
111
+ to: {
112
+ column: targetColumn,
113
+ row: isSelfLoop ? ++row : row,
114
+ },
115
+ edge,
116
+ isSelfLoop,
117
+ isBack,
118
+ parallelPrefix,
119
+ offset: isContinuing ? (prevStep?.offset ?? 0) + CONTINUOUS_OFFSET : 0,
120
+ source,
121
+ target,
122
+ label: edge.labelBBox
123
+ ? {
124
+ height: edge.labelBBox.height + 8 + (edge.navigateTo ? 20 : 0),
125
+ width: edge.labelBBox.width + 16,
126
+ text: edge.label,
127
+ }
128
+ : null,
129
+ }
130
+ steps.push(step)
131
+ actorPorts.get(source).push({ step, row, type: 'source', position: isBack && !isSelfLoop ? 'left' : 'right' })
132
+ actorPorts.get(target).push({ step, row, type: 'target', position: isBack || isSelfLoop ? 'right' : 'left' })
133
+ }
134
+
135
+ // Update columns, as actors may have been re-ordered
136
+ for (const step of steps) {
137
+ step.from.column = actors.indexOf(step.source)
138
+ step.to.column = actors.indexOf(step.target)
139
+ }
140
+
141
+ invariant(hasAtLeast(actors, 1), 'actors array must not be empty')
142
+
143
+ const layout = new SequenceViewLayouter({
144
+ actors,
145
+ steps,
146
+ compounds: buildCompounds(actors, view.nodes),
147
+ })
148
+
149
+ const bounds = layout.getViewBounds()
150
+
151
+ return {
152
+ bounds,
153
+ xynodes: [
154
+ ...layout.getCompoundBoxes().map((box, i) => toCompoundArea(box, i, view)),
155
+ ...layout.getParallelBoxes().map(box => toSeqParallelArea(box, view)),
156
+ ...actors.map(actor =>
157
+ toSeqActorNode({
158
+ actor,
159
+ ports: actorPorts.get(actor),
160
+ bounds,
161
+ layout,
162
+ view,
163
+ })
164
+ ),
165
+ ],
166
+ xyedges: steps.map(({ id, edge, ...step }): Types.SequenceStepEdge => ({
167
+ id: id,
168
+ type: 'seq-step',
169
+ data: {
170
+ id,
171
+ label: step.label?.text ?? null,
172
+ technology: edge.technology,
173
+ notes: RichText.from(edge.notes),
174
+ navigateTo: edge.navigateTo,
175
+ controlPoints: edge.controlPoints ?? null,
176
+ labelBBox: {
177
+ x: 0,
178
+ y: 0,
179
+ width: step.label?.width ?? edge.labelBBox?.width ?? 32,
180
+ height: step.label?.height ?? edge.labelBBox?.height ?? 32,
181
+ },
182
+ labelXY: null,
183
+ points: edge.points,
184
+ color: edge.color ?? 'gray',
185
+ line: edge.line ?? 'solid',
186
+ dir: 'forward',
187
+ head: 'normal',
188
+ tail: 'none',
189
+ },
190
+ selectable: true,
191
+ focusable: false,
192
+ zIndex: 20,
193
+ interactionWidth: 40,
194
+ source: step.source.id,
195
+ sourceHandle: id + '_source',
196
+ target: step.target.id,
197
+ targetHandle: id + '_target',
198
+ })),
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Shows a compound as a view group node
204
+ */
205
+ function toCompoundArea(
206
+ { node, x, y, width, height, depth }: BBox & { node: DiagramNode; depth: number },
207
+ index: number,
208
+ view: LayoutedDynamicView,
209
+ ): Types.ViewGroupNode {
210
+ return {
211
+ id: `${node.id}-${index}` as NodeId,
212
+ type: 'view-group',
213
+ data: {
214
+ id: node.id,
215
+ title: node.title,
216
+ color: node.color ?? 'gray',
217
+ shape: node.shape,
218
+ style: node.style,
219
+ tags: node.tags,
220
+ position: [x, y],
221
+ viewId: view.id,
222
+ depth,
223
+ isViewGroup: true,
224
+ },
225
+ // zIndex: SeqZIndex.compound,
226
+ position: {
227
+ x,
228
+ y,
229
+ },
230
+ draggable: false,
231
+ selectable: false,
232
+ focusable: false,
233
+ style: {
234
+ pointerEvents: 'none',
235
+ },
236
+ width,
237
+ initialWidth: width,
238
+ height,
239
+ initialHeight: height,
240
+ }
241
+ }
242
+
243
+ function toSeqParallelArea(
244
+ { parallelPrefix, x, y, width, height }: BBox & { parallelPrefix: string },
245
+ view: LayoutedDynamicView,
246
+ ): Types.SequenceParallelArea {
247
+ return {
248
+ id: `seq-parallel-${parallelPrefix}` as NodeId,
249
+ type: 'seq-parallel',
250
+ data: {
251
+ id: `seq-parallel-${parallelPrefix}` as NodeId,
252
+ title: 'PARALLEL',
253
+ technology: null,
254
+ color: SeqParallelAreaColor.default,
255
+ shape: 'rectangle',
256
+ style: {},
257
+ tags: [],
258
+ position: [x, y],
259
+ level: 0,
260
+ icon: null,
261
+ width,
262
+ height,
263
+ description: RichText.EMPTY,
264
+ viewId: view.id,
265
+ parallelPrefix,
266
+ },
267
+ zIndex: SeqZIndex.parallel,
268
+ position: {
269
+ x,
270
+ y,
271
+ },
272
+ draggable: false,
273
+ deletable: false,
274
+ selectable: false,
275
+ focusable: false,
276
+ style: {
277
+ pointerEvents: 'none',
278
+ },
279
+ width,
280
+ initialWidth: width,
281
+ height,
282
+ initialHeight: height,
283
+ }
284
+ }
285
+
286
+ function toSeqActorNode({ actor, ports, bounds, layout, view }: {
287
+ actor: DiagramNode
288
+ ports: Port[]
289
+ bounds: BBox
290
+ layout: SequenceViewLayouter
291
+ view: LayoutedDynamicView
292
+ }): Types.SequenceActorNode {
293
+ const { x, y, width, height } = layout.getActorBox(actor)
294
+ return {
295
+ id: actor.id,
296
+ type: 'seq-actor',
297
+ data: {
298
+ id: actor.id,
299
+ position: [x, y],
300
+ level: 0,
301
+ icon: actor.icon ?? null,
302
+ isMultiple: actor.style.multiple ?? false,
303
+ title: actor.title,
304
+ width,
305
+ height,
306
+ color: actor.color,
307
+ navigateTo: actor.navigateTo ?? null,
308
+ shape: actor.shape,
309
+ style: actor.style,
310
+ tags: actor.tags,
311
+ modelFqn: actor.modelRef ?? null,
312
+ technology: actor.technology,
313
+ description: RichText.from(actor.description),
314
+ viewHeight: bounds.height,
315
+ viewId: view.id,
316
+ ports: ports.map((p): Types.SequenceActorNodePort => {
317
+ const bbox = layout.getPortCenter(p.step, p.type)
318
+ return ({
319
+ id: p.step.id + '_' + p.type,
320
+ cx: bbox.cx - x,
321
+ cy: bbox.cy - y,
322
+ height: bbox.height,
323
+ type: p.type,
324
+ position: p.position,
325
+ })
326
+ }),
327
+ },
328
+ deletable: false,
329
+ selectable: true,
330
+ zIndex: SeqZIndex.actor,
331
+ position: { x, y },
332
+ width,
333
+ initialWidth: width,
334
+ height,
335
+ initialHeight: height,
336
+ }
337
+ }
@@ -0,0 +1,112 @@
1
+ import type { DiagramEdge, DiagramNode, EdgeId } from '@likec4/core/types'
2
+ import { isAncestor, nonNullable, Stack } from '@likec4/core/utils'
3
+ import { groupBy, mapValues, pipe, values } from 'remeda'
4
+ import type { Compound, ParallelRect, Step } from './_types'
5
+
6
+ /**
7
+ * From steps find boxes that must be marked as parallel on the layout
8
+ */
9
+ export function findParallelRects(steps: Array<Step>): Array<ParallelRect> {
10
+ return pipe(
11
+ steps,
12
+ groupBy(s => s.parallelPrefix ?? undefined),
13
+ mapValues((steps, parallelPrefix) => {
14
+ return steps.reduce(
15
+ (acc, step) => {
16
+ acc.min.column = Math.min(acc.min.column, step.from.column, step.to.column)
17
+ acc.min.row = Math.min(acc.min.row, step.from.row, step.to.row)
18
+
19
+ acc.max.column = Math.max(acc.max.column, step.from.column, step.to.column)
20
+ acc.max.row = Math.max(acc.max.row, step.from.row, step.to.row)
21
+
22
+ return acc
23
+ },
24
+ {
25
+ parallelPrefix,
26
+ min: {
27
+ column: Infinity,
28
+ row: Infinity,
29
+ },
30
+ max: {
31
+ column: -Infinity,
32
+ row: -Infinity,
33
+ },
34
+ } as ParallelRect,
35
+ )
36
+ }),
37
+ values(),
38
+ )
39
+ }
40
+
41
+ /**
42
+ * Builds a tree of compounds from actors and nodes.
43
+ * @param actors the actors in the sequence view
44
+ * @param nodes the nodes in likec4 diagram
45
+ * @returns an array of compounds where each compound is a node in the sequence view
46
+ * that is an ancestor of one of the actors
47
+ */
48
+ export function buildCompounds(actors: Array<DiagramNode>, nodes: Array<DiagramNode>): Array<Compound> {
49
+ if (actors.length === 0 || actors.length === nodes.length) {
50
+ return []
51
+ }
52
+ const getNode = (id: string) => nonNullable(nodes.find(n => n.id === id))
53
+ function parentsLookup(node: DiagramNode): DiagramNode[] {
54
+ const parent = node.parent ? getNode(node.parent) : null
55
+ if (parent) {
56
+ return [parent, ...parentsLookup(parent)]
57
+ }
58
+ return []
59
+ }
60
+
61
+ const stack = new Stack<Compound>()
62
+ const result = [] as Array<Compound>
63
+
64
+ actors.forEach(actor => {
65
+ const _ancestors = parentsLookup(actor)
66
+ if (_ancestors.length === 0) {
67
+ stack.clear()
68
+ return
69
+ }
70
+ const ancestors = Stack.from(_ancestors)
71
+ let compound: DiagramNode | undefined
72
+ let parent: DiagramNode | undefined
73
+ while (true) {
74
+ compound = stack.peek()?.node
75
+ parent = ancestors.peek()
76
+ if (!parent || !compound) {
77
+ break
78
+ }
79
+ // Drop ancestors that are ancestors of the current compound
80
+ if (isAncestor(parent.id, compound.id) || parent.id === compound.id) {
81
+ ancestors.pop()
82
+ continue
83
+ }
84
+ // Drop compounds that are not ancestors of the current parent
85
+ if (!isAncestor(compound.id, parent.id)) {
86
+ stack.pop()
87
+ continue
88
+ }
89
+ break
90
+ }
91
+
92
+ // Add ancestors to the stack
93
+ while (parent = ancestors.pop()) {
94
+ const parentAsCompound = {
95
+ node: parent,
96
+ from: actor,
97
+ to: actor,
98
+ nested: [],
99
+ }
100
+ let compound = stack.peek()
101
+ if (!compound) {
102
+ result.push(parentAsCompound)
103
+ } else {
104
+ compound.nested.push(parentAsCompound)
105
+ }
106
+ stack.push(parentAsCompound)
107
+ }
108
+ stack.forEach(c => c.to = actor)
109
+ })
110
+
111
+ return result
112
+ }
@@ -13,7 +13,7 @@ import { type NavigationPanelActorSnapshot } from './actor'
13
13
  import { BurgerButton, NavigationButtons, OpenSource, SearchControl, ToggleReadonly, ViewDetailsCard } from './controls'
14
14
  import { useNavigationActor } from './hooks'
15
15
  import { breadcrumbTitle } from './styles.css'
16
- import { StartWalkthroughButton } from './walkthrough'
16
+ import { DynamicViewControls } from './walkthrough'
17
17
 
18
18
  const selectBreadcrumbs = ({ context }: NavigationPanelActorSnapshot) => {
19
19
  const folder = context.viewModel.folder
@@ -126,7 +126,7 @@ export const NavigationPanelControls = () => {
126
126
  <OpenSource />
127
127
  <ToggleReadonly />
128
128
  </m.div>
129
- {enableDynamicViewWalkthrough && isDynamicView && <StartWalkthroughButton key="walkthrough-button" />}
129
+ {enableDynamicViewWalkthrough && isDynamicView && <DynamicViewControls key="dynamic-view-controls" />}
130
130
  {enableSearch && <SearchControl key="search-control" />}
131
131
  </AnimatePresence>
132
132
  )
@@ -41,6 +41,9 @@ export const ViewDetailsCard = (props: PopoverProps) => {
41
41
  shadow="xl"
42
42
  clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
43
43
  trapFocus
44
+ offset={{
45
+ mainAxis: 4,
46
+ }}
44
47
  {...portalProps}
45
48
  {...props}
46
49
  >
@@ -63,8 +66,8 @@ const ViewDetailsCardTrigger = ({ linksCount }: { linksCount: number }) => (
63
66
  'group',
64
67
  hstack({
65
68
  gap: '2',
66
- paddingInline: 'xxs',
67
- paddingBlock: 'xxs',
69
+ paddingInline: '2',
70
+ paddingBlock: '1',
68
71
  rounded: 'sm',
69
72
  userSelect: 'none',
70
73
  cursor: 'pointer',
@@ -23,7 +23,11 @@ export function ProjectsMenu(props: ButtonProps) {
23
23
  }}>
24
24
  Project
25
25
  </Box>
26
- <Menu shadow="md" position="bottom-start" offset={{ mainAxis: 2 }} closeOnItemClick>
26
+ <Menu
27
+ withinPortal={false} // if we render menu in portal, NavigationPanelDropdown receives onMouseLeave event
28
+ shadow="md"
29
+ position="bottom-start"
30
+ offset={{ mainAxis: 2 }}>
27
31
  <MenuTarget>
28
32
  <Button
29
33
  tabIndex={-1}
@@ -1,9 +1,7 @@
1
- import { RichText } from '@likec4/core'
2
1
  import { css } from '@likec4/styles/css'
3
2
  import { Box } from '@likec4/styles/jsx'
4
3
  import { Badge, Button, Portal } from '@mantine/core'
5
4
  import {
6
- IconPlayerPlayFilled,
7
5
  IconPlayerSkipBackFilled,
8
6
  IconPlayerSkipForwardFilled,
9
7
  IconPlayerStopFilled,
@@ -13,40 +11,7 @@ import * as m from 'motion/react-m'
13
11
  import { isTruthy } from 'remeda'
14
12
  import { useMantinePortalProps } from '../../hooks'
15
13
  import { useDiagram, useDiagramContext } from '../../hooks/useDiagram'
16
- import { Tooltip } from '../_common'
17
- import { useNavigationActor } from '../hooks'
18
-
19
- const TriggerWalkthroughButton = Button.withProps({
20
- // Button is polymorphic, but we dont want it to inherit the motion props
21
- component: m.button as any as 'button',
22
- // @ts-expect-error
23
- layoutId: 'trigger-dynamic-walkthrough',
24
- variant: 'filled',
25
- size: 'xs',
26
- fw: '500',
27
- className: css({
28
- flexShrink: 0,
29
- }),
30
- })
31
-
32
- export const StartWalkthroughButton = () => {
33
- const diagram = useDiagram()
34
- const actor = useNavigationActor()
35
- return (
36
- <Tooltip label="Start Dynamic View Walkthrough">
37
- <TriggerWalkthroughButton
38
- onClick={e => {
39
- e.stopPropagation()
40
- actor.closeDropdown()
41
- diagram.startWalkthrough()
42
- }}
43
- rightSection={<IconPlayerPlayFilled size={10} />}
44
- >
45
- Start
46
- </TriggerWalkthroughButton>
47
- </Tooltip>
48
- )
49
- }
14
+ import { TriggerWalkthroughButton } from './DynamicViewControls'
50
15
 
51
16
  const PrevNextButton = Button.withProps({
52
17
  // Button is polymorphic, but we dont want it to inherit the motion props
@@ -82,7 +47,7 @@ const ParallelFrame = () => {
82
47
  )
83
48
  }
84
49
 
85
- export const ActiveWalkthroughControls = () => {
50
+ export function ActiveWalkthroughControls() {
86
51
  const diagram = useDiagram()
87
52
  const {
88
53
  isParallel,
@@ -90,7 +55,6 @@ export const ActiveWalkthroughControls = () => {
90
55
  hasPrevious,
91
56
  currentStep,
92
57
  totalSteps,
93
- notes,
94
58
  } = useDiagramContext(s => {
95
59
  const currentStepIndex = s.xyedges.findIndex(e => e.id === s.activeWalkthrough?.stepId)
96
60
  return ({
@@ -99,14 +63,15 @@ export const ActiveWalkthroughControls = () => {
99
63
  hasPrevious: currentStepIndex > 0,
100
64
  currentStep: currentStepIndex + 1,
101
65
  totalSteps: s.xyedges.length,
102
- notes: s.xyedges[currentStepIndex]?.data?.notes ?? RichText.EMPTY,
103
66
  })
104
67
  })
105
68
 
106
69
  return (
107
70
  <AnimatePresence propagate>
108
71
  <TriggerWalkthroughButton
72
+ key="stop-walkthrough"
109
73
  variant="light"
74
+ size="xs"
110
75
  color="orange"
111
76
  mr={'sm'}
112
77
  onClick={e => {
@@ -161,7 +126,7 @@ export const ActiveWalkthroughControls = () => {
161
126
  >
162
127
  Next
163
128
  </PrevNextButton>
164
- {isParallel && <ParallelFrame />}
129
+ {isParallel && <ParallelFrame key="parallel-frame" />}
165
130
  </AnimatePresence>
166
131
  )
167
132
  }