@likec4/diagram 1.3.0 → 1.5.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/dist/LikeC4Diagram.css.d.ts +0 -1
- package/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.props.d.ts +7 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1580 -1194
- package/dist/state/DiagramContext.d.ts +97 -4169
- package/dist/state/diagramStore.d.ts +98 -4172
- package/dist/state/hooks.d.ts +4 -0
- package/dist/state/index.d.ts +1 -3
- package/dist/state/useDiagramStore.d.ts +3 -4622
- package/dist/state/useNavigationHistory.d.ts +5 -0
- package/dist/style.css +217 -159
- package/dist/theme.css.d.ts +5 -0
- package/dist/ui/BackwardForwardPanel.css.d.ts +1 -0
- package/dist/ui/BackwardForwardPanel.d.ts +1 -0
- package/dist/ui/DiagramTitlePanel.css.d.ts +1 -1
- package/dist/ui/OptionsPanel.d.ts +1 -3
- package/dist/ui/options/EdgeOptions.d.ts +19 -0
- package/dist/ui/options/NodeOptions.d.ts +2 -2
- package/dist/xyflow/EdgeMarkers.d.ts +11 -0
- package/dist/xyflow/XYFlowEvents.d.ts +12 -9
- package/dist/xyflow/XYFlowInner.d.ts +1 -0
- package/dist/xyflow/const.d.ts +3 -3
- package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
- package/dist/xyflow/edges/RelationshipEdge.d.ts +4 -3
- package/dist/xyflow/edges/edges.css.d.ts +1 -0
- package/dist/xyflow/edges/utils.d.ts +1 -0
- package/dist/xyflow/hooks/index.d.ts +3 -2
- package/dist/xyflow/hooks/useXYFlow.d.ts +22 -21
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
- package/dist/xyflow/nodes/element/ElementIcon.d.ts +1 -4
- package/dist/xyflow/nodes/element/element.css.d.ts +3 -1
- package/dist/xyflow/types.d.ts +2 -32
- package/package.json +21 -21
- package/src/LikeC4Diagram.css.ts +10 -8
- package/src/LikeC4Diagram.props.ts +12 -5
- package/src/LikeC4Diagram.tsx +43 -26
- package/src/globals.css.ts +11 -4
- package/src/index.ts +1 -1
- package/src/mantine.css.ts +2 -2
- package/src/state/DiagramContext.tsx +47 -16
- package/src/state/diagramStore.ts +89 -80
- package/src/state/hooks.ts +5 -0
- package/src/state/index.ts +1 -3
- package/src/state/useDiagramStore.ts +6 -13
- package/src/state/useNavigationHistory.ts +39 -0
- package/src/theme.css.ts +5 -0
- package/src/ui/BackwardForwardPanel.css.ts +22 -0
- package/src/ui/BackwardForwardPanel.tsx +59 -0
- package/src/ui/DiagramTitlePanel.css.ts +17 -6
- package/src/ui/DiagramTitlePanel.tsx +108 -94
- package/src/ui/DynamicViewWalkthrough.tsx +6 -4
- package/src/ui/OptionsPanel.tsx +37 -18
- package/src/ui/options/EdgeOptions.tsx +157 -0
- package/src/ui/options/NodeOptions.tsx +37 -29
- package/src/xyflow/EdgeMarkers.tsx +148 -0
- package/src/xyflow/FitviewOnDiagramChange.tsx +8 -4
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +59 -55
- package/src/xyflow/SyncWithDiagram.tsx +13 -20
- package/src/xyflow/XYFlow.tsx +2 -4
- package/src/xyflow/XYFlowEvents.tsx +48 -33
- package/src/xyflow/XYFlowInner.tsx +6 -3
- package/src/xyflow/const.ts +3 -3
- package/src/xyflow/diagram-to-xyflow.ts +18 -53
- package/src/xyflow/edges/RelationshipEdge.tsx +218 -126
- package/src/xyflow/edges/edges.css.ts +44 -26
- package/src/xyflow/edges/utils.ts +10 -0
- package/src/xyflow/hooks/index.ts +4 -2
- package/src/xyflow/hooks/useLayoutConstraints.ts +5 -5
- package/src/xyflow/hooks/useXYFlow.ts +28 -1
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +41 -31
- package/src/xyflow/nodes/compound/CompoundNode.tsx +52 -53
- package/src/xyflow/nodes/element/ElementIcon.tsx +11 -22
- package/src/xyflow/nodes/element/ElementLink.tsx +11 -16
- package/src/xyflow/nodes/element/ElementNode.tsx +69 -114
- package/src/xyflow/nodes/element/element.css.ts +48 -15
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -1
- package/src/xyflow/types.ts +2 -4
- package/src/xyflow/utils.ts +3 -6
- package/dist/KeepAspectRatioContainer.d.ts +0 -9
- package/dist/icons/Clipboard.d.ts +0 -3
- package/dist/icons/Link.d.ts +0 -3
- package/dist/icons/ZoomIn.d.ts +0 -3
- package/dist/icons/index.d.ts +0 -3
- package/dist/mantine/theme.d.ts +0 -3
- package/dist/state/WhenInitialized.d.ts +0 -3
- package/dist/ui/Camera.d.ts +0 -3
- package/src/KeepAspectRatioContainer.tsx +0 -36
- package/src/icons/Clipboard.tsx +0 -24
- package/src/icons/Link.tsx +0 -23
- package/src/icons/ZoomIn.tsx +0 -25
- package/src/icons/index.ts +0 -3
- package/src/mantine/theme.ts +0 -20
- package/src/state/WhenInitialized.tsx +0 -24
- package/src/ui/Camera.tsx +0 -174
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
extractStep,
|
|
5
|
-
type Fqn,
|
|
6
|
-
invariant,
|
|
7
|
-
type NonEmptyArray,
|
|
8
|
-
nonNullable,
|
|
9
|
-
type Point
|
|
10
|
-
} from '@likec4/core'
|
|
11
|
-
import { Bezier } from 'bezier-js'
|
|
12
|
-
import { hasAtLeast, reduce } from 'remeda'
|
|
1
|
+
import { extractStep, invariant, nonNullable } from '@likec4/core'
|
|
2
|
+
import type { DiagramNode, DiagramView, Fqn } from '@likec4/core/types'
|
|
3
|
+
import { hasAtLeast } from 'remeda'
|
|
13
4
|
import type { XYFlowData } from '../xyflow/types'
|
|
14
|
-
import {
|
|
5
|
+
import { ZIndexes } from './const'
|
|
15
6
|
|
|
16
|
-
// const nodeZIndex = (node: DiagramNode) => node.level
|
|
17
|
-
const nodeZIndex = (node: DiagramNode) => node.level + 1
|
|
7
|
+
// const nodeZIndex = (node: DiagramNode) => node.level - (node.children.length > 0 ? 1 : 0)
|
|
18
8
|
|
|
19
9
|
export function diagramViewToXYFlowData(
|
|
20
10
|
view: Pick<DiagramView, 'id' | 'nodes' | 'edges' | '__' | 'manualLayout'>,
|
|
@@ -50,10 +40,10 @@ export function diagramViewToXYFlowData(
|
|
|
50
40
|
const nodeById = (id: Fqn) => nonNullable(nodeLookup.get(id), `Node not found: ${id}`)
|
|
51
41
|
|
|
52
42
|
let next: typeof traverse[0] | undefined
|
|
53
|
-
while ((next = traverse.
|
|
43
|
+
while ((next = traverse.shift())) {
|
|
54
44
|
const { node, parent } = next
|
|
55
45
|
if (node.children.length > 0) {
|
|
56
|
-
traverse.
|
|
46
|
+
traverse.push(...node.children.map(child => ({ node: nodeById(child), parent: node })))
|
|
57
47
|
}
|
|
58
48
|
const isCompound = hasAtLeast(node.children, 1)
|
|
59
49
|
const position = {
|
|
@@ -69,6 +59,7 @@ export function diagramViewToXYFlowData(
|
|
|
69
59
|
// const inEdges = node.inEdges.map(e => view.edges.find(edge => edge.id === e)).filter(Boolean)
|
|
70
60
|
|
|
71
61
|
const id = ns + node.id
|
|
62
|
+
const draggable = opts.draggable && (!parent || parent.children.length > 1)
|
|
72
63
|
editor.nodes.push({
|
|
73
64
|
id,
|
|
74
65
|
type: isCompound ? 'compound' : 'element',
|
|
@@ -76,43 +67,22 @@ export function diagramViewToXYFlowData(
|
|
|
76
67
|
fqn: node.id,
|
|
77
68
|
element: node
|
|
78
69
|
},
|
|
79
|
-
draggable
|
|
70
|
+
draggable,
|
|
80
71
|
selectable: opts.selectable,
|
|
81
72
|
deletable: false,
|
|
82
73
|
position,
|
|
83
|
-
zIndex:
|
|
74
|
+
zIndex: isCompound ? ZIndexes.Compound : ZIndexes.Element,
|
|
84
75
|
hidden: false,
|
|
85
|
-
|
|
86
|
-
initialHeight: node.height,
|
|
76
|
+
initialWidth: node.width,
|
|
77
|
+
initialHeight: node.height,
|
|
87
78
|
width: node.width,
|
|
88
79
|
height: node.height,
|
|
89
|
-
// measured: {
|
|
90
|
-
// width: node.width,
|
|
91
|
-
// height: node.height
|
|
92
|
-
// },
|
|
93
80
|
...(parent && {
|
|
94
81
|
parentId: ns + parent.id
|
|
82
|
+
}),
|
|
83
|
+
...(isCompound && {
|
|
84
|
+
dragHandle: '.likec4-compound-title'
|
|
95
85
|
})
|
|
96
|
-
// handles: [
|
|
97
|
-
// ...outEdges.map(out => ({
|
|
98
|
-
// id: `${out.id}`,
|
|
99
|
-
// type: 'source' as const,
|
|
100
|
-
// position: Position.Bottom,
|
|
101
|
-
// x: out.points[0][0],
|
|
102
|
-
// y: out.points[0][1],
|
|
103
|
-
// width: 10,
|
|
104
|
-
// height: 10
|
|
105
|
-
// })),
|
|
106
|
-
// ...inEdges.map(out => ({
|
|
107
|
-
// id: out.id,
|
|
108
|
-
// type: 'target' as const,
|
|
109
|
-
// position: Position.Top,
|
|
110
|
-
// x: out.points[0][0],
|
|
111
|
-
// y: out.points[0][1],
|
|
112
|
-
// width: 10,
|
|
113
|
-
// height: 10
|
|
114
|
-
// }))
|
|
115
|
-
// ],
|
|
116
86
|
})
|
|
117
87
|
}
|
|
118
88
|
|
|
@@ -131,33 +101,28 @@ export function diagramViewToXYFlowData(
|
|
|
131
101
|
invariant(hasAtLeast(edge.points, 2), 'edge should have at least 2 points')
|
|
132
102
|
// invariant(hasAtLeast(controlPoints, 2), 'edge controlPoints should have at least 2 points')
|
|
133
103
|
|
|
134
|
-
const level = Math.max(nodeZIndex(nodeById(source)), nodeZIndex(nodeById(target)))
|
|
104
|
+
// const level = Math.max(nodeZIndex(nodeById(source)), nodeZIndex(nodeById(target)))
|
|
135
105
|
|
|
136
106
|
editor.edges.push({
|
|
137
107
|
id,
|
|
138
108
|
type: 'relationship',
|
|
139
109
|
source: ns + source,
|
|
140
110
|
target: ns + target,
|
|
141
|
-
zIndex:
|
|
111
|
+
zIndex: ZIndexes.Edge,
|
|
112
|
+
selectable: opts.selectable,
|
|
142
113
|
deletable: false,
|
|
143
114
|
data: {
|
|
144
115
|
edge,
|
|
145
116
|
type: 'bezier',
|
|
146
117
|
controlPoints: view.manualLayout?.edges[edge.id]?.controlPoints || null,
|
|
147
|
-
headPoint: edge.headArrowPoint ?? null,
|
|
148
|
-
tailPoint: edge.tailArrowPoint ?? null,
|
|
149
118
|
stepNum: isDynamicView ? extractStep(edge.id) : null,
|
|
150
119
|
label: !!edge.labelBBox
|
|
151
120
|
? {
|
|
152
121
|
bbox: edge.labelBBox,
|
|
153
|
-
// text: edge.labels ? edge.labels.map(l => l.text).join('\n') : ''
|
|
154
122
|
text: edge.label ?? ''
|
|
155
123
|
}
|
|
156
124
|
: null
|
|
157
125
|
},
|
|
158
|
-
// markerEnd: {
|
|
159
|
-
// type: MarkerType.ArrowClosed
|
|
160
|
-
// },
|
|
161
126
|
interactionWidth: 20
|
|
162
127
|
})
|
|
163
128
|
}
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
invariant,
|
|
3
|
+
type NonEmptyArray,
|
|
4
|
+
nonexhaustive,
|
|
5
|
+
nonNullable,
|
|
6
|
+
type Point,
|
|
7
|
+
type RelationshipArrowType
|
|
8
|
+
} from '@likec4/core'
|
|
2
9
|
import { Box } from '@mantine/core'
|
|
3
10
|
import { useIsomorphicLayoutEffect } from '@react-hookz/web'
|
|
4
11
|
import { assignInlineVars } from '@vanilla-extract/dynamic'
|
|
5
12
|
import type { EdgeProps, XYPosition } from '@xyflow/react'
|
|
6
13
|
import { EdgeLabelRenderer } from '@xyflow/react'
|
|
7
|
-
import { getNodePositionWithOrigin } from '@xyflow/system'
|
|
8
14
|
import clsx from 'clsx'
|
|
9
15
|
import { curveCatmullRomOpen, line as d3line } from 'd3-shape'
|
|
10
|
-
import { deepEqual as eq } from 'fast-equals'
|
|
11
|
-
import { memo, useRef, useState } from 'react'
|
|
16
|
+
import { deepEqual, deepEqual as eq } from 'fast-equals'
|
|
17
|
+
import { memo, type PointerEvent as ReactPointerEvent, useRef, useState } from 'react'
|
|
12
18
|
import { first, hasAtLeast, isArray, isTruthy, last } from 'remeda'
|
|
13
|
-
import { useDiagramState, useDiagramStoreApi } from '../../state'
|
|
19
|
+
import { useDiagramState, useDiagramStoreApi } from '../../state/hooks'
|
|
14
20
|
import { ZIndexes } from '../const'
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
21
|
+
import { EdgeMarkers } from '../EdgeMarkers'
|
|
22
|
+
import { useXYStoreApi } from '../hooks'
|
|
23
|
+
import { type RelationshipData, type XYFlowEdge } from '../types'
|
|
24
|
+
import { bezierControlPoints } from '../utils'
|
|
18
25
|
import * as edgesCss from './edges.css'
|
|
19
26
|
import { getNodeIntersectionFromCenterToPoint } from './utils'
|
|
20
27
|
// import { getEdgeParams } from './utils'
|
|
@@ -49,6 +56,32 @@ import { getNodeIntersectionFromCenterToPoint } from './utils'
|
|
|
49
56
|
// return path + segment
|
|
50
57
|
// }
|
|
51
58
|
|
|
59
|
+
const toMarker = (arrowType?: RelationshipArrowType) => {
|
|
60
|
+
if (!arrowType || arrowType === 'none') {
|
|
61
|
+
return undefined
|
|
62
|
+
}
|
|
63
|
+
switch (arrowType) {
|
|
64
|
+
case 'normal':
|
|
65
|
+
case 'crow':
|
|
66
|
+
return 'Arrow' as const
|
|
67
|
+
case 'onormal':
|
|
68
|
+
return 'OArrow' as const
|
|
69
|
+
case 'diamond':
|
|
70
|
+
return 'Diamond' as const
|
|
71
|
+
case 'odiamond':
|
|
72
|
+
return 'ODiamond' as const
|
|
73
|
+
case 'open':
|
|
74
|
+
case 'vee':
|
|
75
|
+
return 'Open' as const
|
|
76
|
+
case 'dot':
|
|
77
|
+
return 'Dot' as const
|
|
78
|
+
case 'odot':
|
|
79
|
+
return `ODot` as const
|
|
80
|
+
default:
|
|
81
|
+
nonexhaustive(arrowType)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
52
85
|
function bezierPath(bezierSpline: NonEmptyArray<Point>) {
|
|
53
86
|
let [start, ...points] = bezierSpline
|
|
54
87
|
invariant(start, 'start should be defined')
|
|
@@ -83,8 +116,8 @@ const sameControlPoints = (a: XYPosition[] | null, b: XYPosition[] | null) => {
|
|
|
83
116
|
|
|
84
117
|
const isEqualProps = (prev: EdgeProps<XYFlowEdge>, next: EdgeProps<XYFlowEdge>) => (
|
|
85
118
|
prev.id === next.id
|
|
86
|
-
&& prev.source
|
|
87
|
-
&& prev.target
|
|
119
|
+
&& eq(prev.source, next.source)
|
|
120
|
+
&& eq(prev.target, next.target)
|
|
88
121
|
&& eq(prev.selected ?? false, next.selected ?? false)
|
|
89
122
|
&& isSame(prev.sourceX, next.sourceX)
|
|
90
123
|
&& isSame(prev.sourceY, next.sourceY)
|
|
@@ -103,12 +136,17 @@ const curve = d3line<XYPosition>()
|
|
|
103
136
|
export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(function RelationshipEdgeR({
|
|
104
137
|
id,
|
|
105
138
|
data,
|
|
106
|
-
selected
|
|
139
|
+
selected,
|
|
140
|
+
sourceX,
|
|
141
|
+
sourceY,
|
|
142
|
+
targetX,
|
|
143
|
+
targetY,
|
|
107
144
|
style,
|
|
108
145
|
source,
|
|
109
146
|
target,
|
|
110
147
|
interactionWidth
|
|
111
148
|
}) {
|
|
149
|
+
const [isControlPointDragging, setIsControlPointDragging] = useState(false)
|
|
112
150
|
const diagramStore = useDiagramStoreApi()
|
|
113
151
|
const xyflowStore = useXYStoreApi()
|
|
114
152
|
const { isActive, isEditable, isEdgePathEditable, isHovered, isDimmed } = useDiagramState(s => ({
|
|
@@ -164,16 +202,24 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
|
|
|
164
202
|
if (isModified) {
|
|
165
203
|
labelX = labelPos.x
|
|
166
204
|
labelY = labelPos.y
|
|
167
|
-
const sourceCenterPos =
|
|
168
|
-
const targetCenterPos =
|
|
205
|
+
const sourceCenterPos = { x: sourceX, y: sourceY }
|
|
206
|
+
const targetCenterPos = { x: targetX, y: targetY }
|
|
169
207
|
|
|
170
|
-
const points =
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
208
|
+
const points = diagramEdge.dir === 'back'
|
|
209
|
+
? [
|
|
210
|
+
targetCenterPos,
|
|
211
|
+
getNodeIntersectionFromCenterToPoint(targetNode, first(controlPoints) ?? sourceCenterPos),
|
|
212
|
+
...controlPoints,
|
|
213
|
+
getNodeIntersectionFromCenterToPoint(sourceNode, last(controlPoints) ?? targetCenterPos),
|
|
214
|
+
sourceCenterPos
|
|
215
|
+
]
|
|
216
|
+
: [
|
|
217
|
+
sourceCenterPos,
|
|
218
|
+
getNodeIntersectionFromCenterToPoint(sourceNode, first(controlPoints) ?? targetCenterPos),
|
|
219
|
+
...controlPoints,
|
|
220
|
+
getNodeIntersectionFromCenterToPoint(targetNode, last(controlPoints) ?? sourceCenterPos),
|
|
221
|
+
targetCenterPos
|
|
222
|
+
]
|
|
177
223
|
|
|
178
224
|
edgePath = nonNullable(curve(points))
|
|
179
225
|
} else {
|
|
@@ -198,69 +244,88 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
|
|
|
198
244
|
setLabelPosition(current => isSamePoint(current, point) ? current : point)
|
|
199
245
|
}, [edgePath])
|
|
200
246
|
|
|
201
|
-
const onControlPointerDown = (index: number, e:
|
|
247
|
+
const onControlPointerDown = (index: number, e: ReactPointerEvent<SVGCircleElement>) => {
|
|
202
248
|
const { domNode } = xyflowStore.getState()
|
|
203
|
-
if (!domNode)
|
|
249
|
+
if (!domNode || e.pointerType !== 'mouse') {
|
|
250
|
+
return
|
|
251
|
+
}
|
|
204
252
|
const { xyflow } = diagramStore.getState()
|
|
253
|
+
if (e.button === 2 && controlPoints.length > 1) {
|
|
254
|
+
const newControlPoints = controlPoints.slice()
|
|
255
|
+
newControlPoints.splice(index, 1)
|
|
256
|
+
e.stopPropagation()
|
|
257
|
+
// Defer the update to avoid conflict with the pointerup event
|
|
258
|
+
setTimeout(() => {
|
|
259
|
+
xyflow.updateEdgeData(id, { controlPoints: newControlPoints })
|
|
260
|
+
}, 10)
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
if (e.button !== 0) {
|
|
264
|
+
return
|
|
265
|
+
}
|
|
205
266
|
e.stopPropagation()
|
|
206
267
|
let hasMoved = false
|
|
207
268
|
let pointer = { x: e.clientX, y: e.clientY }
|
|
208
269
|
const onPointerMove = (e: PointerEvent) => {
|
|
209
270
|
if (!isSamePoint(pointer, [e.clientX, e.clientY])) {
|
|
271
|
+
setIsControlPointDragging(true)
|
|
272
|
+
if (!hasMoved) {
|
|
273
|
+
diagramStore.getState().cancelSaveManualLayout()
|
|
274
|
+
}
|
|
210
275
|
hasMoved = true
|
|
211
276
|
pointer = { x: e.clientX, y: e.clientY }
|
|
212
277
|
const { x, y } = xyflow.screenToFlowPosition(pointer, { snapToGrid: false })
|
|
213
278
|
const newControlPoints = controlPoints.slice()
|
|
214
279
|
newControlPoints[index] = {
|
|
215
|
-
x:
|
|
216
|
-
y:
|
|
280
|
+
x: Math.round(x),
|
|
281
|
+
y: Math.round(y)
|
|
217
282
|
}
|
|
218
283
|
xyflow.updateEdgeData(id, { controlPoints: newControlPoints })
|
|
219
284
|
}
|
|
220
285
|
}
|
|
221
286
|
const onPointerUp = () => {
|
|
287
|
+
setIsControlPointDragging(false)
|
|
222
288
|
domNode.removeEventListener('pointermove', onPointerMove)
|
|
223
289
|
domNode.removeEventListener('pointerup', onPointerUp)
|
|
224
290
|
if (hasMoved) {
|
|
225
|
-
diagramStore.getState().triggerSaveManualLayout(
|
|
291
|
+
diagramStore.getState().triggerSaveManualLayout()
|
|
226
292
|
}
|
|
227
293
|
}
|
|
228
294
|
domNode.addEventListener('pointermove', onPointerMove)
|
|
229
295
|
domNode.addEventListener('pointerup', onPointerUp, { once: true })
|
|
230
296
|
}
|
|
231
297
|
|
|
232
|
-
|
|
233
|
-
let
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
;[markerStart, markerEnd] = [markerEnd, markerStart]
|
|
298
|
+
let markerStartName = toMarker(diagramEdge.tail)
|
|
299
|
+
let markerEndName = toMarker(diagramEdge.head ?? 'normal')
|
|
300
|
+
if (diagramEdge.dir === 'back') {
|
|
301
|
+
;[markerStartName, markerEndName] = [markerEndName, markerStartName]
|
|
237
302
|
}
|
|
238
303
|
|
|
304
|
+
const MarkerStart = markerStartName ? EdgeMarkers[markerStartName] : null
|
|
305
|
+
const MarkerEnd = markerEndName ? EdgeMarkers[markerEndName] : null
|
|
306
|
+
|
|
239
307
|
return (
|
|
240
308
|
<g
|
|
241
309
|
className={clsx([
|
|
242
310
|
edgesCss.container,
|
|
243
|
-
isDimmed && edgesCss.dimmed
|
|
311
|
+
isDimmed && edgesCss.dimmed,
|
|
312
|
+
isControlPointDragging && edgesCss.controlDragging
|
|
244
313
|
])}
|
|
245
314
|
data-likec4-color={color}
|
|
246
315
|
data-edge-dir={diagramEdge.dir}
|
|
247
316
|
data-edge-active={isActive}
|
|
248
317
|
data-edge-hovered={isHovered}>
|
|
318
|
+
<path
|
|
319
|
+
className={clsx('react-flow__edge-interaction')}
|
|
320
|
+
d={edgePath}
|
|
321
|
+
fill="none"
|
|
322
|
+
stroke={'transparent'}
|
|
323
|
+
strokeWidth={interactionWidth ?? 10}
|
|
324
|
+
/>
|
|
249
325
|
<g className={edgesCss.markerContext}>
|
|
250
326
|
<defs>
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
viewBox="0 0 16 10"
|
|
254
|
-
// TODO: graphviz cut bezier path at arrow, we don't
|
|
255
|
-
refX={isModified ? '15' : '1'}
|
|
256
|
-
refY="5"
|
|
257
|
-
markerWidth="5"
|
|
258
|
-
markerHeight="4"
|
|
259
|
-
markerUnits="strokeWidth"
|
|
260
|
-
preserveAspectRatio="xMaxYMid meet"
|
|
261
|
-
orient="auto-start-reverse">
|
|
262
|
-
<path d="M 0 0 L 16 5 L 0 10 z" stroke="context-stroke" fill="context-stroke" />
|
|
263
|
-
</marker>
|
|
327
|
+
{MarkerStart && <MarkerStart id={'start' + id} />}
|
|
328
|
+
{MarkerEnd && <MarkerEnd id={'end' + id} />}
|
|
264
329
|
</defs>
|
|
265
330
|
<path
|
|
266
331
|
className={clsx('react-flow__edge-path', edgesCss.edgePathBg)}
|
|
@@ -275,92 +340,119 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
|
|
|
275
340
|
style={style}
|
|
276
341
|
strokeLinecap={'round'}
|
|
277
342
|
strokeDasharray={strokeDasharray}
|
|
278
|
-
markerStart={
|
|
279
|
-
markerEnd={
|
|
343
|
+
markerStart={MarkerStart ? `url(#start${id})` : undefined}
|
|
344
|
+
markerEnd={MarkerEnd ? `url(#end${id})` : undefined}
|
|
280
345
|
/>
|
|
281
346
|
</g>
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
347
|
+
|
|
348
|
+
{isEdgePathEditable && (
|
|
349
|
+
<g
|
|
350
|
+
onContextMenu={e => {
|
|
351
|
+
e.preventDefault()
|
|
352
|
+
e.stopPropagation()
|
|
353
|
+
}}>
|
|
354
|
+
{controlPoints.map((p, i) => (
|
|
355
|
+
<circle
|
|
356
|
+
onPointerDown={e => onControlPointerDown(i, e)}
|
|
357
|
+
className={edgesCss.controlPoint}
|
|
358
|
+
key={i}
|
|
359
|
+
cx={p.x}
|
|
360
|
+
cy={p.y}
|
|
361
|
+
r={3}
|
|
362
|
+
/>
|
|
363
|
+
))}
|
|
364
|
+
</g>
|
|
365
|
+
)}
|
|
299
366
|
{(data.label || isStepEdge) && (
|
|
300
|
-
<
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
...(data.label && {
|
|
317
|
-
maxWidth: data.label.bbox.width + 12
|
|
318
|
-
}),
|
|
319
|
-
zIndex: edgeLookup.get(id)?.zIndex ?? ZIndexes.Edge
|
|
320
|
-
}}
|
|
321
|
-
mod={{
|
|
322
|
-
'data-edge-hovered': isHovered,
|
|
323
|
-
'data-edge-active': isActive
|
|
324
|
-
}}
|
|
325
|
-
>
|
|
326
|
-
{isStepEdge && (
|
|
327
|
-
<Box className={edgesCss.stepEdgeNumber}>
|
|
328
|
-
{data.stepNum}
|
|
329
|
-
</Box>
|
|
330
|
-
)}
|
|
331
|
-
{data.label?.text && (
|
|
332
|
-
<Box className={edgesCss.edgeLabelText}>
|
|
333
|
-
{data.label.text}
|
|
334
|
-
</Box>
|
|
335
|
-
)}
|
|
336
|
-
{
|
|
337
|
-
/* <Popover
|
|
338
|
-
position="bottom"
|
|
339
|
-
floatingStrategy="fixed"
|
|
340
|
-
shadow="lg"
|
|
341
|
-
disabled={!selected}
|
|
342
|
-
transitionProps={{
|
|
343
|
-
transition: 'pop'
|
|
344
|
-
}}>
|
|
345
|
-
<Popover.Target>
|
|
346
|
-
<Box className={clsx('nodrag nopan', edgeLabelBody)}>
|
|
347
|
-
{data.label.text}
|
|
348
|
-
</Box>
|
|
349
|
-
</Popover.Target>
|
|
350
|
-
<Popover.Dropdown>
|
|
351
|
-
{edge.relations.map((relation) => (
|
|
352
|
-
<Box key={relation}>
|
|
353
|
-
<Text size='xs'>{relation}</Text>
|
|
354
|
-
</Box>
|
|
355
|
-
))}
|
|
356
|
-
<TextInput label="Name" placeholder="Name" size="xs" />
|
|
357
|
-
<TextInput label="Email" placeholder="john@doe.com" size="xs" mt="xs" />
|
|
358
|
-
</Popover.Dropdown>
|
|
359
|
-
</Popover> */
|
|
360
|
-
}
|
|
361
|
-
</Box>
|
|
362
|
-
</EdgeLabelRenderer>
|
|
367
|
+
<EdgeLabel
|
|
368
|
+
{...({
|
|
369
|
+
isDimmed,
|
|
370
|
+
color,
|
|
371
|
+
isModified,
|
|
372
|
+
labelX,
|
|
373
|
+
labelY,
|
|
374
|
+
isEdgePathEditable,
|
|
375
|
+
selected: selected ?? false,
|
|
376
|
+
stepNum: data.stepNum,
|
|
377
|
+
label: data.label,
|
|
378
|
+
zIndex: edgeLookup.get(id)!.zIndex ?? ZIndexes.Edge,
|
|
379
|
+
isHovered,
|
|
380
|
+
isActive,
|
|
381
|
+
isStepEdge
|
|
382
|
+
})} />
|
|
363
383
|
)}
|
|
364
384
|
</g>
|
|
365
385
|
)
|
|
366
386
|
}, isEqualProps)
|
|
387
|
+
|
|
388
|
+
type EdgeLabelProps = {
|
|
389
|
+
isDimmed: boolean
|
|
390
|
+
color: string
|
|
391
|
+
isModified: boolean
|
|
392
|
+
labelX: number
|
|
393
|
+
labelY: number
|
|
394
|
+
isEdgePathEditable: boolean
|
|
395
|
+
selected: boolean
|
|
396
|
+
stepNum: number | null
|
|
397
|
+
label: RelationshipData['label']
|
|
398
|
+
isHovered: boolean
|
|
399
|
+
isActive: boolean
|
|
400
|
+
zIndex: number
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const EdgeLabel = memo<EdgeLabelProps>(({
|
|
404
|
+
isDimmed,
|
|
405
|
+
color,
|
|
406
|
+
isModified,
|
|
407
|
+
labelX,
|
|
408
|
+
labelY,
|
|
409
|
+
isEdgePathEditable,
|
|
410
|
+
selected,
|
|
411
|
+
label,
|
|
412
|
+
stepNum,
|
|
413
|
+
isHovered,
|
|
414
|
+
isActive,
|
|
415
|
+
zIndex
|
|
416
|
+
}) => {
|
|
417
|
+
return (
|
|
418
|
+
<EdgeLabelRenderer>
|
|
419
|
+
<Box
|
|
420
|
+
className={clsx([
|
|
421
|
+
'nodrag nopan',
|
|
422
|
+
edgesCss.container,
|
|
423
|
+
edgesCss.edgeLabel,
|
|
424
|
+
isDimmed && edgesCss.dimmed
|
|
425
|
+
])}
|
|
426
|
+
style={{
|
|
427
|
+
...assignInlineVars({
|
|
428
|
+
[edgesCss.varLabelX]: isModified ? `calc(${labelX}px - 10%)` : `${labelX}px`,
|
|
429
|
+
[edgesCss.varLabelY]: isModified ? `${labelY - 5}px` : `${labelY}px`
|
|
430
|
+
}),
|
|
431
|
+
...(isEdgePathEditable && selected && {
|
|
432
|
+
pointerEvents: 'none'
|
|
433
|
+
}),
|
|
434
|
+
...(label && {
|
|
435
|
+
maxWidth: label.bbox.width + 18
|
|
436
|
+
}),
|
|
437
|
+
zIndex
|
|
438
|
+
}}
|
|
439
|
+
mod={{
|
|
440
|
+
'data-likec4-color': color,
|
|
441
|
+
'data-edge-hovered': isHovered,
|
|
442
|
+
'data-edge-active': isActive
|
|
443
|
+
}}
|
|
444
|
+
>
|
|
445
|
+
{stepNum !== null && (
|
|
446
|
+
<Box className={edgesCss.stepEdgeNumber}>
|
|
447
|
+
{stepNum}
|
|
448
|
+
</Box>
|
|
449
|
+
)}
|
|
450
|
+
{isTruthy(label?.text) && (
|
|
451
|
+
<Box className={edgesCss.edgeLabelText}>
|
|
452
|
+
{label.text}
|
|
453
|
+
</Box>
|
|
454
|
+
)}
|
|
455
|
+
</Box>
|
|
456
|
+
</EdgeLabelRenderer>
|
|
457
|
+
)
|
|
458
|
+
}, deepEqual)
|