@likec4/diagram 1.20.3 → 1.21.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.
Files changed (105) hide show
  1. package/README.md +2 -2
  2. package/dist/base/BaseXYFlow.js +1 -2
  3. package/dist/base/example/_types.d.ts +3 -3
  4. package/dist/base/example/custom.d.ts +3 -2
  5. package/dist/base/primitives/compound/CompoundActionButton.js +1 -1
  6. package/dist/base/primitives/compound/CompoundDetailsButton.js +1 -1
  7. package/dist/base/primitives/compound/CompoundNodeContainer.js +2 -2
  8. package/dist/base/primitives/compound/CompoundTitle.css.d.ts +1 -0
  9. package/dist/base/primitives/compound/CompoundTitle.css.js +2 -0
  10. package/dist/base/primitives/compound/CompoundTitle.d.ts +2 -2
  11. package/dist/base/primitives/compound/CompoundTitle.js +17 -6
  12. package/dist/base/primitives/edge/EdgeLabel.js +2 -2
  13. package/dist/base/primitives/element/ElementActionButtons.js +1 -1
  14. package/dist/base/primitives/element/ElementDetailsButton.js +1 -1
  15. package/dist/base/primitives/element/ElementNodeContainer.d.ts +7 -2
  16. package/dist/base/primitives/element/ElementNodeContainer.js +34 -1
  17. package/dist/base/primitives/element/ElementShape.js +1 -1
  18. package/dist/base/primitives/element/ElementTitle.css.d.ts +0 -1
  19. package/dist/base/primitives/element/ElementTitle.css.js +8 -8
  20. package/dist/base/primitives/element/ElementTitle.d.ts +1 -1
  21. package/dist/base/primitives/element/ElementTitle.js +11 -5
  22. package/dist/base/primitives/element/styles.css.d.ts +3 -0
  23. package/dist/base/primitives/element/styles.css.js +6 -0
  24. package/dist/context/DiagramEventHandlers.js +1 -1
  25. package/dist/context/DiagramFeatures.js +1 -1
  26. package/dist/context/RootContainer.js +1 -1
  27. package/dist/hooks/index.d.ts +0 -1
  28. package/dist/hooks/useDiagram.d.ts +1 -2
  29. package/dist/hooks/useXYFlow.js +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/likec4diagram/DiagramUI.d.ts +1 -2
  32. package/dist/likec4diagram/DiagramUI.js +3 -4
  33. package/dist/likec4diagram/XYFlow.js +1 -1
  34. package/dist/likec4diagram/custom/edges/NotePopover.js +1 -1
  35. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +1 -1
  36. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +5 -4
  37. package/dist/likec4diagram/custom/nodes/nodeTypes.js +8 -8
  38. package/dist/likec4diagram/custom/nodes/toolbar/_shared.js +2 -2
  39. package/dist/likec4diagram/state/assign.js +1 -1
  40. package/dist/likec4diagram/state/machine.js +5 -5
  41. package/dist/likec4diagram/types.d.ts +2 -2
  42. package/dist/likec4diagram/ui/controls/Controls.js +1 -1
  43. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +5 -4
  44. package/dist/likec4diagram/ui/index.d.ts +2 -0
  45. package/dist/likec4diagram/ui/walkthrough/DynamicViewWalkthrough.js +2 -2
  46. package/dist/likec4diagram/useViewToNodesEdges.js +1 -1
  47. package/dist/overlays/Overlays.js +43 -22
  48. package/dist/overlays/element-details/ElementDetailsCard.d.ts +7 -3
  49. package/dist/overlays/element-details/ElementDetailsCard.js +38 -39
  50. package/dist/overlays/element-details/TabPanelDeployments.js +5 -3
  51. package/dist/overlays/element-details/TabPanelRelationships.js +14 -30
  52. package/dist/overlays/overlay/Overlay.css.js +2 -1
  53. package/dist/overlays/overlay/Overlay.js +2 -2
  54. package/dist/overlays/relationship-details/SelectEdge.js +1 -1
  55. package/dist/overlays/relationship-details/_types.d.ts +3 -3
  56. package/dist/overlays/relationship-details/actor.js +1 -1
  57. package/dist/overlays/relationship-details/custom/nodeTypes.d.ts +3 -3
  58. package/dist/overlays/relationship-details/custom/nodeTypes.js +2 -2
  59. package/dist/overlays/relationship-details/useLayoutedDetails.js +1 -4
  60. package/dist/overlays/relationships-browser/-useRelationshipsView.js +0 -3
  61. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +3 -3
  62. package/dist/overlays/relationships-browser/_types.d.ts +3 -3
  63. package/dist/overlays/relationships-browser/actor.js +16 -16
  64. package/dist/overlays/relationships-browser/custom/nodeTypes.d.ts +3 -2
  65. package/dist/overlays/relationships-browser/custom/nodeTypes.js +3 -3
  66. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +3 -2
  67. package/dist/style.css +1 -1
  68. package/package.json +14 -14
  69. package/src/base/BaseXYFlow.tsx +0 -1
  70. package/src/base/example/_types.ts +3 -2
  71. package/src/base/example/useViewToNodesEdges.ts +2 -1
  72. package/src/base/primitives/compound/CompoundTitle.css.ts +27 -1
  73. package/src/base/primitives/compound/CompoundTitle.tsx +12 -2
  74. package/src/base/primitives/element/ElementNodeContainer.css.ts +4 -7
  75. package/src/base/primitives/element/ElementNodeContainer.tsx +41 -1
  76. package/src/base/primitives/element/ElementShape.css.ts +5 -0
  77. package/src/base/primitives/element/ElementTitle.css.ts +25 -29
  78. package/src/base/primitives/element/ElementTitle.tsx +10 -4
  79. package/src/base/primitives/element/styles.css.ts +52 -0
  80. package/src/globals.css.ts +2 -0
  81. package/src/hooks/index.ts +0 -1
  82. package/src/hooks/useDiagram.ts +1 -1
  83. package/src/index.ts +15 -1
  84. package/src/likec4diagram/DiagramUI.tsx +3 -6
  85. package/src/likec4diagram/XYFlow.tsx +2 -2
  86. package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +0 -1
  87. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.css.ts +1 -0
  88. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +4 -2
  89. package/src/likec4diagram/types.ts +2 -2
  90. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +110 -107
  91. package/src/likec4diagram/ui/index.ts +2 -0
  92. package/src/likec4diagram/ui/notation/NotationPanel.tsx +2 -3
  93. package/src/likec4diagram/useViewToNodesEdges.ts +1 -1
  94. package/src/overlays/Overlays.tsx +62 -282
  95. package/src/overlays/element-details/ElementDetailsCard.tsx +214 -261
  96. package/src/overlays/element-details/TabPanelDeployments.tsx +4 -4
  97. package/src/overlays/element-details/TabPanelRelationships.tsx +13 -75
  98. package/src/overlays/overlay/Overlay.tsx +1 -1
  99. package/src/overlays/relationship-details/_types.ts +3 -3
  100. package/src/overlays/relationship-details/useLayoutedDetails.ts +1 -1
  101. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +44 -55
  102. package/src/overlays/relationships-browser/_types.ts +12 -2
  103. package/src/overlays/relationships-browser/actor.ts +11 -10
  104. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +2 -1
  105. package/dist/hooks/useCurrentViewId.js +0 -8
