@likec4/diagram 1.16.0 → 1.17.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 +22 -18
- package/src/LikeC4CustomColors.tsx +24 -21
- package/src/LikeC4Diagram.props.ts +15 -9
- package/src/LikeC4Diagram.tsx +33 -27
- package/src/LikeC4Search.tsx +146 -0
- package/src/StaticLikeC4Diagram.tsx +10 -5
- package/src/overlays/OverlayContext.tsx +1 -1
- package/src/overlays/Overlays.css.ts +28 -23
- package/src/overlays/Overlays.tsx +116 -64
- package/src/overlays/edge-details/xyflow/ElementNode.tsx +15 -12
- package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +7 -3
- package/src/overlays/edge-details/xyflow/styles.css.ts +22 -16
- package/src/overlays/element-details/ElementDetailsCard.css.ts +322 -0
- package/src/overlays/element-details/ElementDetailsCard.tsx +639 -0
- package/src/overlays/element-details/TabPanelRelationships.css.ts +106 -0
- package/src/overlays/element-details/TabPanelRelationships.tsx +201 -0
- package/src/overlays/element-details/TabPanelStructure.css.ts +36 -0
- package/src/overlays/element-details/TabPanelStructure.tsx +120 -0
- package/src/overlays/relationships-of/RelationshipsOverlay.tsx +185 -0
- package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +179 -213
- package/src/overlays/relationships-of/_types.ts +15 -4
- package/src/overlays/relationships-of/use-layouted-relationships.ts +301 -207
- package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +68 -14
- package/src/overlays/relationships-of/xyflow/ElementNode.tsx +54 -14
- package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +1 -1
- package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +51 -23
- package/src/overlays/relationships-of/xyflow/styles.css.ts +34 -27
- package/src/state/DiagramContext.tsx +15 -8
- package/src/state/aligners.ts +473 -0
- package/src/state/diagramStore.layout.ts +67 -0
- package/src/state/diagramStore.ts +18 -73
- package/src/theme-vars.ts +9 -0
- package/src/ui/FramerMotionConfig.tsx +18 -0
- package/src/ui/notation/NotationPanel.tsx +3 -3
- package/src/ui/top-left/ManualLayoutToolsButton.tsx +15 -0
- package/src/ui/top-left/TopLeftPanel.tsx +14 -1
- package/src/xyflow/XYFlow.tsx +18 -35
- package/src/xyflow/XYFlowInner.tsx +2 -2
- package/src/xyflow/edges/EdgeLabel.tsx +3 -3
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +25 -18
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +96 -30
- package/src/xyflow/nodes/compound/CompoundNode.tsx +149 -165
- package/src/xyflow/nodes/element/ElementDetails.css.ts +101 -0
- package/src/xyflow/nodes/element/ElementDetails.tsx +393 -0
- package/src/xyflow/nodes/element/ElementNode.tsx +165 -133
- package/src/xyflow/nodes/element/element.css.ts +46 -17
- package/src/xyflow/nodes/shared/ElementIcon.tsx +43 -0
- package/src/xyflow/nodes/shared/Toolbar.tsx +6 -6
- package/src/xyflow/{XYFlowEvents.tsx → useXYFlowEvents.ts} +26 -22
- package/src/xyflow/utils.ts +12 -3
- package/src/LikeC4CustomColors.props.ts +0 -5
- /package/src/{mantine → ui}/EnsureMantine.tsx +0 -0
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { ActionIcon, Text as MantineText, Tooltip } from '@mantine/core'
|
|
1
|
+
import { type ThemeColor } from '@likec4/core'
|
|
2
|
+
import { ActionIcon, Box, Group, Text as MantineText, Tooltip } from '@mantine/core'
|
|
3
3
|
import { useDebouncedValue } from '@mantine/hooks'
|
|
4
|
-
import { IconTransform, IconZoomScan } from '@tabler/icons-react'
|
|
4
|
+
import { IconId, IconTransform, IconZoomScan } from '@tabler/icons-react'
|
|
5
5
|
import { Handle, type NodeProps, Position } from '@xyflow/react'
|
|
6
6
|
import clsx from 'clsx'
|
|
7
7
|
import { deepEqual as eq } from 'fast-equals'
|
|
8
8
|
import { m, type Variants } from 'framer-motion'
|
|
9
|
-
import { memo, useCallback, useState } from 'react'
|
|
9
|
+
import React, { memo, useCallback, useState } from 'react'
|
|
10
10
|
import { isNumber, isTruthy } from 'remeda'
|
|
11
11
|
import { useDiagramState } from '../../../hooks/useDiagramState'
|
|
12
|
-
import type { ElementIconRenderer } from '../../../LikeC4Diagram.props'
|
|
13
12
|
import type { ElementXYFlowNode } from '../../types'
|
|
14
13
|
import { stopPropagation, toDomPrecision } from '../../utils'
|
|
14
|
+
import { ElementIcon } from '../shared/ElementIcon'
|
|
15
15
|
import { ElementToolbar } from '../shared/Toolbar'
|
|
16
16
|
import { useFramerAnimateVariants } from '../use-animate-variants'
|
|
17
17
|
import * as css from './element.css'
|
|
18
|
-
import { ElementLink } from './ElementLink'
|
|
19
18
|
import { ElementShapeSvg, SelectedIndicator } from './ElementShapeSvg'
|
|
20
19
|
|
|
21
20
|
const Text = MantineText.withProps({
|
|
@@ -32,9 +31,9 @@ const VariantsRoot = {
|
|
|
32
31
|
transition: isNumber(scale) && scale > selectedScale
|
|
33
32
|
? {
|
|
34
33
|
delay: 0.1,
|
|
35
|
-
delayChildren: 0.06
|
|
36
|
-
staggerChildren: 0.07,
|
|
37
|
-
staggerDirection: -1
|
|
34
|
+
delayChildren: 0.06
|
|
35
|
+
// staggerChildren: 0.07,
|
|
36
|
+
// staggerDirection: -1
|
|
38
37
|
}
|
|
39
38
|
: {}
|
|
40
39
|
}),
|
|
@@ -46,8 +45,7 @@ const VariantsRoot = {
|
|
|
46
45
|
transition: !isNumber(scale) || (scale >= 1 && scale < 1.06)
|
|
47
46
|
? {
|
|
48
47
|
// delay: 0.09,
|
|
49
|
-
delayChildren: 0.
|
|
50
|
-
staggerChildren: 0.1
|
|
48
|
+
delayChildren: 0.08
|
|
51
49
|
}
|
|
52
50
|
: {}
|
|
53
51
|
}),
|
|
@@ -56,21 +54,24 @@ const VariantsRoot = {
|
|
|
56
54
|
}
|
|
57
55
|
} satisfies Variants
|
|
58
56
|
|
|
59
|
-
const
|
|
57
|
+
const VariantsNavigateBtn = {
|
|
60
58
|
idle: {
|
|
61
59
|
'--ai-bg': 'var(--ai-bg-idle)',
|
|
62
60
|
scale: 1,
|
|
63
|
-
opacity: 0.
|
|
64
|
-
|
|
65
|
-
originY: 0.1
|
|
61
|
+
opacity: 0.5,
|
|
62
|
+
originY: 0.5
|
|
66
63
|
},
|
|
67
64
|
selected: {},
|
|
68
65
|
hovered: {
|
|
69
|
-
display: 'block',
|
|
70
66
|
'--ai-bg': 'var(--ai-bg-hover)',
|
|
71
67
|
scale: 1.35,
|
|
72
68
|
opacity: 1
|
|
73
|
-
}
|
|
69
|
+
}
|
|
70
|
+
} satisfies Variants
|
|
71
|
+
VariantsNavigateBtn['selected'] = VariantsNavigateBtn['hovered']
|
|
72
|
+
|
|
73
|
+
const VariantsNavigate = {
|
|
74
|
+
...VariantsNavigateBtn,
|
|
74
75
|
'hovered:navigate': {
|
|
75
76
|
scale: 1.42
|
|
76
77
|
},
|
|
@@ -78,54 +79,37 @@ const VariantsNavigate = {
|
|
|
78
79
|
scale: 1.15
|
|
79
80
|
}
|
|
80
81
|
} satisfies Variants
|
|
81
|
-
VariantsNavigate['selected'] = VariantsNavigate['hovered']
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
const VariantRealationships = {
|
|
84
|
+
...VariantsNavigateBtn,
|
|
85
|
+
'hovered:relationships': {
|
|
86
|
+
scale: 1.42
|
|
87
|
+
},
|
|
88
|
+
'tap:relationships': {
|
|
89
|
+
scale: 1.15
|
|
90
|
+
}
|
|
91
|
+
} satisfies Variants
|
|
92
|
+
|
|
93
|
+
const VariantsDetailsBtn = {
|
|
87
94
|
idle: {
|
|
88
95
|
'--ai-bg': 'var(--ai-bg-idle)',
|
|
89
96
|
scale: 1,
|
|
90
|
-
opacity: 0
|
|
91
|
-
originX: 0.25,
|
|
92
|
-
originY: 0.1,
|
|
93
|
-
translateX: -8,
|
|
94
|
-
transitionEnd: {
|
|
95
|
-
display: 'none'
|
|
96
|
-
}
|
|
97
|
+
opacity: 0.5
|
|
97
98
|
},
|
|
98
99
|
selected: {},
|
|
99
100
|
hovered: {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
scale: 1.25,
|
|
103
|
-
opacity: 1,
|
|
104
|
-
translateX: 0
|
|
101
|
+
scale: 1.2,
|
|
102
|
+
opacity: 0.7
|
|
105
103
|
},
|
|
106
|
-
'hovered:
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
'hovered:details': {
|
|
105
|
+
scale: 1.42,
|
|
106
|
+
opacity: 1
|
|
109
107
|
},
|
|
110
|
-
'tap:
|
|
108
|
+
'tap:details': {
|
|
111
109
|
scale: 1.15
|
|
112
110
|
}
|
|
113
111
|
} satisfies Variants
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Variants for the relationships button (when Navigate button is not visible)
|
|
118
|
-
*/
|
|
119
|
-
const VariantsRelationsBtnSingle = {
|
|
120
|
-
...VariantsNavigate,
|
|
121
|
-
idle: {
|
|
122
|
-
...VariantsNavigate.idle,
|
|
123
|
-
opacity: 0,
|
|
124
|
-
transitionEnd: {
|
|
125
|
-
display: 'none'
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
112
|
+
VariantsDetailsBtn['selected'] = VariantsDetailsBtn['hovered']
|
|
129
113
|
|
|
130
114
|
type ElementNodeProps = NodeProps<ElementXYFlowNode>
|
|
131
115
|
const isEqualProps = (prev: ElementNodeProps, next: ElementNodeProps) => (
|
|
@@ -148,33 +132,36 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
148
132
|
height
|
|
149
133
|
}) {
|
|
150
134
|
const {
|
|
135
|
+
viewId,
|
|
151
136
|
isEditable,
|
|
152
137
|
isHovered,
|
|
153
138
|
isDimmed,
|
|
154
|
-
|
|
155
|
-
isHovercards,
|
|
139
|
+
isNavigable,
|
|
156
140
|
isInteractive,
|
|
157
|
-
|
|
141
|
+
enableElementDetails,
|
|
142
|
+
enableRelationshipBrowser,
|
|
158
143
|
triggerOnNavigateTo,
|
|
159
144
|
openOverlay,
|
|
145
|
+
isInActiveOverlay,
|
|
160
146
|
renderIcon
|
|
161
147
|
} = useDiagramState(s => ({
|
|
148
|
+
viewId: s.view.id,
|
|
162
149
|
isEditable: s.readonly !== true,
|
|
163
150
|
isHovered: s.hoveredNodeId === id,
|
|
164
151
|
isDimmed: s.dimmed.has(id),
|
|
165
|
-
isInteractive: s.nodesDraggable || s.nodesSelectable || s.
|
|
152
|
+
isInteractive: s.nodesDraggable || s.nodesSelectable || s.enableElementDetails || s.enableRelationshipBrowser
|
|
166
153
|
|| (!!s.onNavigateTo && !!element.navigateTo),
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
154
|
+
isNavigable: (!!s.onNavigateTo && !!element.navigateTo),
|
|
155
|
+
enableElementDetails: s.enableElementDetails,
|
|
156
|
+
enableRelationshipBrowser: s.enableRelationshipBrowser,
|
|
170
157
|
triggerOnNavigateTo: s.triggerOnNavigateTo,
|
|
171
158
|
openOverlay: s.openOverlay,
|
|
159
|
+
isInActiveOverlay: (s.activeOverlay?.elementDetails ?? s.activeOverlay?.relationshipsOf) === id,
|
|
172
160
|
renderIcon: s.renderIcon
|
|
173
161
|
}))
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
const _isToolbarVisible = isEditable && (isHovered || (import.meta.env.DEV && selected))
|
|
162
|
+
// For development purposes, show the toolbar when the element is selected
|
|
163
|
+
const isHoveredOrSelected = isHovered || (import.meta.env.DEV && selected)
|
|
164
|
+
const _isToolbarVisible = isEditable && isHoveredOrSelected
|
|
178
165
|
const [isToolbarVisible] = useDebouncedValue(_isToolbarVisible, _isToolbarVisible ? 500 : 300)
|
|
179
166
|
|
|
180
167
|
const w = toDomPrecision(width ?? element.width)
|
|
@@ -184,6 +171,9 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
184
171
|
|
|
185
172
|
let animate: keyof typeof VariantsRoot
|
|
186
173
|
switch (true) {
|
|
174
|
+
case isInActiveOverlay:
|
|
175
|
+
animate = 'idle'
|
|
176
|
+
break
|
|
187
177
|
case dragging && selected:
|
|
188
178
|
animate = 'selected'
|
|
189
179
|
break
|
|
@@ -202,6 +192,8 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
202
192
|
|
|
203
193
|
const elementIcon = ElementIcon({
|
|
204
194
|
element,
|
|
195
|
+
viewId,
|
|
196
|
+
className: css.elementIcon,
|
|
205
197
|
renderIcon
|
|
206
198
|
})
|
|
207
199
|
|
|
@@ -212,7 +204,12 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
212
204
|
triggerOnNavigateTo(id, e)
|
|
213
205
|
}, [triggerOnNavigateTo, id])
|
|
214
206
|
|
|
215
|
-
const
|
|
207
|
+
const onOpenDetails = useCallback((e: React.MouseEvent) => {
|
|
208
|
+
e.stopPropagation()
|
|
209
|
+
openOverlay({ elementDetails: element.id })
|
|
210
|
+
}, [openOverlay, element.id])
|
|
211
|
+
|
|
212
|
+
const onOpenRelationships = useCallback((e: React.MouseEvent) => {
|
|
216
213
|
e.stopPropagation()
|
|
217
214
|
openOverlay({ relationshipsOf: element.id })
|
|
218
215
|
}, [openOverlay, element.id])
|
|
@@ -226,21 +223,23 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
226
223
|
onColorPreview={setPreviewColor}
|
|
227
224
|
/>
|
|
228
225
|
)}
|
|
229
|
-
<
|
|
230
|
-
|
|
226
|
+
<Box
|
|
227
|
+
component={m.div}
|
|
231
228
|
className={clsx([
|
|
232
229
|
css.container,
|
|
233
230
|
isDimmed && css.dimmed,
|
|
234
231
|
animate !== 'idle' && css.containerAnimated,
|
|
235
232
|
'likec4-element-node'
|
|
236
233
|
])}
|
|
234
|
+
key={`${viewId}:element:${id}`}
|
|
235
|
+
layoutId={`${viewId}:element:${id}`}
|
|
237
236
|
data-hovered={!dragging && isHovered}
|
|
238
237
|
data-likec4-color={previewColor ?? element.color}
|
|
239
238
|
data-likec4-shape={element.shape}
|
|
240
239
|
data-animate-target=""
|
|
241
|
-
variants={VariantsRoot}
|
|
242
240
|
initial={false}
|
|
243
|
-
|
|
241
|
+
variants={VariantsRoot}
|
|
242
|
+
animate={(isHovered && !dragging && !isInActiveOverlay) ? (animateVariants ?? animate) : animate}
|
|
244
243
|
tabIndex={-1}
|
|
245
244
|
{...(isInteractive && {
|
|
246
245
|
onTapStart: animateHandlers.onTapStart,
|
|
@@ -248,13 +247,15 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
248
247
|
onTapCancel: animateHandlers.onTapCancel
|
|
249
248
|
})}
|
|
250
249
|
>
|
|
251
|
-
<svg
|
|
250
|
+
<svg
|
|
251
|
+
className={clsx(css.shapeSvg)}
|
|
252
|
+
viewBox={`0 0 ${w} ${h}`}>
|
|
252
253
|
<g className={css.indicator}>
|
|
253
254
|
<SelectedIndicator shape={element.shape} w={w} h={h} />
|
|
254
255
|
</g>
|
|
255
256
|
<ElementShapeSvg shape={element.shape} w={w} h={h} />
|
|
256
257
|
</svg>
|
|
257
|
-
<
|
|
258
|
+
<Box
|
|
258
259
|
className={clsx(
|
|
259
260
|
css.elementDataContainer,
|
|
260
261
|
isTruthy(elementIcon) && css.hasIcon,
|
|
@@ -262,10 +263,15 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
262
263
|
)}
|
|
263
264
|
>
|
|
264
265
|
{elementIcon}
|
|
265
|
-
<
|
|
266
|
-
<Text
|
|
266
|
+
<Box className={clsx(css.elementTextData, 'likec4-element-main-props')}>
|
|
267
|
+
<Text
|
|
268
|
+
component={m.div}
|
|
269
|
+
key={`${viewId}:element:title:${id}`}
|
|
270
|
+
layoutId={`${viewId}:element:title:${id}`}
|
|
271
|
+
className={clsx(css.title, 'likec4-element-title')}>
|
|
267
272
|
{element.title}
|
|
268
273
|
</Text>
|
|
274
|
+
|
|
269
275
|
{element.technology && (
|
|
270
276
|
<Text className={clsx(css.technology, 'likec4-element-technology')}>
|
|
271
277
|
{element.technology}
|
|
@@ -276,80 +282,106 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
276
282
|
{element.description}
|
|
277
283
|
</Text>
|
|
278
284
|
)}
|
|
279
|
-
</
|
|
280
|
-
</
|
|
281
|
-
{isHovercards && element.links && <ElementLink element={element} />}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
285
|
+
</Box>
|
|
286
|
+
</Box>
|
|
287
|
+
{/* {isHovercards && element.links && <ElementLink element={element} />} */}
|
|
288
|
+
|
|
289
|
+
{(enableRelationshipBrowser || isNavigable) && (
|
|
290
|
+
<Group className={css.navigateBtnBox} align="center">
|
|
291
|
+
{enableRelationshipBrowser && (
|
|
292
|
+
<ActionIcon
|
|
293
|
+
component={m.div}
|
|
294
|
+
variants={VariantRealationships}
|
|
295
|
+
data-animate-target="relationships"
|
|
296
|
+
className={clsx('nodrag nopan', css.navigateBtn)}
|
|
297
|
+
radius="md"
|
|
298
|
+
role="button"
|
|
299
|
+
onClick={onOpenRelationships}
|
|
300
|
+
onDoubleClick={stopPropagation}
|
|
301
|
+
{...isInteractive && animateHandlers}
|
|
302
|
+
>
|
|
303
|
+
<IconTransform style={{ width: '75%' }} />
|
|
304
|
+
</ActionIcon>
|
|
305
|
+
)}
|
|
306
|
+
{isNavigable && (
|
|
307
|
+
<ActionIcon
|
|
308
|
+
component={m.div}
|
|
309
|
+
variants={VariantsNavigate}
|
|
310
|
+
data-animate-target="navigate"
|
|
311
|
+
className={clsx('nodrag nopan', css.navigateBtn)}
|
|
312
|
+
radius="md"
|
|
313
|
+
role="button"
|
|
314
|
+
onClick={onNavigateTo}
|
|
315
|
+
onDoubleClick={stopPropagation}
|
|
316
|
+
{...isInteractive && animateHandlers}
|
|
317
|
+
>
|
|
318
|
+
<IconZoomScan style={{ width: '75%' }} />
|
|
319
|
+
</ActionIcon>
|
|
320
|
+
)}
|
|
321
|
+
</Group>
|
|
297
322
|
)}
|
|
298
|
-
{
|
|
299
|
-
<Tooltip
|
|
323
|
+
{enableElementDetails && (
|
|
324
|
+
<Tooltip
|
|
325
|
+
fz="xs"
|
|
326
|
+
color="dark"
|
|
327
|
+
label="Open details"
|
|
328
|
+
withinPortal={false}
|
|
329
|
+
offset={2}
|
|
330
|
+
openDelay={600}>
|
|
300
331
|
<ActionIcon
|
|
301
332
|
component={m.div}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
className={clsx('nodrag nopan', css.navigateBtn)}
|
|
333
|
+
variants={VariantsDetailsBtn}
|
|
334
|
+
data-animate-target="details"
|
|
335
|
+
className={clsx('nodrag nopan', css.detailsBtn)}
|
|
306
336
|
radius="md"
|
|
307
|
-
style={{
|
|
308
|
-
left: isNavigable ? 'calc(50% + 28px)' : '50%',
|
|
309
|
-
zIndex: 99
|
|
310
|
-
}}
|
|
337
|
+
style={{ zIndex: 100 }}
|
|
311
338
|
role="button"
|
|
312
|
-
onClick={
|
|
339
|
+
onClick={onOpenDetails}
|
|
313
340
|
onDoubleClick={stopPropagation}
|
|
314
341
|
{...isInteractive && animateHandlers}
|
|
315
342
|
>
|
|
316
|
-
<
|
|
343
|
+
<IconId stroke={1.8} style={{ width: '75%' }} />
|
|
317
344
|
</ActionIcon>
|
|
318
345
|
</Tooltip>
|
|
319
346
|
)}
|
|
320
|
-
</
|
|
347
|
+
</Box>
|
|
321
348
|
<Handle type="target" position={Position.Top} className={css.handleCenter} />
|
|
322
349
|
<Handle type="source" position={Position.Top} className={css.handleCenter} />
|
|
323
350
|
</>
|
|
324
351
|
)
|
|
325
352
|
}, isEqualProps)
|
|
326
353
|
|
|
327
|
-
const ElementIcon = (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
354
|
+
// const ElementIcon = (
|
|
355
|
+
// { element, viewId, renderIcon: RenderIcon }: {
|
|
356
|
+
// element: DiagramNode
|
|
357
|
+
// viewId: string
|
|
358
|
+
// renderIcon: ElementIconRenderer | null
|
|
359
|
+
// }
|
|
360
|
+
// ) => {
|
|
361
|
+
// if (!element.icon) {
|
|
362
|
+
// return null
|
|
363
|
+
// }
|
|
364
|
+
// let icon = null as React.ReactNode
|
|
365
|
+
// if (element.icon.startsWith('http://') || element.icon.startsWith('https://')) {
|
|
366
|
+
// icon = <img src={element.icon} alt={element.title} />
|
|
367
|
+
// } else if (RenderIcon) {
|
|
368
|
+
// icon = <RenderIcon node={element} />
|
|
369
|
+
// }
|
|
370
|
+
|
|
371
|
+
// if (!icon) {
|
|
372
|
+
// return null
|
|
373
|
+
// }
|
|
374
|
+
// return (
|
|
375
|
+
// <m.div
|
|
376
|
+
// // key={`${viewId}:element:icon:${element.id}`}
|
|
377
|
+
// layoutId={`${viewId}:element:icon:${element.id}`}
|
|
378
|
+
// className={clsx(
|
|
379
|
+
// css.elementIcon,
|
|
380
|
+
// 'likec4-element-icon'
|
|
381
|
+
// )}
|
|
382
|
+
// data-likec4-icon={element.icon}
|
|
383
|
+
// >
|
|
384
|
+
// {icon}
|
|
385
|
+
// </m.div>
|
|
386
|
+
// )
|
|
387
|
+
// }
|
|
@@ -5,11 +5,13 @@ import { mantine, vars } from '../../../theme-vars'
|
|
|
5
5
|
export const stokeFillMix = createVar('stroke-fill-mix')
|
|
6
6
|
|
|
7
7
|
export const container = style({
|
|
8
|
+
position: 'absolute',
|
|
9
|
+
top: 0,
|
|
10
|
+
left: 0,
|
|
8
11
|
width: '100%',
|
|
9
12
|
height: '100%',
|
|
10
13
|
padding: 0,
|
|
11
14
|
margin: 0,
|
|
12
|
-
flex: '1',
|
|
13
15
|
display: 'flex',
|
|
14
16
|
alignItems: 'center',
|
|
15
17
|
justifyContent: 'center',
|
|
@@ -24,6 +26,7 @@ export const container = style({
|
|
|
24
26
|
willChange: 'transform'
|
|
25
27
|
}
|
|
26
28
|
},
|
|
29
|
+
// Catch pointer below the element
|
|
27
30
|
':after': {
|
|
28
31
|
content: ' ',
|
|
29
32
|
position: 'absolute',
|
|
@@ -258,12 +261,7 @@ export const elementIcon = style({
|
|
|
258
261
|
alignItems: 'center',
|
|
259
262
|
justifyContent: 'center',
|
|
260
263
|
mixBlendMode: 'hard-light',
|
|
261
|
-
alignSelf: 'flex-start'
|
|
262
|
-
selectors: {
|
|
263
|
-
'&[data-likec4-icon^="azure:"]': {
|
|
264
|
-
mixBlendMode: 'normal'
|
|
265
|
-
}
|
|
266
|
-
}
|
|
264
|
+
alignSelf: 'flex-start'
|
|
267
265
|
})
|
|
268
266
|
globalStyle(`${elementIcon} svg, ${elementIcon} img`, {
|
|
269
267
|
width: '100%',
|
|
@@ -282,7 +280,7 @@ globalStyle(`${elementIcon} img`, {
|
|
|
282
280
|
|
|
283
281
|
const filterShadow = createVar('filter-shadow')
|
|
284
282
|
|
|
285
|
-
export const
|
|
283
|
+
export const shapeSvgMultiple = style({
|
|
286
284
|
top: 0,
|
|
287
285
|
left: 0,
|
|
288
286
|
position: 'absolute',
|
|
@@ -310,10 +308,12 @@ export const cssShapeSvgMultiple = style({
|
|
|
310
308
|
}
|
|
311
309
|
}
|
|
312
310
|
})
|
|
313
|
-
export const
|
|
311
|
+
export const shapeSvg = style({
|
|
314
312
|
top: 0,
|
|
315
313
|
left: 0,
|
|
316
314
|
position: 'absolute',
|
|
315
|
+
width: '100%',
|
|
316
|
+
height: '100%',
|
|
317
317
|
pointerEvents: 'none',
|
|
318
318
|
fill: vars.element.fill,
|
|
319
319
|
stroke: vars.element.stroke,
|
|
@@ -338,12 +338,8 @@ export const cssShapeSvg = style({
|
|
|
338
338
|
}
|
|
339
339
|
})
|
|
340
340
|
|
|
341
|
-
|
|
342
|
-
zIndex: 'calc(var(--layer-overlays, 1) + 1)',
|
|
343
|
-
position: 'absolute',
|
|
341
|
+
const btn = style({
|
|
344
342
|
pointerEvents: 'all',
|
|
345
|
-
left: '50%',
|
|
346
|
-
bottom: 0,
|
|
347
343
|
color: vars.element.loContrast,
|
|
348
344
|
cursor: 'pointer',
|
|
349
345
|
backgroundColor: 'var(--ai-bg)',
|
|
@@ -353,12 +349,45 @@ export const navigateBtn = style({
|
|
|
353
349
|
'--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill} 65%, ${vars.element.stroke})`,
|
|
354
350
|
'--ai-hover': `color-mix(in srgb , ${vars.element.fill} 50%, ${vars.element.stroke})`
|
|
355
351
|
},
|
|
352
|
+
':hover': {
|
|
353
|
+
boxShadow: mantine.shadows.md
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
export const navigateBtnBox = style({
|
|
358
|
+
zIndex: 100,
|
|
359
|
+
position: 'absolute',
|
|
360
|
+
left: '50%',
|
|
361
|
+
bottom: 2,
|
|
362
|
+
transform: 'translate(-50%, 0%)',
|
|
363
|
+
gap: 0,
|
|
356
364
|
selectors: {
|
|
357
365
|
[`:where([data-likec4-shape='browser']) &`]: {
|
|
358
366
|
bottom: 4
|
|
359
367
|
}
|
|
360
|
-
},
|
|
361
|
-
':hover': {
|
|
362
|
-
boxShadow: mantine.shadows.md
|
|
363
368
|
}
|
|
364
369
|
})
|
|
370
|
+
globalStyle(`:where(.react-flow__node:hover) ${navigateBtnBox}`, {
|
|
371
|
+
transitionDelay: '20ms',
|
|
372
|
+
gap: 16
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
export const navigateBtn = style([btn, {}])
|
|
376
|
+
|
|
377
|
+
export const detailsBtn = style([btn, {
|
|
378
|
+
position: 'absolute',
|
|
379
|
+
top: 2,
|
|
380
|
+
right: 2,
|
|
381
|
+
selectors: {
|
|
382
|
+
[`:where([data-likec4-shape='browser']) &`]: {
|
|
383
|
+
right: 5
|
|
384
|
+
},
|
|
385
|
+
':where([data-likec4-shape="cylinder"], [data-likec4-shape="storage"]) &': {
|
|
386
|
+
top: 14
|
|
387
|
+
},
|
|
388
|
+
':where([data-likec4-shape="queue"]) &': {
|
|
389
|
+
top: 1,
|
|
390
|
+
right: 12
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}])
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DiagramNode } from '@likec4/core'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
import { m } from 'framer-motion'
|
|
4
|
+
import type { ElementIconRenderer } from '../../../LikeC4Diagram.props'
|
|
5
|
+
|
|
6
|
+
export const ElementIcon = ({
|
|
7
|
+
element,
|
|
8
|
+
viewId,
|
|
9
|
+
className,
|
|
10
|
+
renderIcon: RenderIcon
|
|
11
|
+
}: {
|
|
12
|
+
element: DiagramNode
|
|
13
|
+
viewId: string
|
|
14
|
+
className: string
|
|
15
|
+
renderIcon: ElementIconRenderer | null
|
|
16
|
+
}) => {
|
|
17
|
+
if (!element.icon) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
let icon = null as React.ReactNode
|
|
21
|
+
if (element.icon.startsWith('http://') || element.icon.startsWith('https://')) {
|
|
22
|
+
icon = <img src={element.icon} alt={element.title} />
|
|
23
|
+
} else if (RenderIcon) {
|
|
24
|
+
icon = <RenderIcon node={element} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!icon) {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
return (
|
|
31
|
+
<m.div
|
|
32
|
+
key={`${viewId}:element:icon:${element.id}`}
|
|
33
|
+
layoutId={`${viewId}:element:icon:${element.id}`}
|
|
34
|
+
className={clsx(
|
|
35
|
+
className,
|
|
36
|
+
'likec4-element-icon'
|
|
37
|
+
)}
|
|
38
|
+
data-likec4-icon={element.icon}
|
|
39
|
+
>
|
|
40
|
+
{icon}
|
|
41
|
+
</m.div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -90,10 +90,10 @@ export function CompoundToolbar({
|
|
|
90
90
|
const diagramApi = useDiagramStoreApi()
|
|
91
91
|
const {
|
|
92
92
|
hasGoToSource,
|
|
93
|
-
|
|
93
|
+
enableRelationshipBrowser
|
|
94
94
|
} = useDiagramState(s => ({
|
|
95
95
|
hasGoToSource: !!s.onOpenSourceElement,
|
|
96
|
-
|
|
96
|
+
enableRelationshipBrowser: s.enableRelationshipBrowser
|
|
97
97
|
}))
|
|
98
98
|
|
|
99
99
|
const onChange: OnStyleChange = (style) => {
|
|
@@ -118,7 +118,7 @@ export function CompoundToolbar({
|
|
|
118
118
|
onChange={onChange}
|
|
119
119
|
/>
|
|
120
120
|
{hasGoToSource && <GoToSourceButton elementId={element.id} />}
|
|
121
|
-
{
|
|
121
|
+
{enableRelationshipBrowser && <BrowseRelationshipsButton elementId={element.id} />}
|
|
122
122
|
</Toolbar>
|
|
123
123
|
)
|
|
124
124
|
}
|
|
@@ -160,10 +160,10 @@ export function ElementToolbar({
|
|
|
160
160
|
const diagramApi = useDiagramStoreApi()
|
|
161
161
|
const {
|
|
162
162
|
hasGoToSource,
|
|
163
|
-
|
|
163
|
+
enableRelationshipBrowser
|
|
164
164
|
} = useDiagramState(s => ({
|
|
165
165
|
hasGoToSource: !!s.onOpenSourceElement,
|
|
166
|
-
|
|
166
|
+
enableRelationshipBrowser: s.enableRelationshipBrowser
|
|
167
167
|
}))
|
|
168
168
|
const portalProps = useMantinePortalProps()
|
|
169
169
|
|
|
@@ -235,7 +235,7 @@ export function ElementToolbar({
|
|
|
235
235
|
position="right-end"
|
|
236
236
|
/>
|
|
237
237
|
{hasGoToSource && <GoToSourceButton elementId={element.id} />}
|
|
238
|
-
{
|
|
238
|
+
{enableRelationshipBrowser && <BrowseRelationshipsButton elementId={element.id} />}
|
|
239
239
|
</Toolbar>
|
|
240
240
|
)
|
|
241
241
|
}
|