@likec4/diagram 1.9.0 → 1.12.1
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/package.json +20 -43
- package/src/LikeC4CustomColors.tsx +5 -2
- package/src/LikeC4Diagram.css.ts +1 -1
- package/src/LikeC4Diagram.props.ts +40 -94
- package/src/LikeC4Diagram.tsx +14 -4
- package/src/LikeC4ModelProvider.tsx +21 -0
- package/src/StaticLikeC4Diagram.tsx +1 -0
- package/src/globals.css.ts +2 -3
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useMantinePortalProps.ts +8 -0
- package/src/hooks/useXYFlow.ts +1 -2
- package/src/index.ts +9 -0
- package/src/likec4model/LikeC4ModelContext.ts +4 -0
- package/src/likec4model/index.ts +1 -0
- package/src/likec4model/useLikeC4Model.ts +58 -0
- package/src/state/diagram-to-xyflow.ts +1 -2
- package/src/state/diagramStore.ts +185 -66
- package/src/{theme.css.ts → theme-vars.ts} +11 -4
- package/src/ui/DiagramTitlePanel.css.ts +2 -3
- package/src/ui/DiagramTitlePanel.tsx +25 -3
- package/src/ui/DynamicViewWalkthrough.css.ts +28 -2
- package/src/ui/DynamicViewWalkthrough.tsx +97 -52
- package/src/ui/OptionsPanel.css.ts +1 -2
- package/src/ui/OptionsPanel.tsx +5 -2
- package/src/ui/notation/NotationPanel.css.ts +2 -3
- package/src/ui/notation/NotationPanel.tsx +10 -11
- package/src/ui/options/NodeOptions.tsx +4 -1
- package/src/ui/top-left/ChangeAutoLayoutButton.tsx +122 -71
- package/src/ui/top-left/TopLeftPanel.tsx +128 -117
- package/src/ui/top-left/_shared.ts +7 -6
- package/src/ui/top-left/styles.css.ts +48 -8
- package/src/utils/vector.ts +57 -0
- package/src/xyflow/FitviewOnDiagramChange.tsx +5 -4
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +7 -5
- package/src/xyflow/XYFlow.tsx +13 -17
- package/src/xyflow/XYFlowEvents.tsx +58 -58
- package/src/xyflow/XYFlowInner.tsx +4 -8
- package/src/xyflow/edges/EdgeLabel.tsx +185 -0
- package/src/xyflow/edges/RelationshipEdge.tsx +170 -137
- package/src/xyflow/edges/RelationshipsDropdownMenu.css.ts +60 -0
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +214 -0
- package/src/xyflow/edges/edges.css.ts +75 -7
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +1 -2
- package/src/xyflow/nodes/compound/CompoundNode.tsx +25 -8
- package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
- package/src/xyflow/nodes/element/ElementLink.tsx +4 -8
- package/src/xyflow/nodes/element/ElementNode.tsx +47 -8
- package/src/xyflow/nodes/element/element.css.ts +1 -2
- package/src/xyflow/nodes/shared/Toolbar.tsx +426 -0
- package/src/xyflow/types.ts +1 -24
- package/src/xyflow/useLayoutConstraints.ts +10 -6
- package/dist/LikeC4CustomColors.d.ts +0 -3
- package/dist/LikeC4CustomColors.props.d.ts +0 -5
- package/dist/LikeC4Diagram.css.d.ts +0 -4
- package/dist/LikeC4Diagram.d.ts +0 -7
- package/dist/LikeC4Diagram.props.d.ts +0 -154
- package/dist/StaticLikeC4Diagram.d.ts +0 -4
- package/dist/globals.css.d.ts +0 -1
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/useDiagramState.d.ts +0 -6
- package/dist/hooks/useSetState.d.ts +0 -3
- package/dist/hooks/useUpdateEffect.d.ts +0 -5
- package/dist/hooks/useXYFlow.d.ts +0 -267
- package/dist/index.d.ts +0 -9
- package/dist/index.mjs +0 -3684
- package/dist/mantine/EnsureMantine.d.ts +0 -8
- package/dist/mantine.css.d.ts +0 -3
- package/dist/state/DiagramContext.d.ts +0 -1192
- package/dist/state/diagram-to-xyflow.d.ts +0 -12
- package/dist/state/diagramStore.d.ts +0 -1268
- package/dist/style.css +0 -2139
- package/dist/theme.css.d.ts +0 -63
- package/dist/ui/DiagramTitlePanel.css.d.ts +0 -4
- package/dist/ui/DiagramTitlePanel.d.ts +0 -1
- package/dist/ui/DynamicViewWalkthrough.css.d.ts +0 -3
- package/dist/ui/DynamicViewWalkthrough.d.ts +0 -1
- package/dist/ui/OptionsPanel.css.d.ts +0 -1
- package/dist/ui/OptionsPanel.d.ts +0 -1
- package/dist/ui/notation/NotationPanel.css.d.ts +0 -6
- package/dist/ui/notation/NotationPanel.d.ts +0 -1
- package/dist/ui/notation/index.d.ts +0 -1
- package/dist/ui/options/EdgeOptions.d.ts +0 -19
- package/dist/ui/options/NodeOptions.d.ts +0 -19
- package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +0 -3
- package/dist/ui/top-left/TopLeftPanel.d.ts +0 -4
- package/dist/ui/top-left/_shared.d.ts +0 -5
- package/dist/ui/top-left/styles.css.d.ts +0 -3
- package/dist/xyflow/EdgeMarkers.d.ts +0 -12
- package/dist/xyflow/FitviewOnDiagramChange.d.ts +0 -4
- package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +0 -1
- package/dist/xyflow/XYFlow.d.ts +0 -12
- package/dist/xyflow/XYFlowBackground.d.ts +0 -11
- package/dist/xyflow/XYFlowEvents.d.ts +0 -161
- package/dist/xyflow/XYFlowInner.d.ts +0 -10
- package/dist/xyflow/const.d.ts +0 -7
- package/dist/xyflow/edges/RelationshipEdge.d.ts +0 -32
- package/dist/xyflow/edges/edges.css.d.ts +0 -12
- package/dist/xyflow/edges/utils.d.ts +0 -17
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -11
- package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -6
- package/dist/xyflow/nodes/compound/index.d.ts +0 -1
- package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -3
- package/dist/xyflow/nodes/element/ElementLink.d.ts +0 -7
- package/dist/xyflow/nodes/element/ElementNode.d.ts +0 -6
- package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +0 -18
- package/dist/xyflow/nodes/element/element.css.d.ts +0 -19
- package/dist/xyflow/nodes/element/index.d.ts +0 -1
- package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -5
- package/dist/xyflow/types.d.ts +0 -50
- package/dist/xyflow/useLayoutConstraints.d.ts +0 -9
- package/dist/xyflow/utils.d.ts +0 -18
- package/src/mantine.css.ts +0 -7
- package/src/reset.d.ts +0 -2
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { extractStep } from '@likec4/core'
|
|
2
1
|
import type { ReactFlowProps } from '@xyflow/react'
|
|
3
2
|
import { useMemo, useRef } from 'react'
|
|
4
3
|
import { isNonNullish, isTruthy } from 'remeda'
|
|
@@ -46,12 +45,17 @@ export function useXYFlowEvents() {
|
|
|
46
45
|
}
|
|
47
46
|
dblclickTimeout.current = window.setTimeout(() => {
|
|
48
47
|
dblclickTimeout.current = undefined
|
|
49
|
-
},
|
|
48
|
+
}, 300)
|
|
50
49
|
return false
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
const lastClickWasRecent = (ms =
|
|
54
|
-
|
|
52
|
+
const lastClickWasRecent = (ms = 2000) => {
|
|
53
|
+
const prevTimestamp = lastClickTimestamp.current
|
|
54
|
+
lastClickTimestamp.current = Date.now()
|
|
55
|
+
if (prevTimestamp === undefined) {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
return prevTimestamp + ms > Date.now()
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
return ({
|
|
@@ -59,13 +63,11 @@ export function useXYFlowEvents() {
|
|
|
59
63
|
const {
|
|
60
64
|
onCanvasDblClick,
|
|
61
65
|
zoomable,
|
|
62
|
-
xystore,
|
|
63
66
|
viewportChanged,
|
|
64
67
|
fitDiagram,
|
|
65
|
-
|
|
68
|
+
resetFocusAndLastClicked
|
|
66
69
|
} = diagramApi.getState()
|
|
67
|
-
|
|
68
|
-
xystore.getState().resetSelectedElements()
|
|
70
|
+
resetFocusAndLastClicked()
|
|
69
71
|
if (!onCanvasDblClick) {
|
|
70
72
|
event.stopPropagation()
|
|
71
73
|
}
|
|
@@ -75,46 +77,43 @@ export function useXYFlowEvents() {
|
|
|
75
77
|
onCanvasDblClick?.(event)
|
|
76
78
|
},
|
|
77
79
|
onPaneClick: (event) => {
|
|
80
|
+
console.log('onPaneClick')
|
|
78
81
|
if (dbclickLock()) {
|
|
79
82
|
return
|
|
80
83
|
}
|
|
81
84
|
const {
|
|
82
85
|
focusedNodeId,
|
|
83
|
-
|
|
86
|
+
activeWalkthrough,
|
|
84
87
|
fitDiagram,
|
|
85
88
|
onCanvasClick,
|
|
86
|
-
|
|
87
|
-
resetLastClicked
|
|
89
|
+
resetFocusAndLastClicked
|
|
88
90
|
} = diagramApi.getState()
|
|
89
|
-
if ((focusedNodeId ??
|
|
91
|
+
if ((focusedNodeId ?? activeWalkthrough) !== null) {
|
|
90
92
|
fitDiagram()
|
|
91
93
|
if (!onCanvasClick) {
|
|
92
94
|
event.stopPropagation()
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
|
-
|
|
96
|
-
xystore.getState().resetSelectedElements()
|
|
97
|
+
resetFocusAndLastClicked()
|
|
97
98
|
onCanvasClick?.(event)
|
|
98
99
|
},
|
|
99
100
|
onNodeContextMenu: (event, xynode) => {
|
|
100
101
|
diagramApi.getState().setLastClickedNode(xynode.id)
|
|
101
|
-
diagramApi.getState().onNodeContextMenu?.(
|
|
102
|
-
|
|
103
|
-
xynode,
|
|
102
|
+
diagramApi.getState().onNodeContextMenu?.(
|
|
103
|
+
xynode.data.element,
|
|
104
104
|
event
|
|
105
|
-
|
|
105
|
+
)
|
|
106
106
|
},
|
|
107
107
|
onPaneContextMenu: (event) => {
|
|
108
|
-
diagramApi.getState().
|
|
108
|
+
diagramApi.getState().resetFocusAndLastClicked()
|
|
109
109
|
diagramApi.getState().onCanvasContextMenu?.(event as any)
|
|
110
110
|
},
|
|
111
111
|
onEdgeContextMenu: (event, xyedge) => {
|
|
112
112
|
diagramApi.getState().setLastClickedEdge(xyedge.id)
|
|
113
|
-
diagramApi.getState().onEdgeContextMenu?.(
|
|
114
|
-
|
|
115
|
-
xyedge,
|
|
113
|
+
diagramApi.getState().onEdgeContextMenu?.(
|
|
114
|
+
xyedge.data.edge,
|
|
116
115
|
event
|
|
117
|
-
|
|
116
|
+
)
|
|
118
117
|
},
|
|
119
118
|
onNodeClick: (event, xynode) => {
|
|
120
119
|
const {
|
|
@@ -137,39 +136,36 @@ export function useXYFlowEvents() {
|
|
|
137
136
|
|
|
138
137
|
if (clickedRecently && !!onOpenSourceElement) {
|
|
139
138
|
onOpenSourceElement(xynode.data.element.id)
|
|
139
|
+
if (!onNodeClick) {
|
|
140
|
+
event.stopPropagation()
|
|
141
|
+
}
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
if (enableFocusMode || nodesSelectable) {
|
|
143
|
-
let stopPropagation = true
|
|
144
145
|
switch (true) {
|
|
145
|
-
case enableFocusMode &&
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
case enableFocusMode && clickedRecently && focusedNodeId === xynode.id: {
|
|
147
|
+
focusOnNode(false)
|
|
148
|
+
fitDiagram()
|
|
148
149
|
break
|
|
149
150
|
}
|
|
150
|
-
case enableFocusMode &&
|
|
151
|
-
|
|
151
|
+
case enableFocusMode && shallChangeFocus:
|
|
152
|
+
case enableFocusMode && clickedRecently && focusedNodeId !== xynode.id: {
|
|
153
|
+
focusOnNode(xynode.id)
|
|
152
154
|
break
|
|
153
155
|
}
|
|
154
156
|
case nodesSelectable: {
|
|
155
157
|
xystore.getState().addSelectedNodes([xynode.id])
|
|
156
158
|
break
|
|
157
159
|
}
|
|
158
|
-
default: {
|
|
159
|
-
stopPropagation = false
|
|
160
|
-
}
|
|
161
160
|
}
|
|
162
|
-
if (!onNodeClick
|
|
163
|
-
// user did not provide a custom handler, stop propagation
|
|
161
|
+
if (!onNodeClick) {
|
|
164
162
|
event.stopPropagation()
|
|
165
163
|
}
|
|
166
164
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
element: xynode.data.element,
|
|
170
|
-
xynode,
|
|
165
|
+
onNodeClick?.(
|
|
166
|
+
xynode.data.element,
|
|
171
167
|
event
|
|
172
|
-
|
|
168
|
+
)
|
|
173
169
|
},
|
|
174
170
|
onNodeDoubleClick: (event, xynode) => {
|
|
175
171
|
const {
|
|
@@ -196,8 +192,8 @@ export function useXYFlowEvents() {
|
|
|
196
192
|
lastClickedEdgeId,
|
|
197
193
|
isDynamicView,
|
|
198
194
|
enableDynamicViewWalkthrough,
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
activateWalkthrough,
|
|
196
|
+
activeWalkthrough,
|
|
201
197
|
focusedNodeId,
|
|
202
198
|
xystore,
|
|
203
199
|
nodesSelectable,
|
|
@@ -209,22 +205,27 @@ export function useXYFlowEvents() {
|
|
|
209
205
|
setLastClickedEdge(xyedge.id)
|
|
210
206
|
}
|
|
211
207
|
const isNotAFirstClick = lastClickedEdgeId === xyedge.id
|
|
212
|
-
const
|
|
208
|
+
const connectedToFocusedNode = isTruthy(focusedNodeId)
|
|
213
209
|
&& (focusedNodeId === xyedge.source || focusedNodeId === xyedge.target)
|
|
214
210
|
if (
|
|
215
211
|
isDynamicView && enableDynamicViewWalkthrough
|
|
216
|
-
&& (
|
|
212
|
+
&& (connectedToFocusedNode || isNotAFirstClick || isNonNullish(activeWalkthrough))
|
|
217
213
|
) {
|
|
218
|
-
const nextStep =
|
|
219
|
-
if (
|
|
220
|
-
|
|
214
|
+
const nextStep = xyedge.data.edge.id
|
|
215
|
+
if (activeWalkthrough?.stepId !== nextStep) {
|
|
216
|
+
activateWalkthrough(nextStep)
|
|
221
217
|
event.stopPropagation()
|
|
222
218
|
return
|
|
223
219
|
}
|
|
224
220
|
}
|
|
225
|
-
// if we
|
|
226
|
-
if (
|
|
227
|
-
|
|
221
|
+
// if we are in focus mode
|
|
222
|
+
if (isTruthy(focusedNodeId)) {
|
|
223
|
+
// if we focused on a node, and clicked on an edge connected to it - focus on the other node
|
|
224
|
+
if (connectedToFocusedNode) {
|
|
225
|
+
focusOnNode(focusedNodeId === xyedge.source ? xyedge.target : xyedge.source)
|
|
226
|
+
} else {
|
|
227
|
+
focusOnNode(xyedge.source)
|
|
228
|
+
}
|
|
228
229
|
if (!onEdgeClick) {
|
|
229
230
|
event.stopPropagation()
|
|
230
231
|
}
|
|
@@ -234,11 +235,10 @@ export function useXYFlowEvents() {
|
|
|
234
235
|
event.stopPropagation()
|
|
235
236
|
}
|
|
236
237
|
}
|
|
237
|
-
onEdgeClick?.(
|
|
238
|
-
|
|
239
|
-
xyedge,
|
|
238
|
+
onEdgeClick?.(
|
|
239
|
+
xyedge.data.edge,
|
|
240
240
|
event
|
|
241
|
-
|
|
241
|
+
)
|
|
242
242
|
},
|
|
243
243
|
onEdgeDoubleClick: (event, xyedge) => {
|
|
244
244
|
diagramApi.getState().setLastClickedEdge(xyedge.id)
|
|
@@ -247,14 +247,14 @@ export function useXYFlowEvents() {
|
|
|
247
247
|
isDynamicView,
|
|
248
248
|
enableDynamicViewWalkthrough,
|
|
249
249
|
focusOnNode,
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
activeWalkthrough,
|
|
251
|
+
activateWalkthrough
|
|
252
252
|
} = diagramApi.getState()
|
|
253
253
|
// if we are in dynamic view, and clicked on an edge, activate the step
|
|
254
254
|
if (isDynamicView && enableDynamicViewWalkthrough) {
|
|
255
|
-
const nextStep =
|
|
256
|
-
if (
|
|
257
|
-
|
|
255
|
+
const nextStep = xyedge.data.edge.id
|
|
256
|
+
if (activeWalkthrough?.stepId !== nextStep) {
|
|
257
|
+
activateWalkthrough(nextStep)
|
|
258
258
|
event.stopPropagation()
|
|
259
259
|
}
|
|
260
260
|
return
|
|
@@ -7,7 +7,6 @@ import type { LikeC4DiagramProperties } from '../LikeC4Diagram.props'
|
|
|
7
7
|
import DiagramTitlePanel from '../ui/DiagramTitlePanel'
|
|
8
8
|
import { DynamicViewWalkthrough } from '../ui/DynamicViewWalkthrough'
|
|
9
9
|
import NotationPanel from '../ui/notation'
|
|
10
|
-
import OptionsPanel from '../ui/OptionsPanel'
|
|
11
10
|
import { TopLeftPanel } from '../ui/top-left/TopLeftPanel'
|
|
12
11
|
import { XYFlowBackground } from './XYFlowBackground'
|
|
13
12
|
|
|
@@ -17,7 +16,7 @@ type XYFlowInnerProps = PropsWithChildren<{
|
|
|
17
16
|
showDiagramTitle: boolean
|
|
18
17
|
}>
|
|
19
18
|
|
|
20
|
-
export const XYFlowInner = memo(function XYFlowInnerR({
|
|
19
|
+
export const XYFlowInner = /* @__PURE__ */ memo(function XYFlowInnerR({
|
|
21
20
|
children,
|
|
22
21
|
background,
|
|
23
22
|
controls,
|
|
@@ -27,25 +26,22 @@ export const XYFlowInner = memo(function XYFlowInnerR({
|
|
|
27
26
|
showNotations,
|
|
28
27
|
isDynamicView,
|
|
29
28
|
isDynamicViewActive,
|
|
30
|
-
readonly,
|
|
31
29
|
enableDynamicViewWalkthrough
|
|
32
30
|
} = useDiagramState(s => ({
|
|
33
31
|
showNotations: s.showNotations,
|
|
34
32
|
isDynamicView: s.isDynamicView,
|
|
35
|
-
readonly: s.readonly,
|
|
36
33
|
enableDynamicViewWalkthrough: s.enableDynamicViewWalkthrough,
|
|
37
|
-
isDynamicViewActive: isNonNullish(s.
|
|
34
|
+
isDynamicViewActive: isNonNullish(s.activeWalkthrough)
|
|
38
35
|
}))
|
|
39
36
|
|
|
40
37
|
const isBgWithPattern = background !== 'transparent' && background !== 'solid'
|
|
41
38
|
|
|
42
|
-
const isDiagramTitleVisible = showDiagramTitle
|
|
39
|
+
const isDiagramTitleVisible = showDiagramTitle && !isDynamicViewActive
|
|
43
40
|
return (
|
|
44
41
|
<>
|
|
45
42
|
{isBgWithPattern && <XYFlowBackground background={background} />}
|
|
46
|
-
{controls && <Controls position={'
|
|
43
|
+
{controls && <Controls showInteractive={false} position={'top-center'} />}
|
|
47
44
|
<TopLeftPanel />
|
|
48
|
-
{readonly !== true && <OptionsPanel />}
|
|
49
45
|
{isDiagramTitleVisible && <DiagramTitlePanel />}
|
|
50
46
|
{isDynamicView && enableDynamicViewWalkthrough && <DynamicViewWalkthrough />}
|
|
51
47
|
{showNotations && <NotationPanel />}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { extractStep, isStepEdgeId, type ViewID } from '@likec4/core'
|
|
2
|
+
import {
|
|
3
|
+
ActionIcon,
|
|
4
|
+
Box,
|
|
5
|
+
type BoxProps,
|
|
6
|
+
Button,
|
|
7
|
+
CloseButton,
|
|
8
|
+
Group,
|
|
9
|
+
Popover,
|
|
10
|
+
PopoverDropdown,
|
|
11
|
+
PopoverTarget,
|
|
12
|
+
ScrollAreaAutosize,
|
|
13
|
+
Stack,
|
|
14
|
+
Text
|
|
15
|
+
} from '@mantine/core'
|
|
16
|
+
import { useDebouncedEffect } from '@react-hookz/web'
|
|
17
|
+
import { IconZoomScan } from '@tabler/icons-react'
|
|
18
|
+
import { EdgeLabelRenderer } from '@xyflow/react'
|
|
19
|
+
import clsx from 'clsx'
|
|
20
|
+
import { type PropsWithChildren, type ReactNode, useState } from 'react'
|
|
21
|
+
import { isTruthy } from 'remeda'
|
|
22
|
+
import { useDiagramState, useDiagramStoreApi, useMantinePortalProps } from '../../hooks'
|
|
23
|
+
import { useLikeC4Model } from '../../likec4model/useLikeC4Model'
|
|
24
|
+
import type { RelationshipData } from '../types'
|
|
25
|
+
import * as edgesCss from './edges.css'
|
|
26
|
+
import { RelationshipsDropdownMenu } from './RelationshipsDropdownMenu'
|
|
27
|
+
|
|
28
|
+
export interface EdgeLabelProps extends Omit<BoxProps, 'label'> {
|
|
29
|
+
isDimmed: boolean
|
|
30
|
+
edgeData: RelationshipData
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const EdgeLabel = ({
|
|
34
|
+
isDimmed,
|
|
35
|
+
edgeData: {
|
|
36
|
+
label,
|
|
37
|
+
edge
|
|
38
|
+
},
|
|
39
|
+
...props
|
|
40
|
+
}: EdgeLabelProps) => {
|
|
41
|
+
const likec4model = useLikeC4Model()
|
|
42
|
+
const {
|
|
43
|
+
showRelationshipDetails,
|
|
44
|
+
isActiveWalkthroughStep,
|
|
45
|
+
hasOnNavigateTo
|
|
46
|
+
} = useDiagramState(s => ({
|
|
47
|
+
showRelationshipDetails: s.showRelationshipDetails,
|
|
48
|
+
isActiveWalkthroughStep: s.activeWalkthrough?.stepId === edge.id,
|
|
49
|
+
hasOnNavigateTo: !!s.onNavigateTo
|
|
50
|
+
}))
|
|
51
|
+
const notes = isActiveWalkthroughStep ? (edge.notes ?? null) : null
|
|
52
|
+
const stepNum = isStepEdgeId(edge.id) ? extractStep(edge.id) : null
|
|
53
|
+
const navigateTo = hasOnNavigateTo ? edge.navigateTo ?? null : null
|
|
54
|
+
|
|
55
|
+
const wrapWithPopover = (node: ReactNode) => {
|
|
56
|
+
if (isTruthy(notes)) {
|
|
57
|
+
return <NotePopover notes={notes}>{node}</NotePopover>
|
|
58
|
+
}
|
|
59
|
+
if (showRelationshipDetails && likec4model && edge.relations.length > 0) {
|
|
60
|
+
return (
|
|
61
|
+
<RelationshipsDropdownMenu
|
|
62
|
+
disabled={isDimmed}
|
|
63
|
+
likec4model={likec4model}
|
|
64
|
+
edge={edge}>
|
|
65
|
+
{node}
|
|
66
|
+
</RelationshipsDropdownMenu>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
return node
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<EdgeLabelRenderer>
|
|
74
|
+
{wrapWithPopover(
|
|
75
|
+
<Box {...props}>
|
|
76
|
+
{stepNum !== null && (
|
|
77
|
+
<Box className={edgesCss.stepEdgeNumber}>
|
|
78
|
+
{stepNum}
|
|
79
|
+
</Box>
|
|
80
|
+
)}
|
|
81
|
+
{isTruthy(label?.text) && (
|
|
82
|
+
<Box className={edgesCss.edgeLabelText}>
|
|
83
|
+
{label.text}
|
|
84
|
+
</Box>
|
|
85
|
+
)}
|
|
86
|
+
{navigateTo && <NavigateToBtn viewId={navigateTo} />}
|
|
87
|
+
</Box>
|
|
88
|
+
)}
|
|
89
|
+
</EdgeLabelRenderer>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const NotePopover = ({ notes, children }: PropsWithChildren<{ notes: string }>) => {
|
|
94
|
+
const {
|
|
95
|
+
nextDynamicStep,
|
|
96
|
+
hasNext,
|
|
97
|
+
hasPrevious
|
|
98
|
+
} = useDiagramState(s => ({
|
|
99
|
+
nextDynamicStep: s.nextDynamicStep,
|
|
100
|
+
hasNext: s.activeWalkthrough?.hasNext ?? false,
|
|
101
|
+
hasPrevious: s.activeWalkthrough?.hasPrevious ?? false
|
|
102
|
+
}))
|
|
103
|
+
|
|
104
|
+
const [isOpened, setIsOpened] = useState(false)
|
|
105
|
+
const portalProps = useMantinePortalProps()
|
|
106
|
+
|
|
107
|
+
useDebouncedEffect(
|
|
108
|
+
() => {
|
|
109
|
+
setIsOpened(true)
|
|
110
|
+
},
|
|
111
|
+
[],
|
|
112
|
+
300
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<Popover
|
|
117
|
+
shadow="xs"
|
|
118
|
+
offset={16}
|
|
119
|
+
opened={isOpened}
|
|
120
|
+
closeOnClickOutside={false}
|
|
121
|
+
{...portalProps}>
|
|
122
|
+
<PopoverTarget>
|
|
123
|
+
{children}
|
|
124
|
+
</PopoverTarget>
|
|
125
|
+
<PopoverDropdown
|
|
126
|
+
component={Stack}
|
|
127
|
+
p={'xs'}
|
|
128
|
+
onPointerDownCapture={e => e.stopPropagation()}
|
|
129
|
+
onClick={e => e.stopPropagation()}
|
|
130
|
+
onDoubleClick={e => e.stopPropagation()}
|
|
131
|
+
>
|
|
132
|
+
<ScrollAreaAutosize maw={450} mah={350} type="scroll" mx={'auto'} mt={2}>
|
|
133
|
+
<Text component="div" className={edgesCss.edgeNoteText} p={4}>{notes}</Text>
|
|
134
|
+
</ScrollAreaAutosize>
|
|
135
|
+
<CloseButton
|
|
136
|
+
size={'xs'}
|
|
137
|
+
className={edgesCss.edgeNoteCloseButton}
|
|
138
|
+
onClick={() => setIsOpened(false)}
|
|
139
|
+
/>
|
|
140
|
+
{(hasPrevious || hasNext) && (
|
|
141
|
+
<Group gap={0} justify={hasPrevious ? 'flex-start' : 'flex-end'}>
|
|
142
|
+
{hasPrevious && (
|
|
143
|
+
<Button
|
|
144
|
+
variant="subtle"
|
|
145
|
+
radius={'xs'}
|
|
146
|
+
size="compact-xs"
|
|
147
|
+
onClick={() => nextDynamicStep(-1)}>
|
|
148
|
+
back
|
|
149
|
+
</Button>
|
|
150
|
+
)}
|
|
151
|
+
{hasNext && (
|
|
152
|
+
<Button variant="subtle" radius={'xs'} size="compact-xs" onClick={() => nextDynamicStep()}>
|
|
153
|
+
next
|
|
154
|
+
</Button>
|
|
155
|
+
)}
|
|
156
|
+
</Group>
|
|
157
|
+
)}
|
|
158
|
+
</PopoverDropdown>
|
|
159
|
+
</Popover>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type NavigateToBtnProps = {
|
|
164
|
+
viewId: ViewID
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function NavigateToBtn({ viewId }: NavigateToBtnProps) {
|
|
168
|
+
const diagramApi = useDiagramStoreApi()
|
|
169
|
+
return (
|
|
170
|
+
<ActionIcon
|
|
171
|
+
className={clsx('nodrag nopan', edgesCss.cssNavigateBtn)}
|
|
172
|
+
size={'sm'}
|
|
173
|
+
radius="sm"
|
|
174
|
+
onPointerDownCapture={e => e.stopPropagation()}
|
|
175
|
+
onClick={event => {
|
|
176
|
+
event.stopPropagation()
|
|
177
|
+
diagramApi.getState().onNavigateTo?.(viewId, event)
|
|
178
|
+
}}
|
|
179
|
+
role="button"
|
|
180
|
+
onDoubleClick={event => event.stopPropagation()}
|
|
181
|
+
>
|
|
182
|
+
<IconZoomScan className={edgesCss.cssNavigateBtnIcon} />
|
|
183
|
+
</ActionIcon>
|
|
184
|
+
)
|
|
185
|
+
}
|