@@ -3,7 +3,8 @@ import {
3
3
  type Element,
4
4
  type Fqn,
5
5
  type LikeC4View,
6
- ComputedNode,
6
+ type NodeId,
7
+ type ViewId,
7
8
  ComputedView,
8
9
  isDeploymentView,
9
10
  isScopedElementView,
@@ -12,17 +13,13 @@ import {
12
13
  type TextProps,
13
14
  ActionIcon,
14
15
  ActionIconGroup,
15
- Anchor,
16
16
  Badge,
17
17
  Box,
18
18
  Card,
19
19
  CloseButton,
20
20
  Code,
21
- CopyButton,
22
21
  Divider as MantineDivider,
23
22
  Flex,
24
- FocusTrap,
25
- FocusTrapInitialFocus,
26
23
  Group,
27
24
  RemoveScroll,
28
25
  ScrollArea,
@@ -36,18 +33,18 @@ import {
36
33
  Tooltip as MantineTooltip,
37
34
  UnstyledButton,
38
35
  } from '@mantine/core'
39
- import { useDebouncedCallback, useSessionStorage, useViewportSize } from '@mantine/hooks'
40
- import { IconCheck, IconCopy, IconExternalLink, IconFileSymlink, IconStack2, IconZoomScan } from '@tabler/icons-react'
41
- import clsx from 'clsx'
36
+ import { useSessionStorage, useViewportSize } from '@mantine/hooks'
37
+ import { useDebouncedCallback, useSyncedRef } from '@react-hookz/web'
38
+ import { IconExternalLink, IconFileSymlink, IconStack2, IconZoomScan } from '@tabler/icons-react'
39
+ import type { Rect } from '@xyflow/system'
42
40
  import { type PanInfo, m, useDragControls, useMotionValue } from 'framer-motion'
43
- import { type PropsWithChildren, memo, useCallback, useLayoutEffect, useRef } from 'react'
41
+ import { type PropsWithChildren, useCallback, useRef } from 'react'
44
42
  import { clamp, isNullish, map, only, partition, pipe } from 'remeda'
45
43
  import { Link } from '../../base'
46
- import { IconRenderer, IfEnabled } from '../../context'
44
+ import { DiagramFeatures, IconRenderer, IfEnabled } from '../../context'
47
45
  import { useUpdateEffect } from '../../hooks'
48
46
  import { useDiagram } from '../../hooks/useDiagram'
49
- import { useDiagramContext } from '../../hooks/useDiagramContext'
50
- import type { ElementIconRenderer, OnNavigateTo } from '../../LikeC4Diagram.props'
47
+ import type { OnNavigateTo } from '../../LikeC4Diagram.props'
51
48
  import { useLikeC4Model } from '../../likec4model'
52
49
  import * as css from './ElementDetailsCard.css'
53
50
  import { TabPanelDeployments } from './TabPanelDeployments'
@@ -85,6 +82,10 @@ const PropertyLabel = Text.withProps({
85
82
  })
86
83
 
87
84
  type ElementDetailsCardProps = {
85
+ viewId: ViewId
86
+ fromNode: NodeId | null
87
+ rectFromNode: Rect | null
88
+ onClose: () => void
88
89
  fqn: Fqn
89
90
  }
90
91
 
@@ -93,16 +94,13 @@ const MIN_PADDING = 24
93
94
  const TABS = ['Properties', 'Relationships', 'Views', 'Structure', 'Deployments'] as const
94
95
  type TabName = typeof TABS[number]
95
96
 
96
- export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
97
- const {
98
- viewId,
99
- fromNode,
100
- rectFromNode,
101
- } = useDiagramContext(s => ({
102
- viewId: s.view.id,
103
- fromNode: s.activeElementDetails?.fromNode ?? null,
104
- rectFromNode: s.activeElementDetails?.nodeRectScreen ?? null,
105
- }))
97
+ export function ElementDetailsCard({
98
+ viewId,
99
+ fromNode,
100
+ rectFromNode,
101
+ fqn,
102
+ onClose,
103
+ }: ElementDetailsCardProps) {
106
104
  const windowSize = useViewportSize()
107
105
  const windowWidth = windowSize.width || window.innerWidth || 1200,
108
106
  windowHeight = windowSize.height || window.innerHeight || 800
@@ -114,25 +112,18 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
114
112
  const diagram = useDiagram()
115
113
  const likec4model = useLikeC4Model(true)
116
114
  const viewModel = likec4model.view(viewId)
117
- // const currentView = diagram.currentView()
118
-
119
- // const diagramNode = currentView.nodes.find(n => n.id === fqn)
120
- // invariant(diagramNode, `DiagramNode with fqn ${fqn} not found`)
121
115
  const nodeModel = fromNode ? viewModel.findNode(fromNode) : viewModel.findNodeWithElement(fqn)
122
- // invariant(nodeModel && nodeModel.hasElement(), `NodeModel with fqn ${fqn} not found`)
116
+
123
117
  const elementModel = viewModel.$model.element(fqn)
124
118
 
125
119
  // const incoming = elementModel.incoming().map(r => r.id).toArray()
126
120
  // const outgoing = elementModel.outgoing().map(r => r.id).toArray()
127
-
128
121
  // const incomingInView = unique(nodeModel.incoming().flatMap(e => e.$edge.relations).toArray())
129
122
  // const outgoingInView = unique(nodeModel.outgoing().flatMap(e => e.$edge.relations).toArray())
130
-
131
123
  // const notIncludedRelations = [
132
124
  // ...incoming,
133
125
  // ...outgoing
134
126
  // ].filter(r => !incomingInView.includes(r) && !outgoingInView.includes(r)).length
135
-
136
127
  const [viewsOf, otherViews] = pipe(
137
128
  [...elementModel.views()],
138
129
  map(v => v.$view),
@@ -202,15 +193,13 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
202
193
  }, [])
203
194
 
204
195
  const ref = useRef<HTMLDialogElement>(null)
205
- useLayoutEffect(() => {
206
- ref.current?.showModal()
207
- }, [])
208
196
 
209
- const diagramApi = useDiagram()
197
+ const onCloseRef = useSyncedRef(onClose)
210
198
  const close = useDebouncedCallback(
211
199
  () => {
212
- diagramApi.closeOverlay()
200
+ onCloseRef.current()
213
201
  },
202
+ [],
214
203
  50,
215
204
  )
216
205
 
@@ -229,7 +218,6 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
229
218
  <m.dialog
230
219
  ref={ref}
231
220
  className={css.dialog}
232
- layoutRoot
233
221
  initial={{
234
222
  '--backdrop-blur': '0px',
235
223
  '--backdrop-opacity': '10%',
@@ -245,6 +233,9 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
245
233
  duration: 0.1,
246
234
  },
247
235
  }}
236
+ onAnimationStart={() => {
237
+ ref.current?.showModal()
238
+ }}
248
239
  onClick={e => {
249
240
  if ((e.target as any)?.nodeName?.toUpperCase() === 'DIALOG') {
250
241
  e.stopPropagation()
@@ -267,7 +258,6 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
267
258
  shadow="md"
268
259
  component={m.div}
269
260
  className={css.card}
270
- layoutId={fqn}
271
261
  initial={{
272
262
  top,
273
263
  left,
@@ -296,248 +286,211 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
296
286
  height: height as any,
297
287
  }}
298
288
  data-likec4-color={nodeModel?.color ?? elementModel.color}>
299
- <FocusTrap>
300
- <FocusTrapInitialFocus />
301
- <Box
302
- className={css.cardHeader}
303
- onPointerDown={e => controls.start(e)}>
304
- <Group align="start" justify="space-between" gap={'sm'} mb={'sm'} wrap="nowrap">
305
- <Group align="start" gap={'sm'} style={{ cursor: 'default' }} wrap="nowrap">
306
- {elementIcon}
307
- <Box>
308
- <Text
309
- component={m.div}
310
- layout="position"
311
- layoutId={`${viewId}:element:title:${fqn}`}
312
- className={css.title}>
313
- {elementModel.title}
314
- </Text>
315
- {notation && (
316
- <Text component="div" c={'dimmed'} fz={'sm'} fw={500} lh={1.3} lineClamp={1}>
317
- {notation}
318
- </Text>
319
- )}
320
- </Box>
321
- </Group>
322
- <CloseButton
323
- size={'lg'}
324
- onClick={e => {
325
- e.stopPropagation()
326
- close()
327
- }}
328
- />
329
- </Group>
330
- <Group align="baseline" gap={'sm'} wrap="nowrap">
289
+ <Box
290
+ className={css.cardHeader}
291
+ onPointerDown={e => controls.start(e)}>
292
+ <Group align="start" justify="space-between" gap={'sm'} mb={'sm'} wrap="nowrap">
293
+ <Group align="start" gap={'sm'} style={{ cursor: 'default' }} wrap="nowrap">
294
+ {elementIcon}
331
295
  <Box>
332
- <SmallLabel>kind</SmallLabel>
333
- <Badge radius={'sm'} size="sm" fw={600} color="gray">{elementModel.kind}</Badge>
334
- </Box>
335
- <Box flex={1}>
336
- <SmallLabel>tags</SmallLabel>
337
- <Flex gap={4} flex={1} mt={6}>
338
- {elementModel.tags.map((tag) => (
339
- <Badge key={tag} radius={'sm'} size="sm" fw={600} variant="gradient">#{tag}</Badge>
340
- ))}
341
- {elementModel.tags.length === 0 && <Badge radius={'sm'} size="sm" fw={600} color="gray">—</Badge>}
342
- </Flex>
296
+ <Text
297
+ component={m.div}
298
+ layout="position"
299
+ layoutId={`${viewId}:element:title:${fqn}`}
300
+ className={css.title}>
301
+ {elementModel.title}
302
+ </Text>
303
+ {notation && (
304
+ <Text component="div" c={'dimmed'} fz={'sm'} fw={500} lh={1.3} lineClamp={1}>
305
+ {notation}
306
+ </Text>
307
+ )}
343
308
  </Box>
344
- <ActionIconGroup
345
- style={{
346
- alignSelf: 'flex-end',
347
- }}>
348
- {defaultLink && (
309
+ </Group>
310
+ <CloseButton
311
+ size={'lg'}
312
+ onClick={e => {
313
+ e.stopPropagation()
314
+ close()
315
+ }} />
316
+ </Group>
317
+ <Group align="baseline" gap={'sm'} wrap="nowrap">
318
+ <Box>
319
+ <SmallLabel>kind</SmallLabel>
320
+ <Badge radius={'sm'} size="sm" fw={600} color="gray">{elementModel.kind}</Badge>
321
+ </Box>
322
+ <Box flex={1}>
323
+ <SmallLabel>tags</SmallLabel>
324
+ <Flex gap={4} flex={1} mt={6}>
325
+ {elementModel.tags.map((tag) => (
326
+ <Badge key={tag} radius={'sm'} size="sm" fw={600} variant="gradient">#{tag}</Badge>
327
+ ))}
328
+ {elementModel.tags.length === 0 && <Badge radius={'sm'} size="sm" fw={600} color="gray">—</Badge>}
329
+ </Flex>
330
+ </Box>
331
+ <ActionIconGroup
332
+ style={{
333
+ alignSelf: 'flex-end',
334
+ }}>
335
+ {defaultLink && (
336
+ <ActionIcon
337
+ component="a"
338
+ href={defaultLink.url}
339
+ target="_blank"
340
+ size="lg"
341
+ variant="default"
342
+ radius="sm"
343
+ >
344
+ <IconExternalLink stroke={1.6} style={{ width: '65%' }} />
345
+ </ActionIcon>
346
+ )}
347
+ <IfEnabled feature="Vscode">
348
+ <Tooltip label="Open source">
349
+ <ActionIcon
350
+ size="lg"
351
+ variant="default"
352
+ radius="sm"
353
+ onClick={e => {
354
+ e.stopPropagation()
355
+ diagram.openSource({
356
+ element: elementModel.id,
357
+ })
358
+ }}>
359
+ <IconFileSymlink stroke={1.8} style={{ width: '62%' }} />
360
+ </ActionIcon>
361
+ </Tooltip>
362
+ </IfEnabled>
363
+ {defaultView && (
364
+ <Tooltip label="Open default view">
349
365
  <ActionIcon
350
- component="a"
351
- href={defaultLink.url}
352
- target="_blank"
353
366
  size="lg"
354
367
  variant="default"
355
368
  radius="sm"
356
- >
357
- <IconExternalLink stroke={1.6} style={{ width: '65%' }} />
369
+ onClick={e => {
370
+ e.stopPropagation()
371
+ diagram.navigateTo(defaultView.id, fromNode ?? undefined)
372
+ }}>
373
+ <IconZoomScan style={{ width: '70%' }} />
358
374
  </ActionIcon>
375
+ </Tooltip>
376
+ )}
377
+ </ActionIconGroup>
378
+ </Group>
379
+ </Box>
380
+
381
+ <Tabs
382
+ value={activeTab}
383
+ onChange={v => setActiveTab(v as any)}
384
+ variant="none"
385
+ classNames={{
386
+ root: css.tabsRoot,
387
+ list: css.tabsList,
388
+ tab: css.tabsTab,
389
+ panel: css.tabsPanel,
390
+ }}>
391
+ <TabsList>
392
+ {TABS.map(tab => (
393
+ <TabsTab key={tab} value={tab}>
394
+ {tab}
395
+ </TabsTab>
396
+ ))}
397
+ </TabsList>
398
+
399
+ <TabsPanel value="Properties">
400
+ <ScrollArea scrollbars="y" type="auto">
401
+ <Box className={css.propertiesGrid} pt={'xs'}>
402
+ <ElementProperty title="description" emptyValue="no description">
403
+ {elementModel.description}
404
+ </ElementProperty>
405
+ {elementModel.technology && (
406
+ <ElementProperty title="technology">
407
+ {elementModel.technology}
408
+ </ElementProperty>
359
409
  )}
360
- <IfEnabled feature="Vscode">
361
- <Tooltip label="Open source">
362
- <ActionIcon
363
- size="lg"
364
- variant="default"
365
- radius="sm"
366
- onClick={e => {
367
- e.stopPropagation()
368
- diagram.openSource({
369
- element: elementModel.id,
370
- })
371
- }}>
372
- <IconFileSymlink stroke={1.8} style={{ width: '62%' }} />
373
- </ActionIcon>
374
- </Tooltip>
375
- </IfEnabled>
376
- {defaultView && (
377
- <Tooltip label="Open default view">
378
- <ActionIcon
379
- size="lg"
380
- variant="default"
381
- radius="sm"
382
- onClick={e => {
383
- e.stopPropagation()
384
- diagram.navigateTo(defaultView.id, fromNode ?? undefined)
385
- }}>
386
- <IconZoomScan style={{ width: '70%' }} />
387
- </ActionIcon>
388
- </Tooltip>
410
+ {elementModel.links.length > 0 && (
411
+ <>
412
+ <PropertyLabel>links</PropertyLabel>
413
+ <Stack gap={'xs'} align="flex-start">
414
+ {elementModel.links.map((link, i) => <Link key={i} value={link} />)}
415
+ </Stack>
416
+ </>
389
417
  )}
390
- </ActionIconGroup>
391
- </Group>
392
- </Box>
393
-
394
- <Tabs
395
- value={activeTab}
396
- onChange={v => setActiveTab(v as any)}
397
- variant="none"
398
- classNames={{
399
- root: css.tabsRoot,
400
- list: css.tabsList,
401
- tab: css.tabsTab,
402
- panel: css.tabsPanel,
403
- }}>
404
- <TabsList>
405
- {TABS.map(tab => (
406
- <TabsTab key={tab} value={tab}>
407
- {tab}
408
- </TabsTab>
409
- ))}
410
- </TabsList>
411
-
412
- <TabsPanel value="Properties">
413
- <ScrollArea scrollbars="y" type="auto">
414
- <Box className={css.propertiesGrid} pt={'xs'}>
415
- <ElementProperty title="description" emptyValue="no description">
416
- {elementModel.description}
417
- </ElementProperty>
418
- {elementModel.technology && (
419
- <ElementProperty title="technology">
420
- {elementModel.technology}
421
- </ElementProperty>
422
- )}
423
- {elementModel.links.length > 0 && (
424
- <>
425
- <PropertyLabel>links</PropertyLabel>
426
- <Stack gap={'xs'} align="flex-start">
427
- {elementModel.links.map((link, i) => <Link key={i} value={link} />)}
428
- </Stack>
429
- </>
430
- )}
431
- {elementModel.$element.metadata && <ElementMetata value={elementModel.$element.metadata} />}
432
- </Box>
433
- </ScrollArea>
434
- </TabsPanel>
435
-
436
- <TabsPanel value="Relationships">
437
- {nodeModel && activeTab === 'Relationships' && (
418
+ {elementModel.$element.metadata && <ElementMetata value={elementModel.$element.metadata} />}
419
+ </Box>
420
+ </ScrollArea>
421
+ </TabsPanel>
422
+
423
+ <TabsPanel value="Relationships">
424
+ <DiagramFeatures
425
+ overrides={{
426
+ enableRelationshipBrowser: false,
427
+ enableNavigateTo: false,
428
+ }}>
429
+ {!!nodeModel && activeTab === 'Relationships' && (
438
430
  <TabPanelRelationships
439
431
  element={elementModel}
440
432
  node={nodeModel}
441
433
  currentView={diagram.currentView()} />
442
434
  )}
443
- </TabsPanel>
444
-
445
- <TabsPanel value="Views">
446
- <ScrollArea scrollbars="y" type="auto">
447
- <Stack gap={'lg'}>
448
- {viewsOf.length > 0 && (
449
- <Box>
450
- <Divider label="views of the element (scoped)" />
451
- <Stack gap={'sm'}>
452
- {viewsOf.map((view) => (
453
- <ViewButton
454
- key={view.id}
455
- view={view}
456
- onNavigateTo={to => diagram.navigateTo(to, fromNode ?? undefined)}
457
- />
458
- ))}
459
- </Stack>
460
- </Box>
461
- )}
462
- {otherViews.length > 0 && (
463
- <Box>
464
- <Divider label="views including this element" />
465
- <Stack gap={'sm'}>
466
- {otherViews.map((view) => (
467
- <ViewButton
468
- key={view.id}
469
- view={view}
470
- onNavigateTo={to => diagram.navigateTo(to, fromNode ?? undefined)}
471
- />
472
- ))}
473
- </Stack>
474
- </Box>
475
- )}
476
- </Stack>
477
- </ScrollArea>
478
- </TabsPanel>
479
-
480
- <TabsPanel value="Structure">
481
- <ScrollArea scrollbars="y" type="auto">
482
- <TabPanelStructure element={elementModel} />
483
- </ScrollArea>
484
- </TabsPanel>
485
-
486
- <TabsPanel value="Deployments">
487
- <ScrollArea scrollbars="y" type="auto">
488
- <TabPanelDeployments elementFqn={elementModel.id} />
489
- </ScrollArea>
490
- </TabsPanel>
491
- </Tabs>
492
- <m.div
493
- className={css.resizeHandle}
494
- drag
495
- dragElastic={0}
496
- dragMomentum={false}
497
- onDrag={handleDrag}
498
- dragConstraints={{ top: 0, left: 0, right: 0, bottom: 0 }}
499
- />
500
- </FocusTrap>
435
+ </DiagramFeatures>
436
+ </TabsPanel>
437
+
438
+ <TabsPanel value="Views">
439
+ <ScrollArea scrollbars="y" type="auto">
440
+ <Stack gap={'lg'}>
441
+ {viewsOf.length > 0 && (
442
+ <Box>
443
+ <Divider label="views of the element (scoped)" />
444
+ <Stack gap={'sm'}>
445
+ {viewsOf.map((view) => (
446
+ <ViewButton
447
+ key={view.id}
448
+ view={view}
449
+ onNavigateTo={to => diagram.navigateTo(to, fromNode ?? undefined)} />
450
+ ))}
451
+ </Stack>
452
+ </Box>
453
+ )}
454
+ {otherViews.length > 0 && (
455
+ <Box>
456
+ <Divider label="views including this element" />
457
+ <Stack gap={'sm'}>
458
+ {otherViews.map((view) => (
459
+ <ViewButton
460
+ key={view.id}
461
+ view={view}
462
+ onNavigateTo={to => diagram.navigateTo(to, fromNode ?? undefined)} />
463
+ ))}
464
+ </Stack>
465
+ </Box>
466
+ )}
467
+ </Stack>
468
+ </ScrollArea>
469
+ </TabsPanel>
470
+
471
+ <TabsPanel value="Structure">
472
+ <ScrollArea scrollbars="y" type="auto">
473
+ <TabPanelStructure element={elementModel} />
474
+ </ScrollArea>
475
+ </TabsPanel>
476
+
477
+ <TabsPanel value="Deployments">
478
+ <ScrollArea scrollbars="y" type="auto">
479
+ <TabPanelDeployments elementFqn={elementModel.id} />
480
+ </ScrollArea>
481
+ </TabsPanel>
482
+ </Tabs>
483
+ <m.div
484
+ className={css.resizeHandle}
485
+ drag
486
+ dragElastic={0}
487
+ dragMomentum={false}
488
+ onDrag={handleDrag}
489
+ dragConstraints={{ top: 0, left: 0, right: 0, bottom: 0 }} />
501
490
  </Card>
502
491
  </RemoveScroll>
503
492
  </m.dialog>
504
493
  )
505
- })
506
- ElementDetailsCard.displayName = 'ElementDetailsCard'
507
-
508
- const ElementIcon = (
509
- { element, viewId, renderIcon: RenderIcon }: {
510
- element: ComputedNode
511
- viewId: string
512
- renderIcon: ElementIconRenderer | null
513
- },
514
- ) => {
515
- if (!element.icon || element.icon === 'none') {
516
- return null
517
- }
518
- let icon = null as React.ReactNode
519
- if (element.icon.startsWith('http://') || element.icon.startsWith('https://')) {
520
- icon = <img src={element.icon} alt={element.title} />
521
- } else if (RenderIcon) {
522
- icon = <RenderIcon node={element} />
523
- }
524
-
525
- if (!icon) {
526
- return null
527
- }
528
- return (
529
- <m.div
530
- // layout="position"
531
- layoutId={`${viewId}:element:icon:${element.id}`}
532
- className={clsx(
533
- css.elementIcon,
534
- 'likec4-element-icon',
535
- )}
536
- data-likec4-icon={element.icon}
537
- >
538
- {icon}
539
- </m.div>
540
- )
541
494
  }
542
495
 
543
496
  const ViewButton = ({
@@ -2,7 +2,7 @@ import type { Fqn, LikeC4Model } from '@likec4/core'
2
2
  import { Alert, Box, Button, Group, Menu, rem, Text, ThemeIcon, Tree, useTree } from '@mantine/core'
3
3
  import { IconInfoCircle, IconTarget, IconZoomScan } from '@tabler/icons-react'
4
4
  import { type ReactNode, memo, useEffect, useMemo } from 'react'
5
- import { useCurrentViewId } from '../../hooks/useCurrentViewId'
5
+ import { useDiagram } from '../../hooks/useDiagram'
6
6
  import { useLikeC4Model } from '../../likec4model/useLikeC4Model'
7
7
  import { stopPropagation } from '../../utils'
8
8
  import * as css from './TabPanelDeployments.css'
@@ -36,8 +36,8 @@ const DeployedInstanceRenderer = (
36
36
  instance: LikeC4Model.DeployedInstance
37
37
  },
38
38
  ) => {
39
- const currentViewId = useCurrentViewId()
40
- // const diagramApi = useDiagramStoreApi()
39
+ const diagram = useDiagram()
40
+ const currentViewId = diagram.currentView().id
41
41
  const views = [...instance.views()]
42
42
  return (
43
43
  (
@@ -91,7 +91,7 @@ const DeployedInstanceRenderer = (
91
91
  }}
92
92
  onClick={e => {
93
93
  e.stopPropagation()
94
- // diagramApi.getState().onNavigateTo?.(view.id)
94
+ diagram.navigateTo(view.id)
95
95
  }}>
96
96
  {view.title}
97
97
  </Menu.Item>