@likec4/diagram 1.27.1 → 1.27.3
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/index.js +2152 -6757
- package/dist/LikeC4Diagram.js +1 -1
- package/dist/LikeC4Diagram.props.d.ts +5 -3
- package/dist/StaticLikeC4Diagram.js +1 -1
- package/dist/_virtual/set.js +5 -0
- package/dist/_virtual/set2.js +3 -0
- package/dist/base/BaseXYFlow.d.ts +2 -1
- package/dist/base/primitives/compound/CompoundNodeContainer.d.ts +1 -1
- package/dist/base/primitives/compound/CompoundNodeContainer.js +0 -1
- package/dist/base/primitives/edge/EdgeLabelContainer.js +3 -2
- package/dist/base/primitives/element/ElementActionButtons.js +8 -11
- package/dist/base/primitives/element/ElementNodeContainer.css.js +4 -4
- package/dist/base/updateEdges.js +1 -9
- package/dist/base/updateNodes.js +1 -12
- package/dist/bundle/LikeC4View.js +2 -2
- package/dist/bundle/LikeC4View.props.d.ts +7 -7
- package/dist/hooks/useCurrentViewId.js +8 -2
- package/dist/hooks/useDiagram.d.ts +1522 -685
- package/dist/hooks/useDiagram.js +21 -24
- package/dist/likec4diagram/DiagramXYFlow.js +6 -6
- package/dist/likec4diagram/ui/notation/NotationPanel.css.js +3 -3
- package/dist/likec4diagram/ui/notation/NotationPanel.js +10 -4
- package/dist/node_modules/.pnpm/mnemonist@0.40.3_patch_hash_5c12abb43fcff6fd81e53021b89075d53249640d165a2556cc1e58d71201a13a/node_modules/mnemonist/set.js +369 -0
- package/dist/overlays/Overlays.js +3 -1
- package/dist/overlays/element-details/actor.d.ts +2769 -173
- package/dist/overlays/overlay/Overlay.css.d.ts +26 -2
- package/dist/overlays/overlay/Overlay.css.js +66 -18
- package/dist/overlays/overlay/Overlay.d.ts +7 -6
- package/dist/overlays/overlay/Overlay.js +12 -10
- package/dist/overlays/overlaysActor.js +2 -2
- package/dist/overlays/relationship-details/RelationshipDetails.js +113 -70
- package/dist/overlays/relationship-details/SelectEdge.d.ts +1 -1
- package/dist/overlays/relationship-details/SelectEdge.js +15 -9
- package/dist/overlays/relationship-details/_types.d.ts +4 -1
- package/dist/overlays/relationship-details/actor.d.ts +425 -128
- package/dist/overlays/relationship-details/actor.js +157 -53
- package/dist/overlays/relationship-details/compute.d.ts +12 -0
- package/dist/overlays/relationship-details/compute.js +139 -0
- package/dist/overlays/relationship-details/custom/ElementActions.js +27 -5
- package/dist/overlays/relationship-details/custom/edgeTypes.js +1 -1
- package/dist/overlays/relationship-details/custom/nodeTypes.d.ts +2 -1
- package/dist/overlays/relationship-details/hooks.d.ts +336 -6
- package/dist/overlays/relationship-details/hooks.js +11 -12
- package/dist/overlays/relationship-details/layout-to-xyflow.d.ts +12 -0
- package/dist/overlays/relationship-details/layout-to-xyflow.js +145 -0
- package/dist/overlays/relationship-details/layout.d.ts +22 -0
- package/dist/overlays/relationship-details/layout.js +314 -0
- package/dist/overlays/relationships-browser/RelationshipsBrowser.d.ts +3 -1
- package/dist/overlays/relationships-browser/RelationshipsBrowser.js +2 -2
- package/dist/overlays/relationships-browser/_types.d.ts +2 -0
- package/dist/overlays/relationships-browser/actor.d.ts +130 -14
- package/dist/overlays/relationships-browser/actor.js +30 -16
- package/dist/overlays/relationships-browser/custom/ElementActions.js +5 -3
- package/dist/overlays/relationships-browser/custom/edgeTypes.d.ts +3 -1
- package/dist/overlays/relationships-browser/custom/edgeTypes.js +1 -1
- package/dist/overlays/relationships-browser/hooks.d.ts +3 -1
- package/dist/overlays/relationships-browser/hooks.js +6 -9
- package/dist/overlays/relationships-browser/{-useRelationshipsView.d.ts → layout.d.ts} +3 -1
- package/dist/overlays/relationships-browser/{-useRelationshipsView.js → layout.js} +21 -8
- package/dist/overlays/relationships-browser/useViewToNodesEdges.d.ts +1 -1
- package/dist/overlays/relationships-browser/useViewToNodesEdges.js +3 -1
- package/dist/packages/core/src/compute-view/relationships-view/compute.js +1 -1
- package/dist/packages/core/src/model/connection/model/ConnectionModel.js +121 -0
- package/dist/packages/core/src/model/connection/model/find.js +39 -0
- package/dist/packages/core/src/model/connection/ops.js +7 -0
- package/dist/packages/core/src/utils/const.js +3 -0
- package/dist/packages/core/src/utils/fqn.js +9 -1
- package/dist/packages/core/src/utils/iterable/find.js +21 -0
- package/dist/packages/core/src/utils/iterable/some.js +21 -0
- package/dist/packages/core/src/utils/set.js +48 -0
- package/dist/packages/core/src/utils/string-hash.js +13 -0
- package/dist/state/diagram-machine.d.ts +1552 -703
- package/dist/state/diagram-machine.js +39 -3
- package/dist/state/syncManualLayoutActor.d.ts +1 -1
- package/dist/styles.css.js +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/package.json +11 -11
- package/src/LikeC4Diagram.props.ts +7 -3
- package/src/LikeC4Diagram.tsx +1 -1
- package/src/StaticLikeC4Diagram.tsx +1 -1
- package/src/base/BaseXYFlow.tsx +2 -1
- package/src/base/primitives/compound/CompoundNodeContainer.tsx +0 -2
- package/src/base/primitives/edge/EdgeLabelContainer.tsx +7 -5
- package/src/base/primitives/element/ElementActionButtons.tsx +10 -11
- package/src/base/primitives/element/ElementNodeContainer.css.ts +4 -4
- package/src/base/updateEdges.ts +8 -8
- package/src/base/updateNodes.ts +11 -11
- package/src/bundle/LikeC4View.props.ts +7 -7
- package/src/bundle/LikeC4View.tsx +2 -2
- package/src/hooks/useCurrentViewId.ts +4 -2
- package/src/hooks/useDiagram.ts +22 -24
- package/src/likec4diagram/DiagramXYFlow.tsx +6 -6
- package/src/likec4diagram/ui/notation/NotationPanel.css.ts +2 -2
- package/src/likec4diagram/ui/notation/NotationPanel.tsx +8 -6
- package/src/overlays/Overlays.tsx +4 -2
- package/src/overlays/overlay/Overlay.css.ts +67 -17
- package/src/overlays/overlay/Overlay.tsx +27 -24
- package/src/overlays/overlaysActor.ts +1 -1
- package/src/overlays/relationship-details/RelationshipDetails.tsx +131 -68
- package/src/overlays/relationship-details/SelectEdge.tsx +19 -12
- package/src/overlays/relationship-details/_types.ts +3 -1
- package/src/overlays/relationship-details/actor.ts +212 -68
- package/src/overlays/relationship-details/compute.ts +186 -0
- package/src/overlays/relationship-details/custom/ElementActions.tsx +27 -18
- package/src/overlays/relationship-details/custom/edgeTypes.tsx +1 -1
- package/src/overlays/relationship-details/hooks.ts +12 -13
- package/src/overlays/relationship-details/layout-to-xyflow.ts +172 -0
- package/src/overlays/relationship-details/layout.ts +470 -0
- package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +2 -2
- package/src/overlays/relationships-browser/_types.ts +2 -0
- package/src/overlays/relationships-browser/actor.ts +39 -14
- package/src/overlays/relationships-browser/custom/ElementActions.tsx +5 -3
- package/src/overlays/relationships-browser/custom/edgeTypes.tsx +1 -1
- package/src/overlays/relationships-browser/hooks.ts +7 -10
- package/src/overlays/relationships-browser/{-useRelationshipsView.ts → layout.ts} +26 -33
- package/src/overlays/relationships-browser/useViewToNodesEdges.ts +3 -1
- package/src/state/diagram-machine.ts +51 -6
- package/src/utils/index.ts +0 -1
- package/dist/overlays/relationship-details/useLayoutedDetails.d.ts +0 -19
- package/dist/overlays/relationship-details/useLayoutedDetails.js +0 -267
- package/dist/utils/types.d.ts +0 -8
- package/src/overlays/relationship-details/useLayoutedDetails.ts +0 -376
- package/src/utils/types.ts +0 -6
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type BBox,
|
|
3
|
+
type DiagramView,
|
|
4
|
+
type EdgeId,
|
|
5
|
+
type ExclusiveUnion,
|
|
6
|
+
type Fqn,
|
|
7
|
+
type ViewId,
|
|
8
|
+
invariant,
|
|
9
|
+
} from '@likec4/core'
|
|
2
10
|
import {
|
|
3
11
|
type EdgeChange,
|
|
4
12
|
type NodeChange,
|
|
5
13
|
type ReactFlowInstance,
|
|
14
|
+
type useStoreApi,
|
|
6
15
|
applyEdgeChanges,
|
|
7
16
|
applyNodeChanges,
|
|
17
|
+
getViewportForBounds,
|
|
8
18
|
} from '@xyflow/react'
|
|
9
|
-
import {
|
|
19
|
+
import type { InternalNodeUpdate } from '@xyflow/system'
|
|
20
|
+
import { shallowEqual } from 'fast-equals'
|
|
21
|
+
import { isString, prop } from 'remeda'
|
|
10
22
|
import {
|
|
11
23
|
type ActorLogicFrom,
|
|
12
24
|
type ActorRefFromLogic,
|
|
13
25
|
type SnapshotFrom,
|
|
26
|
+
assertEvent,
|
|
14
27
|
assign,
|
|
15
28
|
cancel,
|
|
16
29
|
enqueueActions,
|
|
@@ -19,30 +32,68 @@ import {
|
|
|
19
32
|
} from 'xstate'
|
|
20
33
|
import { Base } from '../../base'
|
|
21
34
|
import { MinZoom } from '../../base/const'
|
|
35
|
+
import { updateEdges } from '../../base/updateEdges'
|
|
36
|
+
import { updateNodes } from '../../base/updateNodes'
|
|
22
37
|
import type { RelationshipDetailsTypes } from './_types'
|
|
38
|
+
import type { LayoutResult } from './layout'
|
|
39
|
+
import { layoutResultToXYFlow } from './layout-to-xyflow'
|
|
23
40
|
|
|
24
41
|
type XYFLowInstance = ReactFlowInstance<RelationshipDetailsTypes.Node, RelationshipDetailsTypes.Edge>
|
|
42
|
+
type XYStoreApi = ReturnType<typeof useStoreApi<RelationshipDetailsTypes.Node, RelationshipDetailsTypes.Edge>>
|
|
43
|
+
|
|
44
|
+
export type Input = ExclusiveUnion<{
|
|
45
|
+
Edge: {
|
|
46
|
+
edgeId: EdgeId
|
|
47
|
+
viewId: ViewId
|
|
48
|
+
}
|
|
49
|
+
Between: {
|
|
50
|
+
source: Fqn
|
|
51
|
+
target: Fqn
|
|
52
|
+
viewId: ViewId
|
|
53
|
+
}
|
|
54
|
+
}>
|
|
25
55
|
|
|
26
|
-
|
|
56
|
+
type Subject = {
|
|
27
57
|
edgeId: EdgeId
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
58
|
+
// relationships: null
|
|
59
|
+
} | {
|
|
60
|
+
source: Fqn
|
|
61
|
+
target: Fqn
|
|
62
|
+
edgeId?: never
|
|
63
|
+
// relationships: null
|
|
31
64
|
}
|
|
32
65
|
|
|
33
|
-
export type Context = Readonly<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
export type Context = Readonly<{
|
|
67
|
+
// parentRef: AnyActorRef | null
|
|
68
|
+
subject: Subject
|
|
69
|
+
// Scope
|
|
70
|
+
viewId: ViewId
|
|
71
|
+
xyflow: XYFLowInstance | null
|
|
72
|
+
xystore: XYStoreApi | null
|
|
73
|
+
initialized: {
|
|
74
|
+
xydata: boolean
|
|
75
|
+
xyflow: boolean
|
|
76
|
+
}
|
|
77
|
+
xynodes: RelationshipDetailsTypes.Node[]
|
|
78
|
+
xyedges: RelationshipDetailsTypes.Edge[]
|
|
79
|
+
bounds: BBox
|
|
80
|
+
}>
|
|
81
|
+
|
|
82
|
+
function inputToSubject(input: { edgeId: EdgeId } | { source: Fqn; target: Fqn }): Context['subject'] {
|
|
83
|
+
if ('edgeId' in input) {
|
|
84
|
+
invariant(isString(input.edgeId), 'edgeId is required')
|
|
85
|
+
return {
|
|
86
|
+
edgeId: input.edgeId,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
source: input.source,
|
|
91
|
+
target: input.target,
|
|
41
92
|
}
|
|
42
|
-
|
|
93
|
+
}
|
|
43
94
|
|
|
44
95
|
export type Events =
|
|
45
|
-
| { type: 'xyflow.init'; instance: XYFLowInstance }
|
|
96
|
+
| { type: 'xyflow.init'; instance: XYFLowInstance; store: XYStoreApi }
|
|
46
97
|
| { type: 'xyflow.nodeClick'; node: RelationshipDetailsTypes.Node }
|
|
47
98
|
| { type: 'xyflow.edgeClick'; edge: RelationshipDetailsTypes.Edge }
|
|
48
99
|
| { type: 'xyflow.edgeMouseEnter'; edge: RelationshipDetailsTypes.Edge }
|
|
@@ -53,15 +104,16 @@ export type Events =
|
|
|
53
104
|
| { type: 'xyflow.applyNodeChanges'; changes: NodeChange<RelationshipDetailsTypes.Node>[] }
|
|
54
105
|
| { type: 'xyflow.applyEdgeChanges'; changes: EdgeChange<RelationshipDetailsTypes.Edge>[] }
|
|
55
106
|
| { type: 'xyflow.paneClick' }
|
|
107
|
+
| { type: 'xyflow.paneDblClick' }
|
|
56
108
|
| { type: 'xyflow.resized' }
|
|
109
|
+
| { type: 'xyflow.updateNodeInternals' }
|
|
57
110
|
| {
|
|
58
|
-
type: 'update.
|
|
59
|
-
|
|
60
|
-
xyedges: RelationshipDetailsTypes.Edge[]
|
|
111
|
+
type: 'update.layoutData'
|
|
112
|
+
data: LayoutResult
|
|
61
113
|
}
|
|
62
114
|
// | { type: 'update.bounds'; bounds: BBox }
|
|
63
115
|
| { type: 'fitDiagram'; duration?: number; bounds?: BBox }
|
|
64
|
-
| { type: 'navigate.to'; edgeId: EdgeId }
|
|
116
|
+
| { type: 'navigate.to'; params: { edgeId: EdgeId; viewId?: ViewId } | { source: Fqn; target: Fqn; viewId?: ViewId } }
|
|
65
117
|
| { type: 'close' }
|
|
66
118
|
|
|
67
119
|
export const relationshipDetailsLogic = setup({
|
|
@@ -72,16 +124,18 @@ export const relationshipDetailsLogic = setup({
|
|
|
72
124
|
},
|
|
73
125
|
actions: {
|
|
74
126
|
'xyflow:fitDiagram': ({ context }, params?: { duration?: number; bounds?: BBox }) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} = params ?? {}
|
|
79
|
-
const { xyflow } = context
|
|
127
|
+
let { duration, bounds } = params ?? {}
|
|
128
|
+
duration ??= 450
|
|
129
|
+
const { xyflow, xystore } = context
|
|
80
130
|
invariant(xyflow, 'xyflow is not initialized')
|
|
131
|
+
invariant(xystore, 'xystore is not initialized')
|
|
132
|
+
bounds ??= context.bounds
|
|
133
|
+
const maxZoom = Math.max(xyflow.getZoom(), 1)
|
|
81
134
|
if (bounds) {
|
|
82
|
-
|
|
135
|
+
const { width, height } = xystore.getState()
|
|
136
|
+
const viewport = getViewportForBounds(bounds, width, height, MinZoom, maxZoom, 0.1)
|
|
137
|
+
xyflow.setViewport(viewport, duration > 0 ? { duration } : undefined)
|
|
83
138
|
} else {
|
|
84
|
-
const maxZoom = Math.max(xyflow.getZoom(), 1)
|
|
85
139
|
xyflow.fitView({
|
|
86
140
|
minZoom: MinZoom,
|
|
87
141
|
maxZoom,
|
|
@@ -90,45 +144,120 @@ export const relationshipDetailsLogic = setup({
|
|
|
90
144
|
})
|
|
91
145
|
}
|
|
92
146
|
},
|
|
147
|
+
'xyflow:updateNodeInternals': ({ context }) => {
|
|
148
|
+
invariant(context.xystore, 'xystore is not initialized')
|
|
149
|
+
const { domNode, updateNodeInternals } = context.xystore.getState()
|
|
150
|
+
const nodeIds = new Set(context.xyedges.flatMap((e) => [e.source, e.target]))
|
|
151
|
+
|
|
152
|
+
if (nodeIds.size === 0 || !domNode) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const updates = new Map<string, InternalNodeUpdate>()
|
|
157
|
+
|
|
158
|
+
for (const updateId of nodeIds) {
|
|
159
|
+
const nodeElement = domNode.querySelector(`.react-flow__node[data-id="${updateId}"]`) as HTMLDivElement
|
|
160
|
+
if (nodeElement) {
|
|
161
|
+
updates.set(updateId, { id: updateId, nodeElement, force: true })
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
updateNodeInternals(updates, { triggerFitView: false })
|
|
166
|
+
},
|
|
167
|
+
'updateXYFlow': assign(({ context, event }) => {
|
|
168
|
+
assertEvent(event, 'xyflow.init')
|
|
169
|
+
let initialized = context.initialized
|
|
170
|
+
if (!initialized.xyflow) {
|
|
171
|
+
initialized = {
|
|
172
|
+
...initialized,
|
|
173
|
+
xyflow: true,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
initialized,
|
|
178
|
+
xyflow: event.instance,
|
|
179
|
+
xystore: event.store,
|
|
180
|
+
}
|
|
181
|
+
}),
|
|
182
|
+
'updateLayoutData': assign(({ context, event }) => {
|
|
183
|
+
assertEvent(event, 'update.layoutData')
|
|
184
|
+
const { xynodes, xyedges, bounds } = layoutResultToXYFlow(event.data)
|
|
185
|
+
let initialized = context.initialized
|
|
186
|
+
if (!initialized.xydata) {
|
|
187
|
+
initialized = {
|
|
188
|
+
...initialized,
|
|
189
|
+
xydata: true,
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
initialized,
|
|
194
|
+
xynodes: updateNodes(context.xynodes, xynodes),
|
|
195
|
+
xyedges: updateEdges(context.xyedges, xyedges),
|
|
196
|
+
bounds: shallowEqual(context.bounds, bounds) ? context.bounds : bounds,
|
|
197
|
+
}
|
|
198
|
+
}),
|
|
93
199
|
},
|
|
94
200
|
guards: {
|
|
201
|
+
'isReady': ({ context }) => context.initialized.xydata && context.initialized.xyflow,
|
|
95
202
|
'enable: navigate.to': () => true,
|
|
96
203
|
},
|
|
97
204
|
}).createMachine({
|
|
98
|
-
initial: '
|
|
205
|
+
initial: 'initializing',
|
|
99
206
|
context: ({ input }) => ({
|
|
100
|
-
|
|
101
|
-
|
|
207
|
+
subject: inputToSubject(input),
|
|
208
|
+
viewId: input.viewId,
|
|
209
|
+
bounds: {
|
|
210
|
+
x: 0,
|
|
211
|
+
y: 0,
|
|
212
|
+
width: 200,
|
|
213
|
+
height: 200,
|
|
214
|
+
},
|
|
215
|
+
initialized: {
|
|
216
|
+
xydata: false,
|
|
217
|
+
xyflow: false,
|
|
218
|
+
},
|
|
102
219
|
xyflow: null,
|
|
220
|
+
xystore: null,
|
|
103
221
|
xynodes: [],
|
|
104
222
|
xyedges: [],
|
|
105
223
|
}),
|
|
106
224
|
states: {
|
|
107
|
-
'
|
|
225
|
+
'initializing': {
|
|
108
226
|
on: {
|
|
109
227
|
'xyflow.init': {
|
|
110
|
-
actions:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
228
|
+
actions: 'updateXYFlow',
|
|
229
|
+
target: 'isReady',
|
|
230
|
+
},
|
|
231
|
+
'update.layoutData': {
|
|
232
|
+
actions: 'updateLayoutData',
|
|
233
|
+
target: 'isReady',
|
|
115
234
|
},
|
|
116
235
|
'close': {
|
|
117
236
|
target: 'closed',
|
|
118
237
|
},
|
|
119
238
|
},
|
|
120
239
|
},
|
|
121
|
-
'
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
240
|
+
'isReady': {
|
|
241
|
+
always: [{
|
|
242
|
+
guard: 'isReady',
|
|
243
|
+
actions: [
|
|
244
|
+
{ type: 'xyflow:fitDiagram', params: { duration: 0 } },
|
|
245
|
+
raise({ type: 'xyflow.updateNodeInternals' }, { delay: 50 }),
|
|
246
|
+
],
|
|
247
|
+
target: 'ready',
|
|
248
|
+
}, {
|
|
249
|
+
target: 'initializing',
|
|
250
|
+
}],
|
|
251
|
+
},
|
|
252
|
+
'ready': {
|
|
126
253
|
on: {
|
|
127
254
|
'xyflow.edgeMouseEnter': {
|
|
128
255
|
actions: [
|
|
129
256
|
assign({
|
|
130
257
|
xyedges: ({ context, event }) => {
|
|
131
|
-
const hasDimmed = context.xyedges.some(edge =>
|
|
258
|
+
const hasDimmed = context.xyedges.some(edge =>
|
|
259
|
+
edge.data.dimmed === true || edge.data.dimmed === 'immediate'
|
|
260
|
+
)
|
|
132
261
|
return context.xyedges.map(edge => {
|
|
133
262
|
if (edge.id === event.edge.id) {
|
|
134
263
|
return Base.setData(edge, {
|
|
@@ -136,7 +265,7 @@ export const relationshipDetailsLogic = setup({
|
|
|
136
265
|
dimmed: false,
|
|
137
266
|
})
|
|
138
267
|
}
|
|
139
|
-
return hasDimmed && !edge.
|
|
268
|
+
return hasDimmed && !edge.selected ? Base.setDimmed(edge, 'immediate') : edge
|
|
140
269
|
})
|
|
141
270
|
},
|
|
142
271
|
}),
|
|
@@ -188,21 +317,53 @@ export const relationshipDetailsLogic = setup({
|
|
|
188
317
|
}
|
|
189
318
|
}),
|
|
190
319
|
},
|
|
191
|
-
'
|
|
320
|
+
'update.layoutData': {
|
|
192
321
|
actions: [
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}),
|
|
196
|
-
raise({ type: '
|
|
322
|
+
'updateLayoutData',
|
|
323
|
+
cancel('fitDiagram'),
|
|
324
|
+
raise({ type: 'fitDiagram', duration: 0 }, { id: 'fitDiagram', delay: 50 }),
|
|
325
|
+
raise({ type: 'xyflow.updateNodeInternals' }, { delay: 75 }),
|
|
197
326
|
],
|
|
198
327
|
},
|
|
328
|
+
'xyflow.init': {
|
|
329
|
+
actions: 'updateXYFlow',
|
|
330
|
+
},
|
|
331
|
+
'xyflow.applyNodeChanges': {
|
|
332
|
+
actions: assign({
|
|
333
|
+
xynodes: ({ context, event }) => {
|
|
334
|
+
return applyNodeChanges(event.changes, context.xynodes)
|
|
335
|
+
},
|
|
336
|
+
}),
|
|
337
|
+
},
|
|
338
|
+
'xyflow.applyEdgeChanges': {
|
|
339
|
+
actions: assign({
|
|
340
|
+
xyedges: ({ context, event }) => {
|
|
341
|
+
return applyEdgeChanges(event.changes, context.xyedges)
|
|
342
|
+
},
|
|
343
|
+
}),
|
|
344
|
+
},
|
|
345
|
+
'xyflow.paneDblClick': {
|
|
346
|
+
actions: 'xyflow:fitDiagram',
|
|
347
|
+
},
|
|
348
|
+
'navigate.to': {
|
|
349
|
+
actions: assign({
|
|
350
|
+
subject: ({ event }) => inputToSubject(event.params),
|
|
351
|
+
viewId: ({ context, event }) => event.params.viewId ?? context.viewId,
|
|
352
|
+
}),
|
|
353
|
+
},
|
|
199
354
|
'close': {
|
|
200
355
|
target: 'closed',
|
|
201
356
|
},
|
|
202
357
|
},
|
|
203
358
|
exit: assign({
|
|
204
|
-
|
|
359
|
+
xyedges: [],
|
|
360
|
+
xynodes: [],
|
|
361
|
+
initialized: {
|
|
362
|
+
xydata: false,
|
|
363
|
+
xyflow: false,
|
|
364
|
+
},
|
|
205
365
|
xyflow: null,
|
|
366
|
+
xystore: null,
|
|
206
367
|
}),
|
|
207
368
|
},
|
|
208
369
|
'closed': {
|
|
@@ -222,25 +383,8 @@ export const relationshipDetailsLogic = setup({
|
|
|
222
383
|
raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 200 }),
|
|
223
384
|
],
|
|
224
385
|
},
|
|
225
|
-
'
|
|
226
|
-
actions:
|
|
227
|
-
xynodes: ({ event }) => event.xynodes,
|
|
228
|
-
xyedges: ({ event }) => event.xyedges,
|
|
229
|
-
}),
|
|
230
|
-
},
|
|
231
|
-
'xyflow.applyNodeChanges': {
|
|
232
|
-
actions: assign({
|
|
233
|
-
xynodes: ({ context, event }) => {
|
|
234
|
-
return applyNodeChanges(event.changes, context.xynodes)
|
|
235
|
-
},
|
|
236
|
-
}),
|
|
237
|
-
},
|
|
238
|
-
'xyflow.applyEdgeChanges': {
|
|
239
|
-
actions: assign({
|
|
240
|
-
xyedges: ({ context, event }) => {
|
|
241
|
-
return applyEdgeChanges(event.changes, context.xyedges)
|
|
242
|
-
},
|
|
243
|
-
}),
|
|
386
|
+
'xyflow.updateNodeInternals': {
|
|
387
|
+
actions: 'xyflow:updateNodeInternals',
|
|
244
388
|
},
|
|
245
389
|
},
|
|
246
390
|
})
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type EdgeId,
|
|
3
|
+
type Fqn,
|
|
4
|
+
type NonEmptyReadonlyArray,
|
|
5
|
+
type RelationId,
|
|
6
|
+
hasAtLeast,
|
|
7
|
+
invariant,
|
|
8
|
+
isAncestor,
|
|
9
|
+
sortParentsFirst,
|
|
10
|
+
} from '@likec4/core'
|
|
11
|
+
import { treeFromElements } from '@likec4/core/compute-view/relationships'
|
|
12
|
+
import {
|
|
13
|
+
type AnyAux,
|
|
14
|
+
type DeploymentRelationModel,
|
|
15
|
+
type ElementModel,
|
|
16
|
+
type LikeC4Model,
|
|
17
|
+
type LikeC4ViewModel,
|
|
18
|
+
type RelationshipModel,
|
|
19
|
+
modelConnection,
|
|
20
|
+
} from '@likec4/core/model'
|
|
21
|
+
|
|
22
|
+
export interface RelationshipDetailsViewData {
|
|
23
|
+
sources: ReadonlySet<ElementModel>
|
|
24
|
+
relationships: ReadonlySet<RelationshipModel>
|
|
25
|
+
targets: ReadonlySet<ElementModel>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const finalize = <M extends AnyAux>(
|
|
29
|
+
elements: Set<ElementModel<M>>,
|
|
30
|
+
explicits: Set<ElementModel<M>>,
|
|
31
|
+
): Set<ElementModel<M>> => {
|
|
32
|
+
if (elements.size > 2 && explicits.size !== elements.size) {
|
|
33
|
+
return new Set(sortParentsFirst([
|
|
34
|
+
...treeFromElements(elements).flatten(),
|
|
35
|
+
...explicits,
|
|
36
|
+
]))
|
|
37
|
+
}
|
|
38
|
+
if (elements.size > 1) {
|
|
39
|
+
return new Set(sortParentsFirst([...elements]))
|
|
40
|
+
}
|
|
41
|
+
return elements
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function computeEdgeDetailsViewData(
|
|
45
|
+
edges: NonEmptyReadonlyArray<EdgeId>,
|
|
46
|
+
view: LikeC4ViewModel<AnyAux>,
|
|
47
|
+
): RelationshipDetailsViewData {
|
|
48
|
+
const sources = new Set<ElementModel>()
|
|
49
|
+
const relationships = new Set<RelationshipModel>()
|
|
50
|
+
const targets = new Set<ElementModel>()
|
|
51
|
+
|
|
52
|
+
const explicit = {
|
|
53
|
+
sources: new Set<ElementModel>(),
|
|
54
|
+
targets: new Set<ElementModel>(),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const addExplicit = (el: ElementModel, type: 'source' | 'target') => {
|
|
58
|
+
if (type === 'source') {
|
|
59
|
+
sources.add(el)
|
|
60
|
+
explicit.sources.add(el)
|
|
61
|
+
} else {
|
|
62
|
+
targets.add(el)
|
|
63
|
+
explicit.targets.add(el)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const edgeId of edges) {
|
|
68
|
+
const edge = view.findEdge(edgeId)
|
|
69
|
+
const _relationships = edge ? [...edge.relationships('model')] : []
|
|
70
|
+
if (!edge || !hasAtLeast(_relationships, 1) || !edge.source.hasElement() || !edge.target.hasElement()) {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
const source = edge.source.element
|
|
74
|
+
const target = edge.target.element
|
|
75
|
+
addExplicit(source, 'source')
|
|
76
|
+
addExplicit(target, 'target')
|
|
77
|
+
|
|
78
|
+
for (const relationship of _relationships) {
|
|
79
|
+
relationships.add(relationship)
|
|
80
|
+
|
|
81
|
+
if (relationship.source !== source) {
|
|
82
|
+
addExplicit(relationship.source, 'source')
|
|
83
|
+
for (const parent of relationship.source.ancestors()) {
|
|
84
|
+
if (parent === source) {
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
sources.add(parent)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (relationship.target !== target) {
|
|
92
|
+
addExplicit(relationship.target, 'target')
|
|
93
|
+
for (const parent of relationship.target.ancestors()) {
|
|
94
|
+
if (parent === target) {
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
targets.add(parent)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
sources: finalize(sources, explicit.sources),
|
|
105
|
+
targets: finalize(targets, explicit.targets),
|
|
106
|
+
relationships,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function computeRelationshipDetailsViewData({
|
|
111
|
+
// relationships: _relationships,
|
|
112
|
+
source,
|
|
113
|
+
target,
|
|
114
|
+
}: {
|
|
115
|
+
source: ElementModel
|
|
116
|
+
target: ElementModel
|
|
117
|
+
// relationships: NonEmptyReadonlyArray<RelationId>
|
|
118
|
+
}): RelationshipDetailsViewData {
|
|
119
|
+
const sources = new Set<ElementModel>()
|
|
120
|
+
const relationships = new Set<RelationshipModel>()
|
|
121
|
+
const targets = new Set<ElementModel>()
|
|
122
|
+
|
|
123
|
+
const explicit = {
|
|
124
|
+
sources: new Set<ElementModel>(),
|
|
125
|
+
targets: new Set<ElementModel>(),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const addExplicit = (el: ElementModel, type: 'source' | 'target') => {
|
|
129
|
+
if (type === 'source') {
|
|
130
|
+
sources.add(el)
|
|
131
|
+
explicit.sources.add(el)
|
|
132
|
+
} else {
|
|
133
|
+
targets.add(el)
|
|
134
|
+
explicit.targets.add(el)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (source) {
|
|
139
|
+
addExplicit(source, 'source')
|
|
140
|
+
}
|
|
141
|
+
if (target) {
|
|
142
|
+
addExplicit(target, 'target')
|
|
143
|
+
}
|
|
144
|
+
const [connection] = modelConnection.findConnection(source, target, 'directed')
|
|
145
|
+
if (!connection) {
|
|
146
|
+
return {
|
|
147
|
+
sources,
|
|
148
|
+
targets,
|
|
149
|
+
relationships,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const relationship of connection.relations) {
|
|
154
|
+
const relationSource = relationship.source
|
|
155
|
+
const relationTarget = relationship.target
|
|
156
|
+
addExplicit(relationSource, 'source')
|
|
157
|
+
addExplicit(relationTarget, 'target')
|
|
158
|
+
relationships.add(relationship)
|
|
159
|
+
|
|
160
|
+
if (source !== relationSource) {
|
|
161
|
+
invariant(isAncestor(source, relationSource), `${source.id} is not an ancestor of ${relationSource.id}`)
|
|
162
|
+
for (const parent of relationSource.ancestors()) {
|
|
163
|
+
if (parent === source) {
|
|
164
|
+
break
|
|
165
|
+
}
|
|
166
|
+
sources.add(parent)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (target !== relationTarget) {
|
|
171
|
+
invariant(isAncestor(target, relationTarget), `${target.id} is not an ancestor of ${relationTarget.id}`)
|
|
172
|
+
for (const parent of relationTarget.ancestors()) {
|
|
173
|
+
if (parent === target) {
|
|
174
|
+
break
|
|
175
|
+
}
|
|
176
|
+
targets.add(parent)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
sources: finalize(sources, explicit.sources),
|
|
183
|
+
targets: finalize(targets, explicit.targets),
|
|
184
|
+
relationships,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import { IconZoomScan } from '@tabler/icons-react'
|
|
1
|
+
import { IconFileSymlink, IconTransform, IconZoomScan } from '@tabler/icons-react'
|
|
2
2
|
import { ElementActionButtons } from '../../../base/primitives'
|
|
3
3
|
import type { NodeProps } from '../../../base/types'
|
|
4
|
-
import {
|
|
4
|
+
import { useEnabledFeatures } from '../../../context/DiagramFeatures'
|
|
5
|
+
import { useCurrentViewId } from '../../../hooks/useCurrentViewId'
|
|
5
6
|
import { useDiagram } from '../../../hooks/useDiagram'
|
|
6
7
|
import type { RelationshipDetailsTypes as Types } from '../_types'
|
|
7
|
-
import { useRelationshipDetails } from '../hooks'
|
|
8
|
-
// import { useRelationshipsBrowser } from '../hooks'
|
|
9
8
|
|
|
10
9
|
type ElementActionsProps = NodeProps<Types.ElementNodeData>
|
|
11
10
|
export const ElementActions = (props: ElementActionsProps) => {
|
|
12
|
-
const { enableNavigateTo } =
|
|
11
|
+
const { enableNavigateTo, enableVscode } = useEnabledFeatures()
|
|
13
12
|
const diagram = useDiagram()
|
|
14
|
-
|
|
13
|
+
const currentViewId = useCurrentViewId()
|
|
15
14
|
const buttons = [] as ElementActionButtons.Item[]
|
|
16
15
|
|
|
17
|
-
const { navigateTo } = props.data
|
|
18
|
-
if (navigateTo && enableNavigateTo) {
|
|
16
|
+
const { navigateTo, fqn } = props.data
|
|
17
|
+
if (navigateTo && enableNavigateTo && currentViewId !== navigateTo) {
|
|
19
18
|
buttons.push({
|
|
20
19
|
key: 'navigate',
|
|
21
20
|
icon: <IconZoomScan />,
|
|
@@ -25,16 +24,26 @@ export const ElementActions = (props: ElementActionsProps) => {
|
|
|
25
24
|
},
|
|
26
25
|
})
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
if (fqn) {
|
|
28
|
+
buttons.push({
|
|
29
|
+
key: 'relationships',
|
|
30
|
+
icon: <IconTransform />,
|
|
31
|
+
onClick: (e) => {
|
|
32
|
+
e.stopPropagation()
|
|
33
|
+
diagram.openRelationshipsBrowser(fqn)
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
if (fqn && enableVscode) {
|
|
38
|
+
buttons.push({
|
|
39
|
+
key: 'goToSource',
|
|
40
|
+
icon: <IconFileSymlink />,
|
|
41
|
+
onClick: (e) => {
|
|
42
|
+
e.stopPropagation()
|
|
43
|
+
diagram.openSource({ element: fqn })
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
}
|
|
38
47
|
return (
|
|
39
48
|
<ElementActionButtons
|
|
40
49
|
buttons={buttons}
|
|
@@ -31,7 +31,7 @@ export const RelationshipEdge = customEdge<RelationshipDetailsTypes.EdgeData>((p
|
|
|
31
31
|
translate: 'translate(-50%, 0)',
|
|
32
32
|
}}
|
|
33
33
|
style={{
|
|
34
|
-
maxWidth: Math.abs(props.targetX - props.sourceX -
|
|
34
|
+
maxWidth: Math.abs(props.targetX - props.sourceX - 100),
|
|
35
35
|
}}>
|
|
36
36
|
<EdgeLabel edgeProps={props}>
|
|
37
37
|
{enableNavigateTo && navigateTo && (
|