@likec4/diagram 1.39.5 → 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.
- package/bundle/{chunk-CW_KClZe.js → chunk-CDr-4Ymp.js} +936 -903
- package/bundle/custom.js +61 -60
- package/bundle/index.js +2667 -539
- package/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.js +7 -4
- package/dist/LikeC4Diagram.props.d.ts +6 -1
- package/dist/base/const.d.ts +9 -0
- package/dist/base/const.js +10 -1
- package/dist/base/index.d.ts +1 -0
- package/dist/base/primitives/edge/EdgeLabel.js +2 -1
- package/dist/base/primitives/edge/EdgeLabelContainer.js +10 -4
- package/dist/base/primitives/edge/EdgePath.js +17 -8
- package/dist/base/primitives/edge/edge.css.d.ts +0 -1
- package/dist/base/primitives/edge/edge.css.js +6 -14
- package/dist/base/primitives/element/ElementActionButtons.js +1 -3
- package/dist/base/primitives/element/ElementNodeContainer.d.ts +0 -6
- package/dist/base/primitives/element/ElementNodeContainer.js +3 -26
- package/dist/base/primitives/element/ElementTitle.js +2 -2
- package/dist/base/types.d.ts +3 -3
- package/dist/bundle/LikeC4View.js +4 -12
- package/dist/bundle/LikeC4View.props.d.ts +12 -3
- package/dist/bundle/custom.js +1 -1
- package/dist/custom/customNodes.d.ts +6 -0
- package/dist/custom/customNodes.js +2 -1
- package/dist/custom/index.js +1 -1
- package/dist/hooks/useDiagram.d.ts +5 -1
- package/dist/hooks/useDiagram.js +3 -0
- package/dist/hooks/useXYFlow.d.ts +4 -439
- package/dist/likec4diagram/DiagramUI.js +1 -1
- package/dist/likec4diagram/DiagramXYFlow.js +8 -4
- package/dist/likec4diagram/convert-to-xyflow.d.ts +14 -0
- package/dist/likec4diagram/convert-to-xyflow.js +29 -0
- package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +3 -3
- package/dist/likec4diagram/custom/edges/RelationshipEdge.js +0 -1
- package/dist/likec4diagram/custom/edges/SequenceStepEdge.d.ts +3 -0
- package/dist/likec4diagram/custom/edges/SequenceStepEdge.js +71 -0
- package/dist/likec4diagram/custom/index.js +7 -2
- package/dist/likec4diagram/custom/nodes/ElementActions.d.ts +2 -1
- package/dist/likec4diagram/custom/nodes/SequenceActorNode.d.ts +4 -0
- package/dist/likec4diagram/custom/nodes/SequenceActorNode.js +157 -0
- package/dist/likec4diagram/custom/nodes/index.d.ts +1 -0
- package/dist/likec4diagram/custom/nodes/nodes.d.ts +1 -2
- package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +31 -27
- package/dist/likec4diagram/types.d.ts +41 -7
- package/dist/likec4diagram/xyflow-diagram/diagram-view.d.ts +11 -0
- package/dist/likec4diagram/{useViewToNodesEdges.js → xyflow-diagram/diagram-view.js} +4 -13
- package/dist/likec4diagram/xyflow-sequence/_types.d.ts +41 -0
- package/dist/likec4diagram/xyflow-sequence/const.d.ts +17 -0
- package/dist/likec4diagram/xyflow-sequence/const.js +16 -0
- package/dist/likec4diagram/xyflow-sequence/layouter.d.ts +46 -0
- package/dist/likec4diagram/xyflow-sequence/layouter.js +408 -0
- package/dist/likec4diagram/xyflow-sequence/sequence-view.d.ts +7 -0
- package/dist/likec4diagram/xyflow-sequence/sequence-view.js +273 -0
- package/dist/likec4diagram/xyflow-sequence/utils.d.ts +14 -0
- package/dist/likec4diagram/xyflow-sequence/utils.js +92 -0
- package/dist/navigationpanel/NavigationPanelControls.js +2 -2
- package/dist/navigationpanel/controls/ViewDetailsCard.js +5 -2
- package/dist/navigationpanel/dropdown/ProjectsMenu.js +53 -44
- package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.d.ts +1 -2
- package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.js +10 -41
- package/dist/navigationpanel/walkthrough/DynamicViewControls.d.ts +292 -0
- package/dist/navigationpanel/walkthrough/DynamicViewControls.js +119 -0
- package/dist/navigationpanel/walkthrough/index.d.ts +2 -1
- package/dist/overlays/relationship-details/RelationshipDetails.js +4 -1
- package/dist/state/DiagramActorProvider.d.ts +3 -2
- package/dist/state/DiagramActorProvider.js +19 -11
- package/dist/state/assign.js +11 -4
- package/dist/state/diagram-machine.d.ts +224 -194
- package/dist/state/diagram-machine.js +170 -63
- package/dist/state/utils.d.ts +7 -1
- package/dist/state/utils.js +67 -7
- package/dist/styles-min.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css2.js +1 -1
- package/package.json +8 -7
- package/panda.buildinfo.json +30 -9
- package/src/LikeC4Diagram.props.ts +14 -1
- package/src/LikeC4Diagram.tsx +7 -4
- package/src/base/const.ts +12 -0
- package/src/base/index.ts +2 -0
- package/src/base/primitives/edge/EdgeLabel.tsx +23 -19
- package/src/base/primitives/edge/EdgeLabelContainer.tsx +12 -7
- package/src/base/primitives/edge/EdgePath.tsx +25 -14
- package/src/base/primitives/edge/edge.css.ts +5 -14
- package/src/base/primitives/element/ElementActionButtons.tsx +1 -3
- package/src/base/primitives/element/ElementNodeContainer.tsx +3 -30
- package/src/base/primitives/element/ElementTitle.tsx +2 -3
- package/src/base/types.ts +7 -5
- package/src/bundle/LikeC4View.props.ts +14 -3
- package/src/bundle/LikeC4View.tsx +4 -14
- package/src/bundle/ReactLikeC4.tsx +1 -2
- package/src/custom/customNodes.ts +7 -0
- package/src/hooks/useDiagram.ts +10 -0
- package/src/likec4diagram/DiagramUI.tsx +1 -1
- package/src/likec4diagram/DiagramXYFlow.tsx +10 -5
- package/src/likec4diagram/convert-to-xyflow.ts +45 -0
- package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +0 -1
- package/src/likec4diagram/custom/edges/SequenceStepEdge.tsx +65 -0
- package/src/likec4diagram/custom/index.ts +7 -1
- package/src/likec4diagram/custom/nodes/ElementActions.tsx +4 -2
- package/src/likec4diagram/custom/nodes/SequenceActorNode.tsx +175 -0
- package/src/likec4diagram/custom/nodes/index.ts +1 -0
- package/src/likec4diagram/custom/nodes/nodes.tsx +1 -1
- package/src/likec4diagram/relationship-popover/RelationshipPopover.tsx +36 -30
- package/src/likec4diagram/types.ts +96 -13
- package/src/likec4diagram/{useViewToNodesEdges.ts → xyflow-diagram/diagram-view.ts} +7 -28
- package/src/likec4diagram/xyflow-sequence/_types.ts +45 -0
- package/src/likec4diagram/xyflow-sequence/const.ts +37 -0
- package/src/likec4diagram/xyflow-sequence/layouter.ts +567 -0
- package/src/likec4diagram/xyflow-sequence/sequence-view.ts +337 -0
- package/src/likec4diagram/xyflow-sequence/utils.ts +112 -0
- package/src/navigationpanel/NavigationPanelControls.tsx +2 -2
- package/src/navigationpanel/controls/ViewDetailsCard.tsx +5 -2
- package/src/navigationpanel/dropdown/ProjectsMenu.tsx +5 -1
- package/src/navigationpanel/walkthrough/ActiveWalkthroughControls.tsx +5 -40
- package/src/navigationpanel/walkthrough/DynamicViewControls.tsx +125 -0
- package/src/navigationpanel/walkthrough/index.tsx +2 -1
- package/src/overlays/relationship-details/RelationshipDetails.tsx +5 -2
- package/src/state/DiagramActorProvider.tsx +21 -10
- package/src/state/assign.ts +18 -5
- package/src/state/diagram-machine.ts +207 -75
- package/src/state/utils.ts +81 -7
- 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 {
|
|
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 && <
|
|
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: '
|
|
67
|
-
paddingBlock: '
|
|
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
|
|
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 {
|
|
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
|
|
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
|
}
|