@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
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
DiagramEdge,
|
|
11
11
|
DiagramNode,
|
|
12
12
|
DiagramView,
|
|
13
|
+
DynamicViewDisplayVariant,
|
|
13
14
|
EdgeId,
|
|
14
15
|
Fqn,
|
|
15
16
|
NodeId,
|
|
@@ -29,11 +30,13 @@ import { type EdgeChange, type NodeChange, type Rect, type Viewport, nodeToRect
|
|
|
29
30
|
import type { MouseEvent } from 'react'
|
|
30
31
|
import { clamp, first, hasAtLeast, prop } from 'remeda'
|
|
31
32
|
import type { PartialDeep } from 'type-fest'
|
|
33
|
+
import type {
|
|
34
|
+
ActorLogicFrom,
|
|
35
|
+
ActorRef,
|
|
36
|
+
AnyEventObject,
|
|
37
|
+
Snapshot,
|
|
38
|
+
} from 'xstate'
|
|
32
39
|
import {
|
|
33
|
-
type ActorLogicFrom,
|
|
34
|
-
type ActorRef,
|
|
35
|
-
type AnyEventObject,
|
|
36
|
-
type Snapshot,
|
|
37
40
|
and,
|
|
38
41
|
assertEvent,
|
|
39
42
|
assign,
|
|
@@ -53,6 +56,7 @@ import { type EnabledFeatures, type FeatureName, AllDisabled } from '../context/
|
|
|
53
56
|
import type { OpenSourceParams, ViewPadding } from '../LikeC4Diagram.props'
|
|
54
57
|
import type { Types } from '../likec4diagram/types'
|
|
55
58
|
import { createLayoutConstraints } from '../likec4diagram/useLayoutConstraints'
|
|
59
|
+
import { SeqParallelAreaColor } from '../likec4diagram/xyflow-sequence/const'
|
|
56
60
|
import { overlaysActorLogic } from '../overlays/overlaysActor'
|
|
57
61
|
import { searchActorLogic } from '../search/searchActor'
|
|
58
62
|
import { type AlignmentMode, getAligner, toNodeRect } from './aligners'
|
|
@@ -71,7 +75,7 @@ import {
|
|
|
71
75
|
import { type HotKeyEvent, hotkeyActorLogic } from './hotkeyActor'
|
|
72
76
|
import { DiagramToggledFeaturesPersistence } from './persistence'
|
|
73
77
|
import { type Events as SyncLayoutEvents, syncManualLayoutActorLogic } from './syncManualLayoutActor'
|
|
74
|
-
import { findDiagramEdge, findDiagramNode, focusedBounds, typedSystem } from './utils'
|
|
78
|
+
import { activeSequenceBounds, findDiagramEdge, findDiagramNode, focusedBounds, typedSystem } from './utils'
|
|
75
79
|
|
|
76
80
|
export type XYStoreApi = ReturnType<typeof useStoreApi<Types.Node, Types.Edge>>
|
|
77
81
|
|
|
@@ -91,6 +95,7 @@ export interface Input {
|
|
|
91
95
|
pannable: boolean
|
|
92
96
|
fitViewPadding: ViewPadding
|
|
93
97
|
nodesSelectable: boolean
|
|
98
|
+
dynamicViewVariant?: DynamicViewDisplayVariant | undefined
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
export type ToggledFeatures = Partial<EnabledFeatures>
|
|
@@ -133,6 +138,7 @@ export interface Context extends Input {
|
|
|
133
138
|
syncLayoutActorRef: null | ActorRef<Snapshot<unknown>, SyncLayoutEvents, AnyEventObject>
|
|
134
139
|
|
|
135
140
|
// If Dynamic View
|
|
141
|
+
dynamicViewVariant: DynamicViewDisplayVariant
|
|
136
142
|
activeWalkthrough: null | {
|
|
137
143
|
stepId: StepEdgeId
|
|
138
144
|
parallelPrefix: string | null
|
|
@@ -158,7 +164,7 @@ export type Events =
|
|
|
158
164
|
| { type: 'update.nodeData'; nodeId: NodeId; data: PartialDeep<Types.NodeData> }
|
|
159
165
|
| { type: 'update.edgeData'; edgeId: EdgeId; data: PartialDeep<Types.EdgeData> }
|
|
160
166
|
| { type: 'update.view'; view: DiagramView; xynodes: Types.Node[]; xyedges: Types.Edge[] }
|
|
161
|
-
| { type: 'update.inputs'; inputs: Partial<Omit<Input, 'view' | 'xystore'>> }
|
|
167
|
+
| { type: 'update.inputs'; inputs: Partial<Omit<Input, 'view' | 'xystore' | 'dynamicViewVariant'>> }
|
|
162
168
|
| { type: 'update.features'; features: EnabledFeatures }
|
|
163
169
|
| { type: 'fitDiagram'; duration?: number; bounds?: BBox }
|
|
164
170
|
| ({ type: 'open.source' } & OpenSourceParams)
|
|
@@ -175,6 +181,7 @@ export type Events =
|
|
|
175
181
|
| { type: 'saveManualLayout.schedule' }
|
|
176
182
|
| { type: 'saveManualLayout.cancel' }
|
|
177
183
|
| { type: 'focus.node'; nodeId: NodeId }
|
|
184
|
+
| { type: 'switch.dynamicViewVariant'; variant: DynamicViewDisplayVariant }
|
|
178
185
|
| { type: 'walkthrough.start'; stepId?: StepEdgeId }
|
|
179
186
|
| { type: 'walkthrough.step'; direction: 'next' | 'previous' }
|
|
180
187
|
| { type: 'walkthrough.end' }
|
|
@@ -222,10 +229,13 @@ const _diagramMachine = setup({
|
|
|
222
229
|
guards: {
|
|
223
230
|
'isReady': ({ context }) => context.initialized.xydata && context.initialized.xyflow,
|
|
224
231
|
'enabled: FitView': ({ context }) => context.features.enableFitView,
|
|
225
|
-
'enabled: FocusMode': ({ context }) =>
|
|
232
|
+
'enabled: FocusMode': ({ context }) =>
|
|
233
|
+
context.features.enableFocusMode &&
|
|
234
|
+
(context.view._type !== 'dynamic' || context.dynamicViewVariant !== 'sequence'),
|
|
226
235
|
'enabled: Readonly': ({ context }) => context.features.enableReadOnly,
|
|
227
236
|
'enabled: RelationshipDetails': ({ context }) => context.features.enableRelationshipDetails,
|
|
228
237
|
'enabled: Search': ({ context }) => context.features.enableSearch,
|
|
238
|
+
'enabled: ElementDetails': ({ context }) => context.features.enableElementDetails,
|
|
229
239
|
'not readonly': ({ context }) => !context.features.enableReadOnly,
|
|
230
240
|
'is dynamic view': ({ context }) => context.view._type === 'dynamic',
|
|
231
241
|
'is another view': ({ context, event }) => {
|
|
@@ -238,6 +248,10 @@ const _diagramMachine = setup({
|
|
|
238
248
|
}
|
|
239
249
|
nonexhaustive(event.type)
|
|
240
250
|
},
|
|
251
|
+
'click: node has modelFqn': ({ event }) => {
|
|
252
|
+
assertEvent(event, 'xyflow.nodeClick')
|
|
253
|
+
return 'modelFqn' in event.node.data
|
|
254
|
+
},
|
|
241
255
|
'click: selected node': ({ event }) => {
|
|
242
256
|
assertEvent(event, 'xyflow.nodeClick')
|
|
243
257
|
return event.node.selected === true
|
|
@@ -269,13 +283,28 @@ const _diagramMachine = setup({
|
|
|
269
283
|
type: 'openSource',
|
|
270
284
|
params: _params,
|
|
271
285
|
})),
|
|
272
|
-
|
|
273
286
|
'assign lastClickedNode': assign(({ context, event }) => {
|
|
274
287
|
assertEvent(event, 'xyflow.nodeClick')
|
|
275
288
|
return {
|
|
276
289
|
lastClickedNode: lastClickedNode({ context, event }),
|
|
277
290
|
}
|
|
278
291
|
}),
|
|
292
|
+
'assign: focusedNode': assign(({ context, event }) => {
|
|
293
|
+
let focusedNode
|
|
294
|
+
switch (event.type) {
|
|
295
|
+
case 'xyflow.nodeClick':
|
|
296
|
+
focusedNode = event.node.data.id
|
|
297
|
+
break
|
|
298
|
+
case 'focus.node':
|
|
299
|
+
focusedNode = event.nodeId
|
|
300
|
+
break
|
|
301
|
+
default:
|
|
302
|
+
throw new Error(`Unexpected event type: ${event.type} in action 'assign: focusedNode'`)
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
focusedNode,
|
|
306
|
+
}
|
|
307
|
+
}),
|
|
279
308
|
'reset lastClickedNode': assign(() => ({
|
|
280
309
|
lastClickedNode: null,
|
|
281
310
|
})),
|
|
@@ -315,7 +344,10 @@ const _diagramMachine = setup({
|
|
|
315
344
|
},
|
|
316
345
|
|
|
317
346
|
'xyflow:fitFocusedBounds': ({ context }) => {
|
|
318
|
-
const
|
|
347
|
+
const isActiveSequenceWalkthrough = !!context.activeWalkthrough && context.dynamicViewVariant === 'sequence'
|
|
348
|
+
const { bounds, duration = 450 } = isActiveSequenceWalkthrough
|
|
349
|
+
? activeSequenceBounds({ context })
|
|
350
|
+
: focusedBounds({ context })
|
|
319
351
|
const { width, height, panZoom, transform } = nonNullable(context.xystore).getState()
|
|
320
352
|
|
|
321
353
|
const maxZoom = Math.max(1, transform[2])
|
|
@@ -554,6 +586,61 @@ const _diagramMachine = setup({
|
|
|
554
586
|
})),
|
|
555
587
|
})),
|
|
556
588
|
'update active walkthrough': assign(updateActiveWalkthrough),
|
|
589
|
+
'open element details': sendTo(
|
|
590
|
+
({ system }) => typedSystem(system).overlaysActorRef!,
|
|
591
|
+
({ context, event }, params?: { fqn: Fqn; fromNode?: NodeId | undefined }) => {
|
|
592
|
+
let initiatedFrom = null as null | {
|
|
593
|
+
node: NodeId
|
|
594
|
+
clientRect: Rect
|
|
595
|
+
}
|
|
596
|
+
let fromNodeId: NodeId | undefined, subject: Fqn
|
|
597
|
+
// Use from params if available
|
|
598
|
+
if (params) {
|
|
599
|
+
subject = params.fqn
|
|
600
|
+
fromNodeId = params.fromNode ?? context.view.nodes.find(n => n.modelRef === params.fqn)?.id
|
|
601
|
+
}
|
|
602
|
+
// Use from event if available
|
|
603
|
+
else if (event.type === 'xyflow.nodeClick') {
|
|
604
|
+
invariant('modelFqn' in event.node.data && !!event.node.data.modelFqn, 'No modelFqn in event node data')
|
|
605
|
+
subject = event.node.data.modelFqn
|
|
606
|
+
fromNodeId = event.node.data.id
|
|
607
|
+
}
|
|
608
|
+
else if (event.type === 'open.elementDetails') {
|
|
609
|
+
subject = event.fqn
|
|
610
|
+
fromNodeId = event.fromNode
|
|
611
|
+
}
|
|
612
|
+
// Use from lastClickedNode if available
|
|
613
|
+
else {
|
|
614
|
+
invariant(context.lastClickedNode, 'No last clicked node')
|
|
615
|
+
fromNodeId = nonNullable(context.lastClickedNode).id
|
|
616
|
+
const node = nonNullable(context.xynodes.find(n => n.id === fromNodeId))
|
|
617
|
+
invariant('modelFqn' in node.data && !!node.data.modelFqn, 'No modelFqn in last clicked node')
|
|
618
|
+
subject = node.data.modelFqn
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const internalNode = fromNodeId ? context.xystore.getState().nodeLookup.get(fromNodeId) : null
|
|
622
|
+
if (fromNodeId && internalNode) {
|
|
623
|
+
const nodeRect = nodeToRect(internalNode)
|
|
624
|
+
const zoom = context.xyflow!.getZoom()
|
|
625
|
+
|
|
626
|
+
const clientRect = {
|
|
627
|
+
...context.xyflow!.flowToScreenPosition(nodeRect),
|
|
628
|
+
width: nodeRect.width * zoom,
|
|
629
|
+
height: nodeRect.height * zoom,
|
|
630
|
+
}
|
|
631
|
+
initiatedFrom = {
|
|
632
|
+
node: fromNodeId,
|
|
633
|
+
clientRect,
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return ({
|
|
637
|
+
type: 'open.elementDetails' as const,
|
|
638
|
+
subject,
|
|
639
|
+
currentView: context.view,
|
|
640
|
+
...(initiatedFrom && { initiatedFrom }),
|
|
641
|
+
})
|
|
642
|
+
},
|
|
643
|
+
),
|
|
557
644
|
'emit: walkthroughStarted': emit(({ context }) => {
|
|
558
645
|
const edge = context.xyedges.find(x => x.id === context.activeWalkthrough?.stepId)
|
|
559
646
|
invariant(edge, 'Invalid walkthrough state')
|
|
@@ -602,6 +689,12 @@ const _diagramMachine = setup({
|
|
|
602
689
|
xyedge: event.edge,
|
|
603
690
|
}
|
|
604
691
|
}),
|
|
692
|
+
'assign: dynamicViewVariant': assign(({ event }) => {
|
|
693
|
+
assertEvent(event, 'switch.dynamicViewVariant')
|
|
694
|
+
return {
|
|
695
|
+
dynamicViewVariant: event.variant,
|
|
696
|
+
}
|
|
697
|
+
}),
|
|
605
698
|
},
|
|
606
699
|
}).createMachine({
|
|
607
700
|
initial: 'initializing',
|
|
@@ -630,6 +723,9 @@ const _diagramMachine = setup({
|
|
|
630
723
|
viewport: { x: 0, y: 0, zoom: 1 },
|
|
631
724
|
xyflow: null,
|
|
632
725
|
syncLayoutActorRef: null,
|
|
726
|
+
dynamicViewVariant: input.dynamicViewVariant ?? (
|
|
727
|
+
input.view._type === 'dynamic' ? input.view.variant : 'diagram'
|
|
728
|
+
) ?? 'diagram',
|
|
633
729
|
activeWalkthrough: null,
|
|
634
730
|
}),
|
|
635
731
|
// entry: ({ spawn }) => spawn(layoutActor, { id: 'layout', input: { parent: self } }),
|
|
@@ -736,34 +832,8 @@ const _diagramMachine = setup({
|
|
|
736
832
|
],
|
|
737
833
|
},
|
|
738
834
|
'open.elementDetails': {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
node: NodeId
|
|
742
|
-
clientRect: Rect
|
|
743
|
-
}
|
|
744
|
-
const fromNodeId = event.fromNode ?? context.view.nodes.find(n => n.modelRef === event.fqn)?.id
|
|
745
|
-
const internalNode = fromNodeId ? nonNullable(context.xystore).getState().nodeLookup.get(fromNodeId) : null
|
|
746
|
-
if (fromNodeId && internalNode) {
|
|
747
|
-
const nodeRect = nodeToRect(internalNode)
|
|
748
|
-
const zoom = context.xyflow!.getZoom()
|
|
749
|
-
|
|
750
|
-
const clientRect = {
|
|
751
|
-
...context.xyflow!.flowToScreenPosition(nodeRect),
|
|
752
|
-
width: nodeRect.width * zoom,
|
|
753
|
-
height: nodeRect.height * zoom,
|
|
754
|
-
}
|
|
755
|
-
initiatedFrom = {
|
|
756
|
-
node: fromNodeId,
|
|
757
|
-
clientRect,
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
return ({
|
|
761
|
-
type: 'open.elementDetails' as const,
|
|
762
|
-
subject: event.fqn,
|
|
763
|
-
currentView: context.view,
|
|
764
|
-
...(initiatedFrom && { initiatedFrom }),
|
|
765
|
-
})
|
|
766
|
-
}),
|
|
835
|
+
guard: 'enabled: ElementDetails',
|
|
836
|
+
actions: 'open element details',
|
|
767
837
|
},
|
|
768
838
|
'open.relationshipsBrowser': {
|
|
769
839
|
actions: sendTo(({ system }) => typedSystem(system).overlaysActorRef!, ({ context, event }) => ({
|
|
@@ -876,14 +946,29 @@ const _diagramMachine = setup({
|
|
|
876
946
|
]),
|
|
877
947
|
]) as any,
|
|
878
948
|
actions: [
|
|
879
|
-
assign
|
|
880
|
-
|
|
881
|
-
focusedNode: ({ event }) => event.node.id as NodeId,
|
|
882
|
-
}),
|
|
949
|
+
'assign lastClickedNode',
|
|
950
|
+
'assign: focusedNode',
|
|
883
951
|
'emit: nodeClick',
|
|
884
952
|
],
|
|
885
953
|
target: 'focused',
|
|
886
954
|
},
|
|
955
|
+
{
|
|
956
|
+
// TODO: xstate fails to infer the type of the guard
|
|
957
|
+
guard: and([
|
|
958
|
+
'enabled: ElementDetails',
|
|
959
|
+
'click: node has modelFqn',
|
|
960
|
+
or([
|
|
961
|
+
'click: same node',
|
|
962
|
+
'click: selected node',
|
|
963
|
+
]),
|
|
964
|
+
]) as any,
|
|
965
|
+
actions: [
|
|
966
|
+
'assign lastClickedNode',
|
|
967
|
+
'open source of focused or last clicked node',
|
|
968
|
+
'open element details',
|
|
969
|
+
'emit: nodeClick',
|
|
970
|
+
],
|
|
971
|
+
},
|
|
887
972
|
{
|
|
888
973
|
actions: [
|
|
889
974
|
'assign lastClickedNode',
|
|
@@ -907,9 +992,7 @@ const _diagramMachine = setup({
|
|
|
907
992
|
},
|
|
908
993
|
'focus.node': {
|
|
909
994
|
guard: 'enabled: FocusMode',
|
|
910
|
-
actions: assign
|
|
911
|
-
focusedNode: ({ event }) => event.nodeId as NodeId,
|
|
912
|
-
}),
|
|
995
|
+
actions: 'assign: focusedNode',
|
|
913
996
|
target: 'focused',
|
|
914
997
|
},
|
|
915
998
|
'xyflow.edgeClick': {
|
|
@@ -952,6 +1035,16 @@ const _diagramMachine = setup({
|
|
|
952
1035
|
}),
|
|
953
1036
|
on: {
|
|
954
1037
|
'xyflow.nodeClick': [
|
|
1038
|
+
{
|
|
1039
|
+
guard: and([
|
|
1040
|
+
'click: focused node',
|
|
1041
|
+
'click: node has modelFqn',
|
|
1042
|
+
]) as any,
|
|
1043
|
+
actions: [
|
|
1044
|
+
'open element details',
|
|
1045
|
+
'emit: nodeClick',
|
|
1046
|
+
],
|
|
1047
|
+
},
|
|
955
1048
|
{
|
|
956
1049
|
guard: 'click: focused node',
|
|
957
1050
|
actions: 'emit: nodeClick',
|
|
@@ -959,9 +1052,7 @@ const _diagramMachine = setup({
|
|
|
959
1052
|
},
|
|
960
1053
|
{
|
|
961
1054
|
actions: [
|
|
962
|
-
assign
|
|
963
|
-
lastClickedNode,
|
|
964
|
-
}),
|
|
1055
|
+
'assign lastClickedNode',
|
|
965
1056
|
raise(({ event }) => ({
|
|
966
1057
|
type: 'focus.node',
|
|
967
1058
|
nodeId: event.node.id as NodeId,
|
|
@@ -972,9 +1063,7 @@ const _diagramMachine = setup({
|
|
|
972
1063
|
],
|
|
973
1064
|
'focus.node': {
|
|
974
1065
|
actions: [
|
|
975
|
-
assign
|
|
976
|
-
focusedNode: ({ event }) => event.nodeId,
|
|
977
|
-
}),
|
|
1066
|
+
'assign: focusedNode',
|
|
978
1067
|
'focus on nodes and edges',
|
|
979
1068
|
'open source of focused or last clicked node',
|
|
980
1069
|
'xyflow:fitFocusedBounds',
|
|
@@ -1016,6 +1105,34 @@ const _diagramMachine = setup({
|
|
|
1016
1105
|
'xyflow:fitFocusedBounds',
|
|
1017
1106
|
'emit: walkthroughStarted',
|
|
1018
1107
|
],
|
|
1108
|
+
exit: enqueueActions(({ enqueue, context, event }) => {
|
|
1109
|
+
enqueue.stopChild('hotkey')
|
|
1110
|
+
if (context.viewportBeforeFocus) {
|
|
1111
|
+
enqueue({ type: 'xyflow:setViewport', params: { viewport: context.viewportBeforeFocus } })
|
|
1112
|
+
} else {
|
|
1113
|
+
enqueue('xyflow:fitDiagram')
|
|
1114
|
+
}
|
|
1115
|
+
// Disable parallel areas highlight
|
|
1116
|
+
if (context.dynamicViewVariant === 'sequence' && context.activeWalkthrough?.parallelPrefix) {
|
|
1117
|
+
enqueue.assign({
|
|
1118
|
+
xynodes: context.xynodes.map(n => {
|
|
1119
|
+
if (n.type === 'seq-parallel') {
|
|
1120
|
+
return Base.setData(n, {
|
|
1121
|
+
color: SeqParallelAreaColor.default,
|
|
1122
|
+
})
|
|
1123
|
+
}
|
|
1124
|
+
return n
|
|
1125
|
+
}),
|
|
1126
|
+
})
|
|
1127
|
+
}
|
|
1128
|
+
enqueue('undim everything')
|
|
1129
|
+
enqueue.assign({
|
|
1130
|
+
activeWalkthrough: null,
|
|
1131
|
+
viewportBeforeFocus: null,
|
|
1132
|
+
})
|
|
1133
|
+
|
|
1134
|
+
enqueue('emit: walkthroughStopped')
|
|
1135
|
+
}),
|
|
1019
1136
|
on: {
|
|
1020
1137
|
'key.esc': {
|
|
1021
1138
|
target: 'idle',
|
|
@@ -1089,20 +1206,6 @@ const _diagramMachine = setup({
|
|
|
1089
1206
|
target: 'idle',
|
|
1090
1207
|
},
|
|
1091
1208
|
},
|
|
1092
|
-
exit: enqueueActions(({ enqueue, context, event }) => {
|
|
1093
|
-
enqueue.stopChild('hotkey')
|
|
1094
|
-
if (context.viewportBeforeFocus) {
|
|
1095
|
-
enqueue({ type: 'xyflow:setViewport', params: { viewport: context.viewportBeforeFocus } })
|
|
1096
|
-
} else {
|
|
1097
|
-
enqueue('xyflow:fitDiagram')
|
|
1098
|
-
}
|
|
1099
|
-
enqueue('undim everything')
|
|
1100
|
-
enqueue.assign({
|
|
1101
|
-
activeWalkthrough: null,
|
|
1102
|
-
viewportBeforeFocus: null,
|
|
1103
|
-
})
|
|
1104
|
-
enqueue('emit: walkthroughStopped')
|
|
1105
|
-
}),
|
|
1106
1209
|
},
|
|
1107
1210
|
},
|
|
1108
1211
|
},
|
|
@@ -1123,10 +1226,10 @@ const _diagramMachine = setup({
|
|
|
1123
1226
|
enqueue({
|
|
1124
1227
|
type: 'xyflow:alignNodeFromToAfterNavigate',
|
|
1125
1228
|
params: {
|
|
1126
|
-
fromNode: fromNode.id,
|
|
1229
|
+
fromNode: fromNode.id as NodeId,
|
|
1127
1230
|
toPosition: {
|
|
1128
|
-
x: toNode.position[0],
|
|
1129
|
-
y: toNode.position[1],
|
|
1231
|
+
x: toNode.data.position[0],
|
|
1232
|
+
y: toNode.data.position[1],
|
|
1130
1233
|
},
|
|
1131
1234
|
},
|
|
1132
1235
|
})
|
|
@@ -1208,10 +1311,10 @@ const _diagramMachine = setup({
|
|
|
1208
1311
|
enqueue({
|
|
1209
1312
|
type: 'xyflow:alignNodeFromToAfterNavigate',
|
|
1210
1313
|
params: {
|
|
1211
|
-
fromNode: fromNode.id,
|
|
1314
|
+
fromNode: fromNode.id as NodeId,
|
|
1212
1315
|
toPosition: {
|
|
1213
|
-
x: toNode.position[0],
|
|
1214
|
-
y: toNode.position[1],
|
|
1316
|
+
x: toNode.data.position[0],
|
|
1317
|
+
y: toNode.data.position[1],
|
|
1215
1318
|
},
|
|
1216
1319
|
},
|
|
1217
1320
|
})
|
|
@@ -1223,6 +1326,19 @@ const _diagramMachine = setup({
|
|
|
1223
1326
|
})
|
|
1224
1327
|
enqueue.raise({ type: 'fitDiagram', duration: 200 }, { id: 'fitDiagram', delay: 25 })
|
|
1225
1328
|
}
|
|
1329
|
+
} else {
|
|
1330
|
+
// Check if dynamic view mode changed
|
|
1331
|
+
const nextView = event.view
|
|
1332
|
+
if (
|
|
1333
|
+
nextView._type === 'dynamic' &&
|
|
1334
|
+
context.view._type === 'dynamic' &&
|
|
1335
|
+
nextView.variant !== context.view.variant
|
|
1336
|
+
) {
|
|
1337
|
+
enqueue({
|
|
1338
|
+
type: 'xyflow:setViewportCenter',
|
|
1339
|
+
params: BBox.center(nextView.bounds),
|
|
1340
|
+
})
|
|
1341
|
+
}
|
|
1226
1342
|
}
|
|
1227
1343
|
enqueue.assign({
|
|
1228
1344
|
...mergeXYNodesEdges({ context, event }),
|
|
@@ -1249,6 +1365,9 @@ const _diagramMachine = setup({
|
|
|
1249
1365
|
'ensure search actor state',
|
|
1250
1366
|
],
|
|
1251
1367
|
},
|
|
1368
|
+
'switch.dynamicViewVariant': {
|
|
1369
|
+
actions: 'assign: dynamicViewVariant',
|
|
1370
|
+
},
|
|
1252
1371
|
},
|
|
1253
1372
|
exit: [
|
|
1254
1373
|
'stopSyncLayout',
|
|
@@ -1270,14 +1389,27 @@ const _diagramMachine = setup({
|
|
|
1270
1389
|
],
|
|
1271
1390
|
})
|
|
1272
1391
|
|
|
1273
|
-
function nodeRef(node:
|
|
1274
|
-
|
|
1392
|
+
function nodeRef(node: Types.Node) {
|
|
1393
|
+
switch (node.type) {
|
|
1394
|
+
case 'element':
|
|
1395
|
+
case 'compound-element':
|
|
1396
|
+
case 'seq-actor':
|
|
1397
|
+
return node.data.modelFqn
|
|
1398
|
+
case 'deployment':
|
|
1399
|
+
case 'compound-deployment':
|
|
1400
|
+
return node.data.modelFqn ?? node.data.deploymentFqn
|
|
1401
|
+
case 'seq-parallel':
|
|
1402
|
+
case 'view-group':
|
|
1403
|
+
return null
|
|
1404
|
+
default:
|
|
1405
|
+
nonexhaustive(node)
|
|
1406
|
+
}
|
|
1275
1407
|
}
|
|
1276
|
-
function findCorrespondingNode(context: Context, event: { view: DiagramView }) {
|
|
1408
|
+
function findCorrespondingNode(context: Context, event: { view: DiagramView; xynodes: Types.Node[] }) {
|
|
1277
1409
|
const fromNodeId = context.lastOnNavigate?.fromNode
|
|
1278
|
-
const fromNode = fromNodeId && context.
|
|
1410
|
+
const fromNode = fromNodeId && context.xynodes.find(n => n.id === fromNodeId)
|
|
1279
1411
|
const fromRef = fromNode && nodeRef(fromNode)
|
|
1280
|
-
const toNode = fromRef && event.
|
|
1412
|
+
const toNode = fromRef && event.xynodes.find(n => nodeRef(n) === fromRef)
|
|
1281
1413
|
return { fromNode, toNode }
|
|
1282
1414
|
}
|
|
1283
1415
|
|
package/src/state/utils.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type DiagramEdge, type DiagramNode, type XYPoint, BBox } from '@likec4/core/types'
|
|
2
|
+
import { nonNullable } from '@likec4/core/utils'
|
|
3
|
+
import { getEdgePosition, getNodeDimensions, getNodesBounds } from '@xyflow/system'
|
|
2
4
|
import type { ActorSystem } from 'xstate'
|
|
5
|
+
import type { XYStoreState } from '../hooks'
|
|
6
|
+
import type { Types } from '../likec4diagram/types'
|
|
3
7
|
import type { OverlaysActorRef } from '../overlays/overlaysActor'
|
|
4
8
|
import type { SearchActorRef } from '../search/searchActor'
|
|
5
9
|
import type { Context } from './diagram-machine'
|
|
@@ -47,12 +51,12 @@ export function focusedBounds(params: { context: Context }): { bounds: BBox; dur
|
|
|
47
51
|
|
|
48
52
|
const width = node.measured?.width ?? node.width ?? node.initialWidth
|
|
49
53
|
const height = node.measured?.height ?? node.height ?? node.initialHeight
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
|
|
55
|
+
acc.minX = Math.min(acc.minX, position.x)
|
|
56
|
+
acc.minY = Math.min(acc.minY, position.y)
|
|
57
|
+
acc.maxX = Math.max(acc.maxX, position.x + width)
|
|
58
|
+
acc.maxY = Math.max(acc.maxY, position.y + height)
|
|
59
|
+
return acc
|
|
56
60
|
}, {
|
|
57
61
|
minX: Infinity,
|
|
58
62
|
minY: Infinity,
|
|
@@ -76,3 +80,73 @@ export function focusedBounds(params: { context: Context }): { bounds: BBox; dur
|
|
|
76
80
|
},
|
|
77
81
|
}
|
|
78
82
|
}
|
|
83
|
+
|
|
84
|
+
const MARGIN = 32
|
|
85
|
+
export function activeSequenceBounds(params: { context: Context }): { bounds: BBox; duration?: number } {
|
|
86
|
+
const activeWalkthrough = nonNullable(params.context.activeWalkthrough)
|
|
87
|
+
|
|
88
|
+
const stepEdge = nonNullable(params.context.xyedges.find(e => e.id === activeWalkthrough.stepId))
|
|
89
|
+
const xystate = params.context.xystore.getState()
|
|
90
|
+
|
|
91
|
+
const sourceNode = nonNullable(xystate.nodeLookup.get(stepEdge.source))
|
|
92
|
+
const targetNode = nonNullable(xystate.nodeLookup.get(stepEdge.target))
|
|
93
|
+
|
|
94
|
+
const actorsBounds = getNodesBounds([sourceNode, targetNode], xystate)
|
|
95
|
+
|
|
96
|
+
let stepBounds: BBox | undefined | null
|
|
97
|
+
if (activeWalkthrough.parallelPrefix) {
|
|
98
|
+
const parallelArea = params.context.xynodes.find(n =>
|
|
99
|
+
n.type === 'seq-parallel' && n.data.parallelPrefix === activeWalkthrough.parallelPrefix
|
|
100
|
+
)
|
|
101
|
+
if (parallelArea) {
|
|
102
|
+
stepBounds = {
|
|
103
|
+
x: parallelArea.position.x,
|
|
104
|
+
y: parallelArea.position.y,
|
|
105
|
+
...getNodeDimensions(parallelArea),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
stepBounds ??= getEdgeBounds(stepEdge, xystate)
|
|
111
|
+
|
|
112
|
+
if (stepBounds) {
|
|
113
|
+
stepBounds = BBox.merge(stepBounds, actorsBounds)
|
|
114
|
+
} else {
|
|
115
|
+
stepBounds = actorsBounds
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
duration: 350,
|
|
120
|
+
bounds: BBox.expand(
|
|
121
|
+
stepBounds,
|
|
122
|
+
MARGIN,
|
|
123
|
+
),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getEdgeBounds(edge: Types.Edge, store: XYStoreState): BBox | null {
|
|
128
|
+
const sourceNode = store.nodeLookup.get(edge.source)
|
|
129
|
+
const targetNode = store.nodeLookup.get(edge.target)
|
|
130
|
+
|
|
131
|
+
if (!sourceNode || !targetNode) {
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const edgePosition = getEdgePosition({
|
|
136
|
+
id: edge.id,
|
|
137
|
+
sourceNode,
|
|
138
|
+
targetNode,
|
|
139
|
+
sourceHandle: edge.sourceHandle || null,
|
|
140
|
+
targetHandle: edge.targetHandle || null,
|
|
141
|
+
connectionMode: store.connectionMode,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
if (!edgePosition) {
|
|
145
|
+
return null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return BBox.fromPoints([
|
|
149
|
+
[edgePosition.sourceX, edgePosition.sourceY],
|
|
150
|
+
[edgePosition.targetX, edgePosition.targetY],
|
|
151
|
+
])
|
|
152
|
+
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { DiagramView, WhereOperator } from '@likec4/core';
|
|
2
|
-
import { Types } from './types';
|
|
3
|
-
type ViewToNodesEdgeInput = {
|
|
4
|
-
view: DiagramView;
|
|
5
|
-
where: WhereOperator | null;
|
|
6
|
-
nodesSelectable: boolean;
|
|
7
|
-
};
|
|
8
|
-
export declare function useViewToNodesEdges(params: ViewToNodesEdgeInput): {
|
|
9
|
-
xynodes: Types.Node[];
|
|
10
|
-
xyedges: Types.Edge[];
|
|
11
|
-
};
|
|
12
|
-
export {};
|