@likec4/diagram 1.15.1 → 1.17.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/package.json +7 -7
- package/src/LikeC4CustomColors.tsx +24 -21
- package/src/LikeC4Diagram.props.ts +9 -9
- package/src/LikeC4Diagram.tsx +25 -26
- package/src/StaticLikeC4Diagram.tsx +9 -3
- 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/diagramStore.ts +14 -6
- package/src/theme-vars.ts +9 -0
- package/src/ui/FramerMotionConfig.tsx +18 -0
- package/src/ui/notation/NotationPanel.tsx +3 -3
- 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 +110 -116
- package/src/xyflow/nodes/element/element.css.ts +38 -18
- 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, Text as MantineText, Tooltip } from '@mantine/core'
|
|
3
3
|
import { useDebouncedValue } from '@mantine/hooks'
|
|
4
|
-
import {
|
|
4
|
+
import { IconId, 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
|
}),
|
|
@@ -60,14 +58,14 @@ const VariantsNavigate = {
|
|
|
60
58
|
idle: {
|
|
61
59
|
'--ai-bg': 'var(--ai-bg-idle)',
|
|
62
60
|
scale: 1,
|
|
63
|
-
opacity: 0.
|
|
61
|
+
opacity: 0.5,
|
|
64
62
|
translateX: '-50%',
|
|
65
|
-
originY: 0.
|
|
63
|
+
originY: 0.2
|
|
66
64
|
},
|
|
67
65
|
selected: {},
|
|
68
66
|
hovered: {
|
|
69
|
-
display: 'block',
|
|
70
67
|
'--ai-bg': 'var(--ai-bg-hover)',
|
|
68
|
+
translateX: '-50%',
|
|
71
69
|
scale: 1.35,
|
|
72
70
|
opacity: 1
|
|
73
71
|
},
|
|
@@ -80,52 +78,26 @@ const VariantsNavigate = {
|
|
|
80
78
|
} satisfies Variants
|
|
81
79
|
VariantsNavigate['selected'] = VariantsNavigate['hovered']
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
* Variants for the relationships button (when Navigate button is visible)
|
|
85
|
-
*/
|
|
86
|
-
const VariantsRelationsBtn = {
|
|
81
|
+
const VariantsDetailsBtn = {
|
|
87
82
|
idle: {
|
|
88
83
|
'--ai-bg': 'var(--ai-bg-idle)',
|
|
89
84
|
scale: 1,
|
|
90
|
-
opacity: 0
|
|
91
|
-
originX: 0.25,
|
|
92
|
-
originY: 0.1,
|
|
93
|
-
translateX: -8,
|
|
94
|
-
transitionEnd: {
|
|
95
|
-
display: 'none'
|
|
96
|
-
}
|
|
85
|
+
opacity: 0.5
|
|
97
86
|
},
|
|
98
87
|
selected: {},
|
|
99
88
|
hovered: {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
scale: 1.25,
|
|
103
|
-
opacity: 1,
|
|
104
|
-
translateX: 0
|
|
89
|
+
scale: 1.2,
|
|
90
|
+
opacity: 0.7
|
|
105
91
|
},
|
|
106
|
-
'hovered:
|
|
107
|
-
|
|
108
|
-
|
|
92
|
+
'hovered:details': {
|
|
93
|
+
scale: 1.42,
|
|
94
|
+
opacity: 1
|
|
109
95
|
},
|
|
110
|
-
'tap:
|
|
96
|
+
'tap:details': {
|
|
111
97
|
scale: 1.15
|
|
112
98
|
}
|
|
113
99
|
} 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
|
-
}
|
|
100
|
+
VariantsDetailsBtn['selected'] = VariantsDetailsBtn['hovered']
|
|
129
101
|
|
|
130
102
|
type ElementNodeProps = NodeProps<ElementXYFlowNode>
|
|
131
103
|
const isEqualProps = (prev: ElementNodeProps, next: ElementNodeProps) => (
|
|
@@ -148,33 +120,34 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
148
120
|
height
|
|
149
121
|
}) {
|
|
150
122
|
const {
|
|
123
|
+
viewId,
|
|
151
124
|
isEditable,
|
|
152
125
|
isHovered,
|
|
153
126
|
isDimmed,
|
|
154
|
-
|
|
155
|
-
isHovercards,
|
|
127
|
+
isNavigable,
|
|
156
128
|
isInteractive,
|
|
157
|
-
|
|
129
|
+
enableElementDetails,
|
|
158
130
|
triggerOnNavigateTo,
|
|
159
131
|
openOverlay,
|
|
132
|
+
isInActiveOverlay,
|
|
160
133
|
renderIcon
|
|
161
134
|
} = useDiagramState(s => ({
|
|
135
|
+
viewId: s.view.id,
|
|
162
136
|
isEditable: s.readonly !== true,
|
|
163
137
|
isHovered: s.hoveredNodeId === id,
|
|
164
138
|
isDimmed: s.dimmed.has(id),
|
|
165
|
-
isInteractive: s.nodesDraggable || s.nodesSelectable || s.
|
|
139
|
+
isInteractive: s.nodesDraggable || s.nodesSelectable || s.enableElementDetails
|
|
166
140
|
|| (!!s.onNavigateTo && !!element.navigateTo),
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
enableRelationshipsMode: s.enableRelationshipsBrowser,
|
|
141
|
+
isNavigable: (!!s.onNavigateTo && !!element.navigateTo),
|
|
142
|
+
enableElementDetails: s.enableElementDetails,
|
|
170
143
|
triggerOnNavigateTo: s.triggerOnNavigateTo,
|
|
171
144
|
openOverlay: s.openOverlay,
|
|
145
|
+
isInActiveOverlay: (s.activeOverlay?.elementDetails ?? s.activeOverlay?.relationshipsOf) === id,
|
|
172
146
|
renderIcon: s.renderIcon
|
|
173
147
|
}))
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
const _isToolbarVisible = isEditable && (isHovered || (import.meta.env.DEV && selected))
|
|
148
|
+
// For development purposes, show the toolbar when the element is selected
|
|
149
|
+
const isHoveredOrSelected = isHovered || (import.meta.env.DEV && selected)
|
|
150
|
+
const _isToolbarVisible = isEditable && isHoveredOrSelected
|
|
178
151
|
const [isToolbarVisible] = useDebouncedValue(_isToolbarVisible, _isToolbarVisible ? 500 : 300)
|
|
179
152
|
|
|
180
153
|
const w = toDomPrecision(width ?? element.width)
|
|
@@ -184,6 +157,9 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
184
157
|
|
|
185
158
|
let animate: keyof typeof VariantsRoot
|
|
186
159
|
switch (true) {
|
|
160
|
+
case isInActiveOverlay:
|
|
161
|
+
animate = 'idle'
|
|
162
|
+
break
|
|
187
163
|
case dragging && selected:
|
|
188
164
|
animate = 'selected'
|
|
189
165
|
break
|
|
@@ -202,6 +178,8 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
202
178
|
|
|
203
179
|
const elementIcon = ElementIcon({
|
|
204
180
|
element,
|
|
181
|
+
viewId,
|
|
182
|
+
className: css.elementIcon,
|
|
205
183
|
renderIcon
|
|
206
184
|
})
|
|
207
185
|
|
|
@@ -212,9 +190,9 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
212
190
|
triggerOnNavigateTo(id, e)
|
|
213
191
|
}, [triggerOnNavigateTo, id])
|
|
214
192
|
|
|
215
|
-
const
|
|
193
|
+
const onOpenDetails = useCallback((e: React.MouseEvent) => {
|
|
216
194
|
e.stopPropagation()
|
|
217
|
-
openOverlay({
|
|
195
|
+
openOverlay({ elementDetails: element.id })
|
|
218
196
|
}, [openOverlay, element.id])
|
|
219
197
|
|
|
220
198
|
return (
|
|
@@ -226,21 +204,23 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
226
204
|
onColorPreview={setPreviewColor}
|
|
227
205
|
/>
|
|
228
206
|
)}
|
|
229
|
-
<
|
|
230
|
-
|
|
207
|
+
<Box
|
|
208
|
+
component={m.div}
|
|
231
209
|
className={clsx([
|
|
232
210
|
css.container,
|
|
233
211
|
isDimmed && css.dimmed,
|
|
234
212
|
animate !== 'idle' && css.containerAnimated,
|
|
235
213
|
'likec4-element-node'
|
|
236
214
|
])}
|
|
215
|
+
key={`${viewId}:element:${id}`}
|
|
216
|
+
layoutId={`${viewId}:element:${id}`}
|
|
237
217
|
data-hovered={!dragging && isHovered}
|
|
238
218
|
data-likec4-color={previewColor ?? element.color}
|
|
239
219
|
data-likec4-shape={element.shape}
|
|
240
220
|
data-animate-target=""
|
|
241
|
-
variants={VariantsRoot}
|
|
242
221
|
initial={false}
|
|
243
|
-
|
|
222
|
+
variants={VariantsRoot}
|
|
223
|
+
animate={(isHovered && !dragging && !isInActiveOverlay) ? (animateVariants ?? animate) : animate}
|
|
244
224
|
tabIndex={-1}
|
|
245
225
|
{...(isInteractive && {
|
|
246
226
|
onTapStart: animateHandlers.onTapStart,
|
|
@@ -248,13 +228,15 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
248
228
|
onTapCancel: animateHandlers.onTapCancel
|
|
249
229
|
})}
|
|
250
230
|
>
|
|
251
|
-
<svg
|
|
231
|
+
<svg
|
|
232
|
+
className={clsx(css.shapeSvg)}
|
|
233
|
+
viewBox={`0 0 ${w} ${h}`}>
|
|
252
234
|
<g className={css.indicator}>
|
|
253
235
|
<SelectedIndicator shape={element.shape} w={w} h={h} />
|
|
254
236
|
</g>
|
|
255
237
|
<ElementShapeSvg shape={element.shape} w={w} h={h} />
|
|
256
238
|
</svg>
|
|
257
|
-
<
|
|
239
|
+
<Box
|
|
258
240
|
className={clsx(
|
|
259
241
|
css.elementDataContainer,
|
|
260
242
|
isTruthy(elementIcon) && css.hasIcon,
|
|
@@ -262,10 +244,15 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
262
244
|
)}
|
|
263
245
|
>
|
|
264
246
|
{elementIcon}
|
|
265
|
-
<
|
|
266
|
-
<Text
|
|
247
|
+
<Box className={clsx(css.elementTextData, 'likec4-element-main-props')}>
|
|
248
|
+
<Text
|
|
249
|
+
component={m.div}
|
|
250
|
+
key={`${viewId}:element:title:${id}`}
|
|
251
|
+
layoutId={`${viewId}:element:title:${id}`}
|
|
252
|
+
className={clsx(css.title, 'likec4-element-title')}>
|
|
267
253
|
{element.title}
|
|
268
254
|
</Text>
|
|
255
|
+
|
|
269
256
|
{element.technology && (
|
|
270
257
|
<Text className={clsx(css.technology, 'likec4-element-technology')}>
|
|
271
258
|
{element.technology}
|
|
@@ -276,9 +263,9 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
276
263
|
{element.description}
|
|
277
264
|
</Text>
|
|
278
265
|
)}
|
|
279
|
-
</
|
|
280
|
-
</
|
|
281
|
-
{isHovercards && element.links && <ElementLink element={element} />}
|
|
266
|
+
</Box>
|
|
267
|
+
</Box>
|
|
268
|
+
{/* {isHovercards && element.links && <ElementLink element={element} />} */}
|
|
282
269
|
{isNavigable && (
|
|
283
270
|
<ActionIcon
|
|
284
271
|
component={m.div}
|
|
@@ -292,64 +279,71 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
292
279
|
onDoubleClick={stopPropagation}
|
|
293
280
|
{...isInteractive && animateHandlers}
|
|
294
281
|
>
|
|
295
|
-
<IconZoomScan
|
|
282
|
+
<IconZoomScan style={{ width: '75%' }} />
|
|
296
283
|
</ActionIcon>
|
|
297
284
|
)}
|
|
298
|
-
{
|
|
299
|
-
<Tooltip
|
|
285
|
+
{enableElementDetails && (
|
|
286
|
+
<Tooltip
|
|
287
|
+
fz="xs"
|
|
288
|
+
color="dark"
|
|
289
|
+
label="Open details"
|
|
290
|
+
withinPortal={false}
|
|
291
|
+
offset={2}
|
|
292
|
+
openDelay={600}>
|
|
300
293
|
<ActionIcon
|
|
301
294
|
component={m.div}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
className={clsx('nodrag nopan', css.navigateBtn)}
|
|
295
|
+
variants={VariantsDetailsBtn}
|
|
296
|
+
data-animate-target="details"
|
|
297
|
+
className={clsx('nodrag nopan', css.detailsBtn)}
|
|
306
298
|
radius="md"
|
|
307
|
-
style={{
|
|
308
|
-
left: isNavigable ? 'calc(50% + 28px)' : '50%',
|
|
309
|
-
zIndex: 99
|
|
310
|
-
}}
|
|
299
|
+
style={{ zIndex: 100 }}
|
|
311
300
|
role="button"
|
|
312
|
-
onClick={
|
|
301
|
+
onClick={onOpenDetails}
|
|
313
302
|
onDoubleClick={stopPropagation}
|
|
314
303
|
{...isInteractive && animateHandlers}
|
|
315
304
|
>
|
|
316
|
-
<
|
|
305
|
+
<IconId stroke={1.8} style={{ width: '75%' }} />
|
|
317
306
|
</ActionIcon>
|
|
318
307
|
</Tooltip>
|
|
319
308
|
)}
|
|
320
|
-
</
|
|
309
|
+
</Box>
|
|
321
310
|
<Handle type="target" position={Position.Top} className={css.handleCenter} />
|
|
322
311
|
<Handle type="source" position={Position.Top} className={css.handleCenter} />
|
|
323
312
|
</>
|
|
324
313
|
)
|
|
325
314
|
}, isEqualProps)
|
|
326
315
|
|
|
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
|
-
}
|
|
316
|
+
// const ElementIcon = (
|
|
317
|
+
// { element, viewId, renderIcon: RenderIcon }: {
|
|
318
|
+
// element: DiagramNode
|
|
319
|
+
// viewId: string
|
|
320
|
+
// renderIcon: ElementIconRenderer | null
|
|
321
|
+
// }
|
|
322
|
+
// ) => {
|
|
323
|
+
// if (!element.icon) {
|
|
324
|
+
// return null
|
|
325
|
+
// }
|
|
326
|
+
// let icon = null as React.ReactNode
|
|
327
|
+
// if (element.icon.startsWith('http://') || element.icon.startsWith('https://')) {
|
|
328
|
+
// icon = <img src={element.icon} alt={element.title} />
|
|
329
|
+
// } else if (RenderIcon) {
|
|
330
|
+
// icon = <RenderIcon node={element} />
|
|
331
|
+
// }
|
|
332
|
+
|
|
333
|
+
// if (!icon) {
|
|
334
|
+
// return null
|
|
335
|
+
// }
|
|
336
|
+
// return (
|
|
337
|
+
// <m.div
|
|
338
|
+
// // key={`${viewId}:element:icon:${element.id}`}
|
|
339
|
+
// layoutId={`${viewId}:element:icon:${element.id}`}
|
|
340
|
+
// className={clsx(
|
|
341
|
+
// css.elementIcon,
|
|
342
|
+
// 'likec4-element-icon'
|
|
343
|
+
// )}
|
|
344
|
+
// data-likec4-icon={element.icon}
|
|
345
|
+
// >
|
|
346
|
+
// {icon}
|
|
347
|
+
// </m.div>
|
|
348
|
+
// )
|
|
349
|
+
// }
|
|
@@ -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,36 @@ 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 navigateBtn = style([btn, {
|
|
358
|
+
position: 'absolute',
|
|
359
|
+
left: '50%',
|
|
360
|
+
bottom: 0,
|
|
356
361
|
selectors: {
|
|
357
362
|
[`:where([data-likec4-shape='browser']) &`]: {
|
|
358
363
|
bottom: 4
|
|
359
364
|
}
|
|
360
|
-
},
|
|
361
|
-
':hover': {
|
|
362
|
-
boxShadow: mantine.shadows.md
|
|
363
365
|
}
|
|
364
|
-
})
|
|
366
|
+
}])
|
|
367
|
+
|
|
368
|
+
export const detailsBtn = style([btn, {
|
|
369
|
+
position: 'absolute',
|
|
370
|
+
top: 2,
|
|
371
|
+
right: 2,
|
|
372
|
+
selectors: {
|
|
373
|
+
[`:where([data-likec4-shape='browser']) &`]: {
|
|
374
|
+
right: 5
|
|
375
|
+
},
|
|
376
|
+
':where([data-likec4-shape="cylinder"], [data-likec4-shape="storage"]) &': {
|
|
377
|
+
top: 14
|
|
378
|
+
},
|
|
379
|
+
':where([data-likec4-shape="queue"]) &': {
|
|
380
|
+
top: 1,
|
|
381
|
+
right: 12
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}])
|
|
@@ -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
|
}
|