@likec4/diagram 1.17.1 → 1.18.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 +17 -15
- package/src/LikeC4Diagram.props.ts +21 -12
- package/src/LikeC4Diagram.tsx +5 -7
- package/src/LikeC4Search.tsx +54 -51
- package/src/hooks/useDiagramState.ts +8 -3
- package/src/hooks/useXYFlow.ts +2 -1
- package/src/likec4model/useLikeC4ElementsTree.ts +6 -13
- package/src/likec4model/useLikeC4Model.ts +21 -11
- package/src/overlays/Overlays.tsx +96 -63
- package/src/overlays/edge-details/EdgeDetailsXYFlow.tsx +3 -1
- package/src/overlays/edge-details/_types.ts +7 -3
- package/src/overlays/edge-details/use-layouted-edge-details.ts +21 -14
- package/src/overlays/edge-details/xyflow/ElementNode.tsx +4 -2
- package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +4 -4
- package/src/overlays/element-details/ElementDetailsCard.tsx +91 -78
- package/src/overlays/element-details/TabPanelDeployments.css.ts +69 -0
- package/src/overlays/element-details/TabPanelDeployments.tsx +217 -0
- package/src/overlays/element-details/TabPanelRelationships.tsx +8 -9
- package/src/overlays/element-details/TabPanelStructure.tsx +10 -10
- package/src/overlays/relationships-of/RelationshipsOverlay.tsx +1 -1
- package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +3 -11
- package/src/overlays/relationships-of/SelectElement.tsx +2 -2
- package/src/overlays/relationships-of/_types.ts +2 -2
- package/src/overlays/relationships-of/use-layouted-relationships.ts +18 -19
- package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +1 -3
- package/src/overlays/relationships-of/xyflow/ElementNode.tsx +12 -6
- package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +0 -1
- package/src/state/diagramStore.ts +28 -11
- package/src/ui/DiagramTitlePanel.tsx +2 -29
- package/src/ui/Link.tsx +40 -0
- package/src/ui/notation/NotationPanel.tsx +4 -2
- package/src/ui/top-left/ChangeAutoLayoutButton.tsx +2 -2
- package/src/ui/top-left/TopLeftPanel.tsx +5 -5
- package/src/xyflow/edges/EdgeLabel.tsx +4 -4
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +20 -7
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +28 -13
- package/src/xyflow/nodes/compound/CompoundNode.tsx +51 -19
- package/src/xyflow/nodes/element/ElementNode.tsx +147 -124
- package/src/xyflow/nodes/element/element.css.ts +52 -19
- package/src/xyflow/nodes/shared/ElementIcon.tsx +1 -1
- package/src/xyflow/nodes/shared/Toolbar.tsx +24 -8
- package/src/xyflow/useLayoutConstraints.ts +1 -1
- package/src/xyflow/useXYFlowEvents.ts +18 -5
- package/src/ui/OptionsPanel.css.ts +0 -9
- package/src/ui/OptionsPanel.tsx +0 -70
- package/src/ui/options/EdgeOptions.tsx +0 -142
- package/src/ui/options/NodeOptions.tsx +0 -362
- package/src/xyflow/nodes/element/ElementDetails.css.ts +0 -101
- package/src/xyflow/nodes/element/ElementDetails.tsx +0 -393
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { type ThemeColor } from '@likec4/core'
|
|
2
|
-
import { ActionIcon,
|
|
1
|
+
import { DiagramNode, type ThemeColor } from '@likec4/core'
|
|
2
|
+
import { ActionIcon, type ActionIconProps, Box, Text as MantineText, Tooltip } from '@mantine/core'
|
|
3
3
|
import { useDebouncedValue } from '@mantine/hooks'
|
|
4
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
|
-
import { m, type Variants } from 'framer-motion'
|
|
9
|
-
import React, { memo, useCallback, useState } from 'react'
|
|
8
|
+
import { type HTMLMotionProps, m, type Variants } from 'framer-motion'
|
|
9
|
+
import React, { memo, type PropsWithoutRef, useCallback, useState } from 'react'
|
|
10
10
|
import { isNumber, isTruthy } from 'remeda'
|
|
11
11
|
import { useDiagramState } from '../../../hooks/useDiagramState'
|
|
12
12
|
import type { ElementXYFlowNode } from '../../types'
|
|
@@ -54,47 +54,60 @@ const VariantsRoot = {
|
|
|
54
54
|
}
|
|
55
55
|
} satisfies Variants
|
|
56
56
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
57
|
+
const variantsBottomButton = (target: 'navigate' | 'relationships', align: 'left' | 'right' | false) => {
|
|
58
|
+
const variants = {
|
|
59
|
+
idle: {
|
|
60
|
+
'--icon-scale': 'scale(1)',
|
|
61
|
+
'--ai-bg': 'var(--ai-bg-idle)',
|
|
62
|
+
scale: 1,
|
|
63
|
+
opacity: 0.5,
|
|
64
|
+
originX: 0.5,
|
|
65
|
+
originY: 0.35,
|
|
66
|
+
translateY: 0,
|
|
67
|
+
...align === 'left' && {
|
|
68
|
+
originX: 0.75,
|
|
69
|
+
translateX: -1
|
|
70
|
+
},
|
|
71
|
+
...align === 'right' && {
|
|
72
|
+
originX: 0.25,
|
|
73
|
+
translateX: 1
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
selected: {},
|
|
77
|
+
hovered: {
|
|
78
|
+
'--icon-scale': 'scale(1)',
|
|
79
|
+
'--ai-bg': 'var(--ai-bg-hover)',
|
|
80
|
+
translateY: 3,
|
|
81
|
+
scale: 1.32,
|
|
82
|
+
opacity: 1,
|
|
83
|
+
...align === 'left' && {
|
|
84
|
+
translateX: -4
|
|
85
|
+
},
|
|
86
|
+
...align === 'right' && {
|
|
87
|
+
translateX: 4
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
'hovered:details': {},
|
|
91
|
+
[`hovered:${target}`]: {
|
|
92
|
+
'--icon-scale': 'scale(1.08)',
|
|
93
|
+
scale: 1.45
|
|
94
|
+
},
|
|
95
|
+
[`tap:${target}`]: {
|
|
96
|
+
scale: 1.15
|
|
97
|
+
}
|
|
98
|
+
} satisfies Variants
|
|
99
|
+
variants['selected'] = variants['hovered']
|
|
100
|
+
variants['hovered:details'] = variants.idle
|
|
101
|
+
return variants
|
|
102
|
+
}
|
|
92
103
|
|
|
93
104
|
const VariantsDetailsBtn = {
|
|
94
105
|
idle: {
|
|
95
106
|
'--ai-bg': 'var(--ai-bg-idle)',
|
|
96
107
|
scale: 1,
|
|
97
|
-
opacity: 0.5
|
|
108
|
+
opacity: 0.5,
|
|
109
|
+
originX: 0.45,
|
|
110
|
+
originY: 0.55
|
|
98
111
|
},
|
|
99
112
|
selected: {},
|
|
100
113
|
hovered: {
|
|
@@ -102,7 +115,7 @@ const VariantsDetailsBtn = {
|
|
|
102
115
|
opacity: 0.7
|
|
103
116
|
},
|
|
104
117
|
'hovered:details': {
|
|
105
|
-
scale: 1.
|
|
118
|
+
scale: 1.44,
|
|
106
119
|
opacity: 1
|
|
107
120
|
},
|
|
108
121
|
'tap:details': {
|
|
@@ -131,6 +144,8 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
131
144
|
width,
|
|
132
145
|
height
|
|
133
146
|
}) {
|
|
147
|
+
const modelRef = DiagramNode.modelRef(element)
|
|
148
|
+
// const deploymentRef = DiagramNode.deploymentRef(element)
|
|
134
149
|
const {
|
|
135
150
|
viewId,
|
|
136
151
|
isEditable,
|
|
@@ -167,27 +182,29 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
167
182
|
const w = toDomPrecision(width ?? element.width)
|
|
168
183
|
const h = toDomPrecision(height ?? element.height)
|
|
169
184
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
let animate: keyof typeof VariantsRoot
|
|
185
|
+
let animateVariant: keyof typeof VariantsRoot | string[]
|
|
173
186
|
switch (true) {
|
|
174
187
|
case isInActiveOverlay:
|
|
175
|
-
|
|
188
|
+
animateVariant = 'idle'
|
|
176
189
|
break
|
|
177
190
|
case dragging && selected:
|
|
178
|
-
|
|
191
|
+
animateVariant = 'selected'
|
|
179
192
|
break
|
|
180
193
|
case dragging:
|
|
181
|
-
|
|
194
|
+
animateVariant = 'idle'
|
|
182
195
|
break
|
|
183
196
|
case isInteractive && isHovered:
|
|
184
|
-
|
|
197
|
+
animateVariant = 'hovered'
|
|
185
198
|
break
|
|
186
199
|
case selected:
|
|
187
|
-
|
|
200
|
+
animateVariant = 'selected'
|
|
188
201
|
break
|
|
189
202
|
default:
|
|
190
|
-
|
|
203
|
+
animateVariant = 'idle'
|
|
204
|
+
}
|
|
205
|
+
const [animateVariants, animateHandlers] = useFramerAnimateVariants()
|
|
206
|
+
if (isHovered && !dragging && !isInActiveOverlay) {
|
|
207
|
+
animateVariant = animateVariants ?? animateVariant
|
|
191
208
|
}
|
|
192
209
|
|
|
193
210
|
const elementIcon = ElementIcon({
|
|
@@ -210,9 +227,11 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
210
227
|
}, [openOverlay, element.id])
|
|
211
228
|
|
|
212
229
|
const onOpenRelationships = useCallback((e: React.MouseEvent) => {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
230
|
+
if (modelRef) {
|
|
231
|
+
e.stopPropagation()
|
|
232
|
+
openOverlay({ relationshipsOf: modelRef })
|
|
233
|
+
}
|
|
234
|
+
}, [openOverlay, modelRef])
|
|
216
235
|
|
|
217
236
|
return (
|
|
218
237
|
<>
|
|
@@ -228,7 +247,7 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
228
247
|
className={clsx([
|
|
229
248
|
css.container,
|
|
230
249
|
isDimmed && css.dimmed,
|
|
231
|
-
|
|
250
|
+
animateVariant !== 'idle' && css.containerAnimated,
|
|
232
251
|
'likec4-element-node'
|
|
233
252
|
])}
|
|
234
253
|
key={`${viewId}:element:${id}`}
|
|
@@ -239,7 +258,7 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
239
258
|
data-animate-target=""
|
|
240
259
|
initial={false}
|
|
241
260
|
variants={VariantsRoot}
|
|
242
|
-
animate={
|
|
261
|
+
animate={animateVariant}
|
|
243
262
|
tabIndex={-1}
|
|
244
263
|
{...(isInteractive && {
|
|
245
264
|
onTapStart: animateHandlers.onTapStart,
|
|
@@ -285,42 +304,13 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
285
304
|
</Box>
|
|
286
305
|
</Box>
|
|
287
306
|
{/* {isHovercards && element.links && <ElementLink element={element} />} */}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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>
|
|
322
|
-
)}
|
|
323
|
-
{enableElementDetails && (
|
|
307
|
+
<BottomButtons
|
|
308
|
+
keyPrefix={`${viewId}:element:${id}:`}
|
|
309
|
+
onNavigateTo={isNavigable && onNavigateTo}
|
|
310
|
+
onOpenRelationships={enableRelationshipBrowser && !!modelRef && onOpenRelationships}
|
|
311
|
+
{...isInteractive && animateHandlers}
|
|
312
|
+
/>
|
|
313
|
+
{enableElementDetails && !!modelRef && (
|
|
324
314
|
<Tooltip
|
|
325
315
|
fz="xs"
|
|
326
316
|
color="dark"
|
|
@@ -329,6 +319,7 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
329
319
|
offset={2}
|
|
330
320
|
openDelay={600}>
|
|
331
321
|
<ActionIcon
|
|
322
|
+
key="details"
|
|
332
323
|
component={m.div}
|
|
333
324
|
variants={VariantsDetailsBtn}
|
|
334
325
|
data-animate-target="details"
|
|
@@ -351,37 +342,69 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
351
342
|
)
|
|
352
343
|
}, isEqualProps)
|
|
353
344
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
// }
|
|
345
|
+
type BottomButtonsProps = PropsWithoutRef<
|
|
346
|
+
ActionIconProps & HTMLMotionProps<'div'> & {
|
|
347
|
+
keyPrefix: string
|
|
348
|
+
onNavigateTo: ((e: React.MouseEvent) => void) | false
|
|
349
|
+
onOpenRelationships: ((e: React.MouseEvent) => void) | false
|
|
350
|
+
}
|
|
351
|
+
>
|
|
352
|
+
const BottomButtons = ({
|
|
353
|
+
keyPrefix,
|
|
354
|
+
onNavigateTo,
|
|
355
|
+
onOpenRelationships,
|
|
356
|
+
...props
|
|
357
|
+
}: BottomButtonsProps) => {
|
|
358
|
+
const enableRelationships = !!onOpenRelationships
|
|
359
|
+
const enableNavigate = !!onNavigateTo
|
|
370
360
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
//
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
361
|
+
if (!enableRelationships && !enableNavigate) {
|
|
362
|
+
return null
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<Box className={css.bottomButtonsContainer}>
|
|
367
|
+
{enableNavigate && (
|
|
368
|
+
<ActionIcon
|
|
369
|
+
{...props}
|
|
370
|
+
key={`${keyPrefix}navigate`}
|
|
371
|
+
data-animate-target="navigate"
|
|
372
|
+
component={m.div}
|
|
373
|
+
// Weird, but dts-bundle-generator fails on "enableRelationships && 'left'"
|
|
374
|
+
variants={variantsBottomButton('navigate', enableRelationships ? 'left' : false)}
|
|
375
|
+
className={clsx('nodrag nopan', css.navigateBtn)}
|
|
376
|
+
radius="md"
|
|
377
|
+
role="button"
|
|
378
|
+
onClick={onNavigateTo}
|
|
379
|
+
onDoubleClick={stopPropagation}
|
|
380
|
+
>
|
|
381
|
+
<IconZoomScan
|
|
382
|
+
style={{
|
|
383
|
+
width: '70%',
|
|
384
|
+
transform: 'var(--icon-scale)'
|
|
385
|
+
}} />
|
|
386
|
+
</ActionIcon>
|
|
387
|
+
)}
|
|
388
|
+
{enableRelationships && (
|
|
389
|
+
<ActionIcon
|
|
390
|
+
{...props}
|
|
391
|
+
key={`${keyPrefix}relationships`}
|
|
392
|
+
data-animate-target="relationships"
|
|
393
|
+
component={m.div}
|
|
394
|
+
variants={variantsBottomButton('relationships', enableNavigate ? 'right' : false)}
|
|
395
|
+
className={clsx('nodrag nopan', css.navigateBtn)}
|
|
396
|
+
radius="md"
|
|
397
|
+
role="button"
|
|
398
|
+
onClick={onOpenRelationships}
|
|
399
|
+
onDoubleClick={stopPropagation}
|
|
400
|
+
>
|
|
401
|
+
<IconTransform
|
|
402
|
+
style={{
|
|
403
|
+
width: '70%',
|
|
404
|
+
transform: 'var(--icon-scale)'
|
|
405
|
+
}} />
|
|
406
|
+
</ActionIcon>
|
|
407
|
+
)}
|
|
408
|
+
</Box>
|
|
409
|
+
)
|
|
410
|
+
}
|
|
@@ -141,7 +141,8 @@ export const title = style({
|
|
|
141
141
|
fontSize: 19,
|
|
142
142
|
lineHeight: 1.15,
|
|
143
143
|
textWrap: 'balance',
|
|
144
|
-
color: vars.element.hiContrast
|
|
144
|
+
color: vars.element.hiContrast,
|
|
145
|
+
whiteSpaceCollapse: 'preserve-breaks'
|
|
145
146
|
})
|
|
146
147
|
|
|
147
148
|
export const description = style({
|
|
@@ -338,6 +339,56 @@ export const shapeSvg = style({
|
|
|
338
339
|
}
|
|
339
340
|
})
|
|
340
341
|
|
|
342
|
+
export const bottomButtonsContainer = style({
|
|
343
|
+
zIndex: 100,
|
|
344
|
+
position: 'absolute',
|
|
345
|
+
left: 0,
|
|
346
|
+
width: '100%',
|
|
347
|
+
bottom: 2,
|
|
348
|
+
display: 'flex',
|
|
349
|
+
flexDirection: 'row',
|
|
350
|
+
flexWrap: 'nowrap',
|
|
351
|
+
alignItems: 'center',
|
|
352
|
+
gap: 2,
|
|
353
|
+
justifyContent: 'center',
|
|
354
|
+
pointerEvents: 'none',
|
|
355
|
+
selectors: {
|
|
356
|
+
[`:where([data-likec4-shape='browser']) &`]: {
|
|
357
|
+
bottom: 4
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
// --group-gap: var(--mantine-spacing-md);
|
|
363
|
+
// --group-align: center;
|
|
364
|
+
// --group-justify: flex-start;
|
|
365
|
+
// --group-wrap: wrap;
|
|
366
|
+
// gap: 0px;
|
|
367
|
+
// }
|
|
368
|
+
// <style>
|
|
369
|
+
// .element_bottomButtonsContainer__14yllojn {
|
|
370
|
+
// z-index: 100;
|
|
371
|
+
// position: absolute;
|
|
372
|
+
// left: 50%;
|
|
373
|
+
// bottom: 2px;
|
|
374
|
+
// transform: translate(-50%, 0%);
|
|
375
|
+
// }
|
|
376
|
+
|
|
377
|
+
// <style>
|
|
378
|
+
// .m_4081bf90 {
|
|
379
|
+
// display: flex
|
|
380
|
+
// ;
|
|
381
|
+
// flex-direction: row;
|
|
382
|
+
// flex-wrap: var(--group-wrap, wrap);
|
|
383
|
+
// justify-content: var(--group-justify, flex-start);
|
|
384
|
+
// align-items: var(--group-align, center);
|
|
385
|
+
// gap: var(--group-gap, var(--mantine-spacing-md));
|
|
386
|
+
// }
|
|
387
|
+
// globalStyle(`:where(.react-flow__node:hover) ${bottomButtonsContainer}`, {
|
|
388
|
+
// transitionDelay: '20ms',
|
|
389
|
+
// gap: 16
|
|
390
|
+
// })
|
|
391
|
+
|
|
341
392
|
const btn = style({
|
|
342
393
|
pointerEvents: 'all',
|
|
343
394
|
color: vars.element.loContrast,
|
|
@@ -354,24 +405,6 @@ const btn = style({
|
|
|
354
405
|
}
|
|
355
406
|
})
|
|
356
407
|
|
|
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,
|
|
364
|
-
selectors: {
|
|
365
|
-
[`:where([data-likec4-shape='browser']) &`]: {
|
|
366
|
-
bottom: 4
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
})
|
|
370
|
-
globalStyle(`:where(.react-flow__node:hover) ${navigateBtnBox}`, {
|
|
371
|
-
transitionDelay: '20ms',
|
|
372
|
-
gap: 16
|
|
373
|
-
})
|
|
374
|
-
|
|
375
408
|
export const navigateBtn = style([btn, {}])
|
|
376
409
|
|
|
377
410
|
export const detailsBtn = style([btn, {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type BorderStyle,
|
|
3
3
|
defaultTheme,
|
|
4
|
+
DiagramNode,
|
|
4
5
|
type Element,
|
|
5
6
|
ElementShapes,
|
|
6
7
|
type Fqn,
|
|
@@ -40,6 +41,7 @@ import { IconCheck, IconFileSymlink, IconSelector, IconTransform } from '@tabler
|
|
|
40
41
|
import { NodeToolbar, type NodeToolbarProps } from '@xyflow/react'
|
|
41
42
|
import clsx from 'clsx'
|
|
42
43
|
import { useEffect, useState } from 'react'
|
|
44
|
+
import type { MergeExclusive } from 'type-fest'
|
|
43
45
|
import { useDiagramState, useDiagramStoreApi, useMantinePortalProps, useUpdateEffect } from '../../../hooks'
|
|
44
46
|
import { stopPropagation } from '../../utils'
|
|
45
47
|
|
|
@@ -75,7 +77,7 @@ const Tooltip = MantineTooltip.withProps({
|
|
|
75
77
|
})
|
|
76
78
|
|
|
77
79
|
type ToolbarProps = NodeToolbarProps & {
|
|
78
|
-
element:
|
|
80
|
+
element: DiagramNode
|
|
79
81
|
onColorPreview: (color: ThemeColor | null) => void
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -86,13 +88,15 @@ export function CompoundToolbar({
|
|
|
86
88
|
onColorPreview,
|
|
87
89
|
...props
|
|
88
90
|
}: ToolbarProps) {
|
|
91
|
+
const modelRef = DiagramNode.modelRef(element)
|
|
92
|
+
const deploymentRef = DiagramNode.deploymentRef(element)
|
|
89
93
|
const targets = [element.id] as NonEmptyArray<Fqn>
|
|
90
94
|
const diagramApi = useDiagramStoreApi()
|
|
91
95
|
const {
|
|
92
96
|
hasGoToSource,
|
|
93
97
|
enableRelationshipBrowser
|
|
94
98
|
} = useDiagramState(s => ({
|
|
95
|
-
hasGoToSource: !!s.
|
|
99
|
+
hasGoToSource: !!s.onOpenSource,
|
|
96
100
|
enableRelationshipBrowser: s.enableRelationshipBrowser
|
|
97
101
|
}))
|
|
98
102
|
|
|
@@ -117,7 +121,8 @@ export function CompoundToolbar({
|
|
|
117
121
|
elementBorderStyle={element.style?.border}
|
|
118
122
|
onChange={onChange}
|
|
119
123
|
/>
|
|
120
|
-
{hasGoToSource && <GoToSourceButton elementId={
|
|
124
|
+
{hasGoToSource && modelRef && <GoToSourceButton elementId={modelRef} />}
|
|
125
|
+
{hasGoToSource && !modelRef && deploymentRef && <GoToSourceButton deploymentId={deploymentRef} />}
|
|
121
126
|
{enableRelationshipBrowser && <BrowseRelationshipsButton elementId={element.id} />}
|
|
122
127
|
</Toolbar>
|
|
123
128
|
)
|
|
@@ -156,13 +161,15 @@ export function ElementToolbar({
|
|
|
156
161
|
onColorPreview,
|
|
157
162
|
...props
|
|
158
163
|
}: ToolbarProps) {
|
|
164
|
+
const modelRef = DiagramNode.modelRef(element)
|
|
165
|
+
const deploymentRef = DiagramNode.deploymentRef(element)
|
|
159
166
|
const targets = [element.id] as NonEmptyArray<Fqn>
|
|
160
167
|
const diagramApi = useDiagramStoreApi()
|
|
161
168
|
const {
|
|
162
169
|
hasGoToSource,
|
|
163
170
|
enableRelationshipBrowser
|
|
164
171
|
} = useDiagramState(s => ({
|
|
165
|
-
hasGoToSource: !!s.
|
|
172
|
+
hasGoToSource: !!s.onOpenSource,
|
|
166
173
|
enableRelationshipBrowser: s.enableRelationshipBrowser
|
|
167
174
|
}))
|
|
168
175
|
const portalProps = useMantinePortalProps()
|
|
@@ -234,20 +241,21 @@ export function ElementToolbar({
|
|
|
234
241
|
onChange={onChange}
|
|
235
242
|
position="right-end"
|
|
236
243
|
/>
|
|
237
|
-
{hasGoToSource && <GoToSourceButton elementId={
|
|
244
|
+
{hasGoToSource && modelRef && <GoToSourceButton elementId={modelRef} />}
|
|
245
|
+
{hasGoToSource && !modelRef && deploymentRef && <GoToSourceButton deploymentId={deploymentRef} />}
|
|
238
246
|
{enableRelationshipBrowser && <BrowseRelationshipsButton elementId={element.id} />}
|
|
239
247
|
</Toolbar>
|
|
240
248
|
)
|
|
241
249
|
}
|
|
242
250
|
|
|
243
251
|
type ColorButtonProps = Omit<PopoverProps, 'onChange'> & {
|
|
244
|
-
element:
|
|
252
|
+
element: DiagramNode
|
|
245
253
|
isOpacityEditable?: boolean
|
|
246
254
|
onColorPreview: (color: ThemeColor | null) => void
|
|
247
255
|
onChange: OnStyleChange
|
|
248
256
|
}
|
|
249
257
|
|
|
250
|
-
function GoToSourceButton({ elementId }
|
|
258
|
+
function GoToSourceButton(props: MergeExclusive<{ elementId: Fqn }, { deploymentId: Fqn }>) {
|
|
251
259
|
const diagramApi = useDiagramStoreApi()
|
|
252
260
|
const portalProps = useMantinePortalProps()
|
|
253
261
|
return (
|
|
@@ -258,7 +266,15 @@ function GoToSourceButton({ elementId }: { elementId: Fqn }) {
|
|
|
258
266
|
color="gray"
|
|
259
267
|
onClick={e => {
|
|
260
268
|
e.stopPropagation()
|
|
261
|
-
|
|
269
|
+
if (props.elementId) {
|
|
270
|
+
diagramApi.getState().onOpenSource?.({
|
|
271
|
+
element: props.elementId
|
|
272
|
+
})
|
|
273
|
+
} else if (props.deploymentId) {
|
|
274
|
+
diagramApi.getState().onOpenSource?.({
|
|
275
|
+
deployment: props.deploymentId
|
|
276
|
+
})
|
|
277
|
+
}
|
|
262
278
|
}}>
|
|
263
279
|
<IconFileSymlink stroke={1.8} style={{ width: '70%' }} />
|
|
264
280
|
</ActionIcon>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type NodeId, type NonEmptyArray, nonNullable } from '@likec4/core'
|
|
2
2
|
import type { InternalNode, NodeChange, ReactFlowProps, XYPosition } from '@xyflow/react'
|
|
3
3
|
import { getNodeDimensions } from '@xyflow/system'
|
|
4
4
|
import { useMemo, useRef } from 'react'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DiagramNode } from '@likec4/core'
|
|
1
2
|
import type { ReactFlowProps } from '@xyflow/react'
|
|
2
3
|
import { useMemo, useRef } from 'react'
|
|
3
4
|
import { isNonNullish, isTruthy } from 'remeda'
|
|
@@ -123,9 +124,11 @@ export function useXYFlowEvents() {
|
|
|
123
124
|
nodesSelectable,
|
|
124
125
|
enableElementDetails,
|
|
125
126
|
setLastClickedNode,
|
|
126
|
-
|
|
127
|
+
onOpenSource,
|
|
127
128
|
openOverlay
|
|
128
129
|
} = diagramApi.getState()
|
|
130
|
+
const modelRef = DiagramNode.modelRef(xynode.data.element)
|
|
131
|
+
const deploymentRef = DiagramNode.deploymentRef(xynode.data.element)
|
|
129
132
|
setLastClickedNode(xynode.id)
|
|
130
133
|
// if we focused on a node, and clicked on another node - focus on the clicked node
|
|
131
134
|
const shallChangeFocus = !!focusedNodeId && focusedNodeId !== xynode.id
|
|
@@ -134,8 +137,16 @@ export function useXYFlowEvents() {
|
|
|
134
137
|
|
|
135
138
|
let stopPropagation = false
|
|
136
139
|
|
|
137
|
-
if (clickedRecently && !!
|
|
138
|
-
|
|
140
|
+
if (clickedRecently && !!onOpenSource && (modelRef || deploymentRef)) {
|
|
141
|
+
if (modelRef) {
|
|
142
|
+
onOpenSource({
|
|
143
|
+
element: modelRef
|
|
144
|
+
})
|
|
145
|
+
} else if (deploymentRef) {
|
|
146
|
+
onOpenSource({
|
|
147
|
+
deployment: deploymentRef
|
|
148
|
+
})
|
|
149
|
+
}
|
|
139
150
|
stopPropagation = true
|
|
140
151
|
}
|
|
141
152
|
|
|
@@ -152,7 +163,7 @@ export function useXYFlowEvents() {
|
|
|
152
163
|
stopPropagation = true
|
|
153
164
|
break
|
|
154
165
|
}
|
|
155
|
-
case !clickedRecently && focusedNodeId === xynode.id && enableElementDetails: {
|
|
166
|
+
case !clickedRecently && modelRef && focusedNodeId === xynode.id && enableElementDetails: {
|
|
156
167
|
openOverlay({
|
|
157
168
|
elementDetails: xynode.data.element.id
|
|
158
169
|
})
|
|
@@ -160,7 +171,9 @@ export function useXYFlowEvents() {
|
|
|
160
171
|
break
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
|
-
} else if (
|
|
174
|
+
} else if (
|
|
175
|
+
enableElementDetails && modelRef && (clickedRecently || focusedNodeId === xynode.id) && !onNodeClick
|
|
176
|
+
) {
|
|
164
177
|
openOverlay({
|
|
165
178
|
elementDetails: xynode.data.element.id
|
|
166
179
|
})
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { fallbackVar, style } from '@vanilla-extract/css'
|
|
2
|
-
import { mantine, vars } from '../theme-vars'
|
|
3
|
-
|
|
4
|
-
export const panel = style({
|
|
5
|
-
top: fallbackVar(vars.optionsPanel.top, '1rem'),
|
|
6
|
-
right: fallbackVar(vars.optionsPanel.right, '1rem'),
|
|
7
|
-
margin: 0,
|
|
8
|
-
boxShadow: mantine.shadows.xl
|
|
9
|
-
})
|