@likec4/diagram 1.4.0 → 1.6.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/LICENSE +1 -1
- package/dist/LikeC4Diagram.css.d.ts +0 -1
- package/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.props.d.ts +19 -4
- package/dist/StaticLikeC4Diagram.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +3937 -3646
- package/dist/state/DiagramContext.d.ts +95 -4159
- package/dist/state/diagramStore.d.ts +100 -4164
- package/dist/state/hooks.d.ts +4 -0
- package/dist/state/index.d.ts +1 -2
- package/dist/style.css +281 -191
- 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 +8 -5
- 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 +3 -2
- 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 +13 -12
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
- package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +5 -0
- package/dist/xyflow/nodes/element/element.css.d.ts +5 -2
- package/dist/xyflow/types.d.ts +2 -32
- package/package.json +19 -19
- package/src/LikeC4Diagram.css.ts +0 -8
- package/src/LikeC4Diagram.props.ts +23 -5
- package/src/LikeC4Diagram.tsx +18 -21
- package/src/StaticLikeC4Diagram.tsx +1 -0
- package/src/globals.css.ts +10 -3
- package/src/index.ts +1 -1
- package/src/mantine.css.ts +2 -2
- package/src/state/DiagramContext.tsx +44 -13
- package/src/state/diagramStore.ts +72 -53
- package/src/state/hooks.ts +5 -0
- package/src/state/index.ts +1 -2
- package/src/ui/BackwardForwardPanel.css.ts +9 -9
- package/src/ui/DiagramTitlePanel.css.ts +7 -2
- package/src/ui/DiagramTitlePanel.tsx +5 -3
- package/src/ui/DynamicViewWalkthrough.tsx +6 -4
- package/src/ui/OptionsPanel.tsx +37 -27
- 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 +9 -5
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +59 -55
- package/src/xyflow/SyncWithDiagram.tsx +13 -20
- package/src/xyflow/XYFlow.tsx +3 -4
- package/src/xyflow/XYFlowEvents.tsx +48 -33
- package/src/xyflow/const.ts +3 -3
- package/src/xyflow/diagram-to-xyflow.ts +17 -41
- package/src/xyflow/edges/RelationshipEdge.tsx +208 -119
- package/src/xyflow/edges/edges.css.ts +40 -26
- package/src/xyflow/edges/utils.ts +10 -0
- package/src/xyflow/hooks/index.ts +4 -2
- package/src/xyflow/hooks/useLayoutConstraints.ts +3 -4
- package/src/xyflow/hooks/useXYFlow.ts +28 -1
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +34 -24
- package/src/xyflow/nodes/compound/CompoundNode.tsx +52 -53
- package/src/xyflow/nodes/element/ElementLink.tsx +12 -16
- package/src/xyflow/nodes/element/ElementNode.tsx +118 -114
- package/src/xyflow/nodes/element/ElementShapeSvg.tsx +2 -2
- package/src/xyflow/nodes/element/element.css.ts +147 -55
- 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/dist/xyflow/nodes/element/ElementIcon.d.ts +0 -10
- 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
- package/src/xyflow/nodes/element/ElementIcon.tsx +0 -44
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useNodesData, useReactFlow, useStore, useStoreApi } from '@xyflow/react'
|
|
2
|
-
import { shallowEqual } from 'fast-equals'
|
|
2
|
+
import { deepEqual, shallowEqual } from 'fast-equals'
|
|
3
|
+
import { useCallback } from 'react'
|
|
3
4
|
import type { XYFlowEdge, XYFlowNode, XYFlowState } from '../types'
|
|
4
5
|
|
|
5
6
|
export const useXYFlow = useReactFlow<XYFlowNode, XYFlowEdge>
|
|
@@ -18,3 +19,29 @@ export function useXYStore<StateSlice = unknown>(
|
|
|
18
19
|
}
|
|
19
20
|
export const useXYStoreApi = useStoreApi<XYFlowNode, XYFlowEdge>
|
|
20
21
|
export type XYStoreApi = ReturnType<typeof useXYStoreApi>
|
|
22
|
+
|
|
23
|
+
export function useXYEdgesData(edgeIds: string[]): Pick<XYFlowEdge, 'id' | 'data'>[] {
|
|
24
|
+
const ids = edgeIds.join(',')
|
|
25
|
+
const edgesData = useXYStore(
|
|
26
|
+
useCallback(
|
|
27
|
+
(s) => {
|
|
28
|
+
const data = [] as Pick<XYFlowEdge, 'id' | 'data'>[]
|
|
29
|
+
for (const id of edgeIds) {
|
|
30
|
+
const edge = s.edgeLookup.get(id)
|
|
31
|
+
if (edge) {
|
|
32
|
+
data.push({
|
|
33
|
+
id: edge.id,
|
|
34
|
+
data: edge.data
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return data
|
|
40
|
+
},
|
|
41
|
+
[ids]
|
|
42
|
+
),
|
|
43
|
+
deepEqual
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return edgesData
|
|
47
|
+
}
|
|
@@ -12,6 +12,12 @@ export const container = style({
|
|
|
12
12
|
margin: 0
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
+
export const nodeHandlerInCenter = style({
|
|
16
|
+
top: '50%',
|
|
17
|
+
left: '50%',
|
|
18
|
+
visibility: 'hidden'
|
|
19
|
+
})
|
|
20
|
+
|
|
15
21
|
export const dimmed = style({})
|
|
16
22
|
|
|
17
23
|
globalStyle(`.react-flow__node-compound:has(${dimmed})`, {
|
|
@@ -37,8 +43,12 @@ export const compoundBody = style({
|
|
|
37
43
|
position: 'relative',
|
|
38
44
|
borderRadius: 6,
|
|
39
45
|
boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.1) , 0 2px 2px -1px rgba(0,0,0,0.4)',
|
|
40
|
-
padding:
|
|
46
|
+
padding: 0,
|
|
47
|
+
margin: 0,
|
|
41
48
|
transition: 'all 200ms ease-out',
|
|
49
|
+
backgroundClip: 'padding-box',
|
|
50
|
+
overflow: 'hidden',
|
|
51
|
+
cursor: 'default',
|
|
42
52
|
selectors: {
|
|
43
53
|
[`:where(.react-flow__node.selected) &`]: {
|
|
44
54
|
boxShadow: 'none'
|
|
@@ -56,11 +66,9 @@ export const compoundBody = style({
|
|
|
56
66
|
left: 0,
|
|
57
67
|
width: '100%',
|
|
58
68
|
height: '100%',
|
|
59
|
-
borderRadius: 6,
|
|
60
69
|
pointerEvents: 'none',
|
|
61
70
|
transition: 'background 175ms ease-out, opacity 175ms ease-out',
|
|
62
|
-
background: vars.element.fill
|
|
63
|
-
backgroundClip: 'padding-box'
|
|
71
|
+
background: vars.element.fill
|
|
64
72
|
}
|
|
65
73
|
})
|
|
66
74
|
|
|
@@ -68,16 +76,15 @@ const opacityDeltaOnHover = createVar('opacityDeltaOnHover')
|
|
|
68
76
|
export const transparent = style({
|
|
69
77
|
borderStyle: 'dashed',
|
|
70
78
|
borderWidth: 3,
|
|
71
|
-
padding: 6,
|
|
72
79
|
boxShadow: 'none',
|
|
73
80
|
borderColor: `color-mix(in srgb , ${vars.element.stroke}, transparent ${fallbackVar(varBorderTransparency, '10%')})`,
|
|
74
|
-
backgroundClip: 'padding-box',
|
|
75
81
|
vars: {
|
|
76
82
|
[opacityDeltaOnHover]: '0',
|
|
77
83
|
'--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`
|
|
78
84
|
},
|
|
79
85
|
':before': {
|
|
80
|
-
|
|
86
|
+
borderRadius: 'unset',
|
|
87
|
+
transitionDelay: '100ms',
|
|
81
88
|
opacity: calc.add(fallbackVar(varOpacity, '1'), opacityDeltaOnHover)
|
|
82
89
|
},
|
|
83
90
|
selectors: {
|
|
@@ -88,7 +95,7 @@ export const transparent = style({
|
|
|
88
95
|
// },
|
|
89
96
|
[`:where([data-hovered]) &`]: {
|
|
90
97
|
vars: {
|
|
91
|
-
[opacityDeltaOnHover]: '.
|
|
98
|
+
[opacityDeltaOnHover]: '.08'
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
}
|
|
@@ -96,21 +103,28 @@ export const transparent = style({
|
|
|
96
103
|
|
|
97
104
|
export const title = style({
|
|
98
105
|
fontFamily: vars.compound.font,
|
|
99
|
-
fontOpticalSizing: 'auto',
|
|
100
|
-
fontStyle: 'normal',
|
|
101
106
|
textAlign: 'left',
|
|
102
|
-
display: 'inline-block',
|
|
103
107
|
fontWeight: 600,
|
|
104
|
-
fontSize:
|
|
108
|
+
fontSize: rem(15),
|
|
105
109
|
textTransform: 'uppercase',
|
|
106
110
|
letterSpacing: '0.2px',
|
|
107
111
|
lineHeight: 1,
|
|
108
|
-
opacity: 0.75,
|
|
109
112
|
color: `var(--_compound-title-color,${vars.compound.titleColor})`,
|
|
110
|
-
paddingLeft:
|
|
113
|
+
paddingLeft: 14,
|
|
114
|
+
paddingTop: 13,
|
|
115
|
+
paddingBottom: 6,
|
|
116
|
+
mixBlendMode: 'screen',
|
|
117
|
+
selectors: {
|
|
118
|
+
[`:where(.react-flow__node.draggable) &`]: {
|
|
119
|
+
cursor: 'grab'
|
|
120
|
+
},
|
|
121
|
+
[`:where(.likec4-compound-transparent) &`]: {
|
|
122
|
+
paddingTop: 10
|
|
123
|
+
}
|
|
124
|
+
}
|
|
111
125
|
})
|
|
112
126
|
export const titleWithNavigation = style({
|
|
113
|
-
paddingLeft:
|
|
127
|
+
paddingLeft: 32
|
|
114
128
|
})
|
|
115
129
|
|
|
116
130
|
globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent)`, {
|
|
@@ -119,10 +133,6 @@ globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transpa
|
|
|
119
133
|
}
|
|
120
134
|
})
|
|
121
135
|
|
|
122
|
-
globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent) ${title}`, {
|
|
123
|
-
opacity: 1
|
|
124
|
-
})
|
|
125
|
-
|
|
126
136
|
const indicatorKeyframes = keyframes({
|
|
127
137
|
'from': {
|
|
128
138
|
opacity: 0.6
|
|
@@ -197,10 +207,10 @@ export const navigateBtn = style({
|
|
|
197
207
|
position: 'absolute',
|
|
198
208
|
pointerEvents: 'all',
|
|
199
209
|
left: 3,
|
|
200
|
-
top:
|
|
210
|
+
top: 6,
|
|
201
211
|
cursor: 'pointer',
|
|
202
212
|
color: `var(--_compound-title-color,${navigateBtnColor})`,
|
|
203
|
-
transformOrigin: '90%
|
|
213
|
+
transformOrigin: '90% 50%',
|
|
204
214
|
opacity: 0.75,
|
|
205
215
|
transition: 'all 150ms ease-out',
|
|
206
216
|
transitionDelay: '0ms',
|
|
@@ -225,7 +235,7 @@ export const navigateBtn = style({
|
|
|
225
235
|
[`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent) &`]: {
|
|
226
236
|
opacity: 0.85,
|
|
227
237
|
vars: {
|
|
228
|
-
'--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill}, transparent
|
|
238
|
+
'--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 60%)`,
|
|
229
239
|
'--ai-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 20%)`
|
|
230
240
|
}
|
|
231
241
|
},
|
|
@@ -241,8 +251,8 @@ export const navigateBtn = style({
|
|
|
241
251
|
}
|
|
242
252
|
})
|
|
243
253
|
globalStyle(`${navigateBtn} svg.icon`, {
|
|
244
|
-
width: '
|
|
245
|
-
height: '
|
|
254
|
+
width: '75%',
|
|
255
|
+
height: '75%',
|
|
246
256
|
strokeWidth: 1.5
|
|
247
257
|
})
|
|
248
258
|
|
|
@@ -2,12 +2,10 @@ import { Box, Text } from '@mantine/core'
|
|
|
2
2
|
import { assignInlineVars } from '@vanilla-extract/dynamic'
|
|
3
3
|
import { Handle, type NodeProps, Position } from '@xyflow/react'
|
|
4
4
|
import clsx from 'clsx'
|
|
5
|
-
import { DEV } from 'esm-env'
|
|
6
5
|
import { deepEqual as eq } from 'fast-equals'
|
|
7
6
|
import { memo } from 'react'
|
|
8
7
|
import { clamp } from 'remeda'
|
|
9
|
-
import
|
|
10
|
-
import { useDiagramState } from '../../../state'
|
|
8
|
+
import { useDiagramState } from '../../../state/useDiagramStore'
|
|
11
9
|
import type { CompoundXYFlowNode } from '../../types'
|
|
12
10
|
import { NavigateToBtn } from '../shared/NavigateToBtn'
|
|
13
11
|
import * as css from './CompoundNode.css'
|
|
@@ -31,7 +29,6 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
|
|
|
31
29
|
element
|
|
32
30
|
}
|
|
33
31
|
}) {
|
|
34
|
-
DEV && useTilg()
|
|
35
32
|
const { color, style, depth = 1, ...compound } = element
|
|
36
33
|
// const w = toDomPrecision(width ?? compound.width)
|
|
37
34
|
// const h = toDomPrecision(height ?? compound.height)
|
|
@@ -52,65 +49,67 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
|
|
|
52
49
|
const isnavigable = !!compound.navigateTo && hasOnNavigateTo
|
|
53
50
|
|
|
54
51
|
return (
|
|
55
|
-
|
|
56
|
-
className={clsx(
|
|
57
|
-
css.container,
|
|
58
|
-
'likec4-compound-node',
|
|
59
|
-
opacity < 1 && 'likec4-compound-transparent',
|
|
60
|
-
isDimmed && css.dimmed
|
|
61
|
-
)}
|
|
62
|
-
mod={{
|
|
63
|
-
'compound-depth': depth,
|
|
64
|
-
'likec4-color': color,
|
|
65
|
-
hovered: isHovered
|
|
66
|
-
}}
|
|
67
|
-
>
|
|
52
|
+
<>
|
|
68
53
|
<Handle
|
|
69
54
|
type="target"
|
|
70
55
|
position={Position.Top}
|
|
71
|
-
|
|
56
|
+
className={css.nodeHandlerInCenter}
|
|
72
57
|
/>
|
|
73
|
-
<
|
|
74
|
-
<rect
|
|
75
|
-
x={0}
|
|
76
|
-
y={0}
|
|
77
|
-
width={'100%'}
|
|
78
|
-
height={'100%'}
|
|
79
|
-
rx={6}
|
|
80
|
-
/>
|
|
81
|
-
</svg>
|
|
82
|
-
<div
|
|
58
|
+
<Box
|
|
83
59
|
className={clsx(
|
|
84
|
-
css.
|
|
85
|
-
|
|
86
|
-
'likec4-compound'
|
|
60
|
+
css.container,
|
|
61
|
+
'likec4-compound-node',
|
|
62
|
+
opacity < 1 && 'likec4-compound-transparent',
|
|
63
|
+
isDimmed && css.dimmed
|
|
87
64
|
)}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}),
|
|
94
|
-
borderStyle: style.border ?? 'dashed'
|
|
95
|
-
}
|
|
96
|
-
: undefined}
|
|
65
|
+
mod={{
|
|
66
|
+
'compound-depth': depth,
|
|
67
|
+
'likec4-color': color,
|
|
68
|
+
hovered: isHovered
|
|
69
|
+
}}
|
|
97
70
|
>
|
|
98
|
-
<
|
|
99
|
-
|
|
71
|
+
<svg className={css.indicator}>
|
|
72
|
+
<rect
|
|
73
|
+
x={0}
|
|
74
|
+
y={0}
|
|
75
|
+
width={'100%'}
|
|
76
|
+
height={'100%'}
|
|
77
|
+
rx={6}
|
|
78
|
+
/>
|
|
79
|
+
</svg>
|
|
80
|
+
<div
|
|
100
81
|
className={clsx(
|
|
101
|
-
css.
|
|
102
|
-
|
|
103
|
-
'likec4-compound
|
|
104
|
-
)}
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
82
|
+
css.compoundBody,
|
|
83
|
+
opacity < 1 && css.transparent,
|
|
84
|
+
'likec4-compound'
|
|
85
|
+
)}
|
|
86
|
+
style={opacity < 1
|
|
87
|
+
? {
|
|
88
|
+
...assignInlineVars({
|
|
89
|
+
[css.varBorderTransparency]: `${borderTransparency}%`,
|
|
90
|
+
[css.varOpacity]: opacity.toFixed(2)
|
|
91
|
+
}),
|
|
92
|
+
borderStyle: style.border ?? 'dashed'
|
|
93
|
+
}
|
|
94
|
+
: undefined}
|
|
95
|
+
>
|
|
96
|
+
<Text
|
|
97
|
+
component="div"
|
|
98
|
+
className={clsx(
|
|
99
|
+
css.title,
|
|
100
|
+
isnavigable && css.titleWithNavigation,
|
|
101
|
+
'likec4-compound-title'
|
|
102
|
+
)}>
|
|
103
|
+
{compound.title}
|
|
104
|
+
</Text>
|
|
105
|
+
</div>
|
|
106
|
+
{isnavigable && <NavigateToBtn xynodeId={id} className={css.navigateBtn} />}
|
|
107
|
+
</Box>
|
|
109
108
|
<Handle
|
|
110
109
|
type="source"
|
|
111
|
-
position={Position.
|
|
112
|
-
|
|
110
|
+
position={Position.Top}
|
|
111
|
+
className={css.nodeHandlerInCenter}
|
|
113
112
|
/>
|
|
114
|
-
|
|
113
|
+
</>
|
|
115
114
|
)
|
|
116
115
|
}, isEqualProps)
|
|
@@ -7,14 +7,17 @@ import {
|
|
|
7
7
|
Group,
|
|
8
8
|
HoverCard,
|
|
9
9
|
HoverCardDropdown,
|
|
10
|
+
type HoverCardProps,
|
|
10
11
|
HoverCardTarget,
|
|
11
12
|
Stack,
|
|
12
13
|
UnstyledButton
|
|
13
14
|
} from '@mantine/core'
|
|
15
|
+
import { IconLink } from '@tabler/icons-react'
|
|
14
16
|
import clsx from 'clsx'
|
|
15
17
|
import { useId } from 'react'
|
|
16
18
|
import { clamp } from 'remeda'
|
|
17
|
-
import {
|
|
19
|
+
import { type DiagramState } from '../../../state/diagramStore'
|
|
20
|
+
import { useDiagramState } from '../../../state/useDiagramStore'
|
|
18
21
|
import type { XYFlowNode } from '../../types'
|
|
19
22
|
import { elementLink, trigger } from './ElementLink.css'
|
|
20
23
|
|
|
@@ -22,23 +25,17 @@ type ElementLinkProps = {
|
|
|
22
25
|
element: XYFlowNode['data']['element']
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
const selector = (s: DiagramState) => {
|
|
29
|
+
const target = s.getContainer()
|
|
30
|
+
return target ? { target } : null
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
export function ElementLink({
|
|
26
34
|
element
|
|
27
35
|
}: ElementLinkProps) {
|
|
28
36
|
invariant(element.links, 'ElementLink: links are required')
|
|
29
37
|
const id = useId()
|
|
30
|
-
|
|
31
|
-
// const root = mantineCtx.getRootElement()
|
|
32
|
-
|
|
33
|
-
// const targetSelector = `.react-flow:has([data-likec4-linkid="${id}"])`
|
|
34
|
-
// // const target = root?.querySelector<HTMLDivElement>(targetSelector) ?? targetSelector
|
|
35
|
-
|
|
36
|
-
// const portalProps = useMemo(() => ({
|
|
37
|
-
// get target(): HTMLElement | string {
|
|
38
|
-
// const root = mantineCtx.getRootElement()
|
|
39
|
-
// return root?.querySelector<HTMLDivElement>(targetSelector) ?? targetSelector
|
|
40
|
-
// }
|
|
41
|
-
// }), [targetSelector])
|
|
38
|
+
const portalProps = useDiagramState(selector)
|
|
42
39
|
return (
|
|
43
40
|
<div className={elementLink} data-likec4-linkid={id}>
|
|
44
41
|
<HoverCard
|
|
@@ -51,8 +48,7 @@ export function ElementLink({
|
|
|
51
48
|
transitionProps={{
|
|
52
49
|
transition: 'pop'
|
|
53
50
|
}}
|
|
54
|
-
|
|
55
|
-
withinPortal={false}
|
|
51
|
+
{...(portalProps ? { portalProps } : { withinPortal: false })}
|
|
56
52
|
floatingStrategy={'fixed'}
|
|
57
53
|
openDelay={350}
|
|
58
54
|
closeDelay={800}
|
|
@@ -64,7 +60,7 @@ export function ElementLink({
|
|
|
64
60
|
<UnstyledButton
|
|
65
61
|
className={clsx('nodrag nopan', trigger)}
|
|
66
62
|
autoFocus={false}>
|
|
67
|
-
<
|
|
63
|
+
<IconLink size={12} />
|
|
68
64
|
<span>links</span>
|
|
69
65
|
</UnstyledButton>
|
|
70
66
|
</HoverCardTarget>
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { DiagramNode } from '@likec4/core/types'
|
|
2
|
+
import { Text as MantineText } from '@mantine/core'
|
|
2
3
|
import { Handle, type NodeProps, Position } from '@xyflow/react'
|
|
3
4
|
import clsx from 'clsx'
|
|
4
|
-
import { DEV } from 'esm-env'
|
|
5
5
|
import { deepEqual as eq } from 'fast-equals'
|
|
6
6
|
import { m, type Variants } from 'framer-motion'
|
|
7
7
|
import { memo } from 'react'
|
|
8
|
-
import { isNumber } from 'remeda'
|
|
9
|
-
import
|
|
10
|
-
import { useDiagramState } from '../../../state'
|
|
8
|
+
import { isNumber, isTruthy } from 'remeda'
|
|
9
|
+
import { useDiagramState } from '../../../state/useDiagramStore'
|
|
11
10
|
import type { ElementXYFlowNode } from '../../types'
|
|
12
11
|
import { toDomPrecision } from '../../utils'
|
|
13
12
|
import { NavigateToBtn } from '../shared/NavigateToBtn'
|
|
14
13
|
import * as css from './element.css'
|
|
15
|
-
import { ElementIcon } from './ElementIcon'
|
|
16
14
|
import { ElementLink } from './ElementLink'
|
|
17
15
|
import { ElementShapeSvg, SelectedIndicator } from './ElementShapeSvg'
|
|
18
16
|
|
|
17
|
+
const Text = MantineText.withProps({
|
|
18
|
+
component: 'div'
|
|
19
|
+
})
|
|
20
|
+
|
|
19
21
|
type ElementNodeProps = NodeProps<ElementXYFlowNode>
|
|
20
22
|
|
|
21
23
|
const selectedScale = 1.015
|
|
@@ -69,6 +71,7 @@ const isEqualProps = (prev: ElementNodeProps, next: ElementNodeProps) => (
|
|
|
69
71
|
prev.id === next.id
|
|
70
72
|
&& eq(prev.selected ?? false, next.selected ?? false)
|
|
71
73
|
&& eq(prev.dragging ?? false, next.dragging ?? false)
|
|
74
|
+
&& eq(prev.draggable ?? false, next.draggable ?? false)
|
|
72
75
|
&& eq(prev.width ?? 0, next.width ?? 0)
|
|
73
76
|
&& eq(prev.height ?? 0, next.height ?? 0)
|
|
74
77
|
&& eq(prev.data.element, next.data.element)
|
|
@@ -80,11 +83,11 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
80
83
|
element
|
|
81
84
|
},
|
|
82
85
|
dragging,
|
|
86
|
+
// draggable = false,
|
|
83
87
|
selected = false,
|
|
84
88
|
width,
|
|
85
89
|
height
|
|
86
90
|
}) {
|
|
87
|
-
DEV && useTilg()
|
|
88
91
|
const { isHovered, isDimmed, hasOnNavigateTo, isHovercards, isInteractive } = useDiagramState(s => ({
|
|
89
92
|
isHovered: s.hoveredNodeId === id,
|
|
90
93
|
isDimmed: s.dimmed.has(id),
|
|
@@ -120,124 +123,125 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
120
123
|
// useTilg(animate, isHovered, isInteractive)
|
|
121
124
|
|
|
122
125
|
return (
|
|
123
|
-
|
|
124
|
-
id={id}
|
|
125
|
-
className={clsx([
|
|
126
|
-
css.container,
|
|
127
|
-
isDimmed && css.dimmed,
|
|
128
|
-
animate !== 'idle' && css.containerAnimated,
|
|
129
|
-
'likec4-element-node'
|
|
130
|
-
])}
|
|
131
|
-
data-hovered={!dragging && isHovered}
|
|
132
|
-
data-likec4-color={element.color}
|
|
133
|
-
data-likec4-shape={element.shape}
|
|
134
|
-
variants={variants}
|
|
135
|
-
initial={false}
|
|
136
|
-
animate={animate}
|
|
137
|
-
{...(isInteractive && {
|
|
138
|
-
whileTap: dragging ? animate : 'tap'
|
|
139
|
-
})}
|
|
140
|
-
>
|
|
141
|
-
{
|
|
142
|
-
/* <NodeToolbar
|
|
143
|
-
position={Position.Bottom}
|
|
144
|
-
align={'start'}
|
|
145
|
-
offset={-10}
|
|
146
|
-
isVisible={selected || isHovered}>
|
|
147
|
-
<Button>editggg</Button>
|
|
148
|
-
</NodeToolbar> */
|
|
149
|
-
}
|
|
150
|
-
{
|
|
151
|
-
/* <NodeResizer minWidth={100} minHeight={30} />
|
|
152
|
-
<NodeToolbar
|
|
153
|
-
position={Position.Right}
|
|
154
|
-
align={'start'}
|
|
155
|
-
style={{
|
|
156
|
-
background: 'blue'
|
|
157
|
-
}}>
|
|
158
|
-
<Stack>
|
|
159
|
-
<Button>edit</Button>
|
|
160
|
-
|
|
161
|
-
</Stack>
|
|
162
|
-
</NodeToolbar>*/
|
|
163
|
-
}
|
|
164
|
-
{
|
|
165
|
-
/* {element.inEdges.map((edge) => (
|
|
166
|
-
<Handle
|
|
167
|
-
key={edge}
|
|
168
|
-
id={edge}
|
|
169
|
-
type="target"
|
|
170
|
-
position={Position.Top}
|
|
171
|
-
style={{ visibility: 'hidden' }}
|
|
172
|
-
/>
|
|
173
|
-
))} */
|
|
174
|
-
}
|
|
126
|
+
<>
|
|
175
127
|
<Handle
|
|
176
128
|
type="target"
|
|
177
129
|
position={Position.Top}
|
|
178
|
-
|
|
130
|
+
className={css.handleCenter}
|
|
179
131
|
/>
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
132
|
+
<m.div
|
|
133
|
+
id={id}
|
|
134
|
+
className={clsx([
|
|
135
|
+
css.container,
|
|
136
|
+
isDimmed && css.dimmed,
|
|
137
|
+
animate !== 'idle' && css.containerAnimated,
|
|
138
|
+
'likec4-element-node'
|
|
139
|
+
])}
|
|
140
|
+
data-hovered={!dragging && isHovered}
|
|
141
|
+
data-likec4-color={element.color}
|
|
142
|
+
data-likec4-shape={element.shape}
|
|
143
|
+
variants={variants}
|
|
144
|
+
initial={false}
|
|
145
|
+
animate={animate}
|
|
146
|
+
{...(isInteractive && {
|
|
147
|
+
whileTap: dragging ? animate : 'tap'
|
|
148
|
+
})}
|
|
187
149
|
>
|
|
188
|
-
|
|
189
|
-
<
|
|
150
|
+
{
|
|
151
|
+
/* <svg
|
|
152
|
+
className={clsx(
|
|
153
|
+
css.cssShapeSvgMultiple
|
|
154
|
+
)}
|
|
155
|
+
viewBox={`0 0 ${w} ${h}`}
|
|
156
|
+
width={w}
|
|
157
|
+
height={h}
|
|
158
|
+
>
|
|
159
|
+
<ElementShapeSvg
|
|
190
160
|
shape={element.shape}
|
|
191
161
|
w={w}
|
|
192
162
|
h={h}
|
|
193
163
|
/>
|
|
194
|
-
</
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
{element.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
164
|
+
</svg> */
|
|
165
|
+
}
|
|
166
|
+
<svg
|
|
167
|
+
className={clsx(
|
|
168
|
+
css.cssShapeSvg
|
|
169
|
+
)}
|
|
170
|
+
viewBox={`0 0 ${w} ${h}`}
|
|
171
|
+
width={w}
|
|
172
|
+
height={h}
|
|
173
|
+
>
|
|
174
|
+
<g className={css.indicator}>
|
|
175
|
+
<SelectedIndicator
|
|
176
|
+
shape={element.shape}
|
|
177
|
+
w={w}
|
|
178
|
+
h={h}
|
|
179
|
+
/>
|
|
180
|
+
</g>
|
|
181
|
+
<ElementShapeSvg
|
|
182
|
+
shape={element.shape}
|
|
183
|
+
w={w}
|
|
184
|
+
h={h}
|
|
185
|
+
/>
|
|
186
|
+
</svg>
|
|
187
|
+
<div
|
|
188
|
+
className={clsx(
|
|
189
|
+
css.elementDataContainer,
|
|
190
|
+
isTruthy(element.icon) && css.hasIcon,
|
|
191
|
+
'likec4-element'
|
|
192
|
+
)}
|
|
193
|
+
>
|
|
194
|
+
{isTruthy(element.icon) && <ElementIcon node={element} />}
|
|
195
|
+
<div className={clsx(css.elementTextData, 'likec4-element-main-props')}>
|
|
196
|
+
<Text
|
|
197
|
+
className={clsx(css.title, 'likec4-element-title')}>
|
|
198
|
+
{element.title}
|
|
199
|
+
</Text>
|
|
200
|
+
{element.technology && (
|
|
201
|
+
<Text
|
|
202
|
+
className={clsx(css.technology, 'likec4-element-technology')}>
|
|
203
|
+
{element.technology}
|
|
204
|
+
</Text>
|
|
205
|
+
)}
|
|
206
|
+
{element.description && (
|
|
207
|
+
<Text
|
|
208
|
+
className={clsx(css.description, 'likec4-element-description')}>
|
|
209
|
+
{element.description}
|
|
210
|
+
</Text>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
{isHovercards && element.links && <ElementLink element={element} />}
|
|
215
|
+
{isNavigable && <NavigateToBtn xynodeId={id} className={css.cssNavigateBtn} />}
|
|
216
|
+
</m.div>
|
|
223
217
|
<Handle
|
|
224
218
|
type="source"
|
|
225
|
-
position={Position.
|
|
226
|
-
|
|
219
|
+
position={Position.Top}
|
|
220
|
+
className={css.handleCenter}
|
|
227
221
|
/>
|
|
228
|
-
|
|
229
|
-
/* {element.outEdges.map((edge) => (
|
|
230
|
-
<Handle
|
|
231
|
-
key={edge}
|
|
232
|
-
id={edge}
|
|
233
|
-
type="source"
|
|
234
|
-
position={Position.Bottom}
|
|
235
|
-
style={{ visibility: 'hidden' }}
|
|
236
|
-
/>
|
|
237
|
-
))} */
|
|
238
|
-
}
|
|
239
|
-
{isHovercards && element.links && <ElementLink element={element} />}
|
|
240
|
-
{isNavigable && <NavigateToBtn xynodeId={id} className={css.cssNavigateBtn} />}
|
|
241
|
-
</m.div>
|
|
222
|
+
</>
|
|
242
223
|
)
|
|
243
224
|
}, isEqualProps)
|
|
225
|
+
|
|
226
|
+
const ElementIcon = ({ node }: { node: DiagramNode }) => {
|
|
227
|
+
const RenderIcon = useDiagramState(s => s.renderIcon)
|
|
228
|
+
if (!node.icon) {
|
|
229
|
+
return null
|
|
230
|
+
}
|
|
231
|
+
if (node.icon.startsWith('http://') || node.icon.startsWith('https://')) {
|
|
232
|
+
return (
|
|
233
|
+
<div className={clsx(css.elementIcon, 'likec4-element-icon')}>
|
|
234
|
+
<img src={node.icon} alt={node.title} />
|
|
235
|
+
</div>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
const icon = RenderIcon ? <RenderIcon node={node} /> : null
|
|
239
|
+
if (!icon) {
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
return (
|
|
243
|
+
<div className={clsx(css.elementIcon, 'likec4-element-icon')}>
|
|
244
|
+
{icon}
|
|
245
|
+
</div>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
@@ -2,7 +2,7 @@ import { type ElementShape, nonexhaustive } from '@likec4/core'
|
|
|
2
2
|
import { toDomPrecision } from '../../utils'
|
|
3
3
|
import { fillElementFill, fillMixStroke } from './element.css'
|
|
4
4
|
|
|
5
|
-
function cylinderSVGPath(diameter: number, height: number, tilt = 0.
|
|
5
|
+
export function cylinderSVGPath(diameter: number, height: number, tilt = 0.065) {
|
|
6
6
|
const radius = Math.round(diameter / 2)
|
|
7
7
|
// const tiltAdjustedHeight = height * Math.cos((tilt * Math.PI) / 2)
|
|
8
8
|
const rx = radius
|
|
@@ -25,7 +25,7 @@ function cylinderSVGPath(diameter: number, height: number, tilt = 0.0725) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function queueSVGPath(width: number, height: number, tilt = 0.
|
|
28
|
+
function queueSVGPath(width: number, height: number, tilt = 0.185) {
|
|
29
29
|
const diameter = height
|
|
30
30
|
const radius = Math.round(diameter / 2)
|
|
31
31
|
const ry = radius
|