@likec4/diagram 1.20.1 → 1.20.2

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 (129) hide show
  1. package/dist/LikeC4Diagram.css.d.ts +1 -0
  2. package/dist/LikeC4Diagram.css.js +3 -1
  3. package/dist/base/BaseXYFlow.js +15 -14
  4. package/dist/base/Link.css.d.ts +3 -0
  5. package/dist/base/Link.css.js +9 -0
  6. package/dist/base/Link.d.ts +5 -0
  7. package/dist/base/Link.js +58 -0
  8. package/dist/base/const.d.ts +1 -0
  9. package/dist/base/const.js +2 -1
  10. package/dist/base/example/custom.d.ts +1 -1
  11. package/dist/base/index.d.ts +1 -0
  12. package/dist/base/primitives/compound/CompoundActionButton.js +2 -1
  13. package/dist/base/primitives/compound/CompoundDetailsButton.js +2 -1
  14. package/dist/base/primitives/compound/CompoundNodeContainer.css.d.ts +0 -1
  15. package/dist/base/primitives/compound/CompoundNodeContainer.css.js +4 -6
  16. package/dist/base/primitives/compound/CompoundNodeContainer.js +2 -2
  17. package/dist/base/primitives/customEdge.d.ts +1 -1
  18. package/dist/base/primitives/customEdge.js +1 -1
  19. package/dist/base/primitives/customNode.js +1 -1
  20. package/dist/base/primitives/edge/EdgeLabel.js +3 -1
  21. package/dist/base/primitives/edge/EdgePath.d.ts +2 -1
  22. package/dist/base/primitives/edge/EdgePath.js +2 -0
  23. package/dist/base/primitives/element/ElementActionButtons.js +2 -1
  24. package/dist/base/primitives/element/ElementDetailsButton.js +2 -1
  25. package/dist/base/primitives/element/ElementNodeContainer.css.d.ts +0 -1
  26. package/dist/base/primitives/element/ElementNodeContainer.css.js +1 -3
  27. package/dist/base/primitives/element/ElementNodeContainer.js +2 -3
  28. package/dist/base/primitives/element/ElementShape.js +4 -2
  29. package/dist/base/primitives/element/ElementTitle.js +2 -1
  30. package/dist/base/updateEdges.js +3 -3
  31. package/dist/base/updateNodes.js +2 -2
  32. package/dist/context/FramerMotionConfig.js +1 -1
  33. package/dist/hooks/useDiagram.d.ts +313 -90
  34. package/dist/hooks/useDiagramActor.d.ts +2 -2
  35. package/dist/hooks/useDiagramContext.d.ts +1 -1
  36. package/dist/hooks/useXYFlow.d.ts +2 -1
  37. package/dist/hooks/useXYFlow.js +5 -0
  38. package/dist/likec4diagram/XYFlow.js +12 -10
  39. package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +1 -1
  40. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +2 -2
  41. package/dist/likec4diagram/custom/index.d.ts +1 -1
  42. package/dist/likec4diagram/custom/nodes/nodeTypes.js +2 -2
  43. package/dist/likec4diagram/state/actorContext.d.ts +1438 -364
  44. package/dist/likec4diagram/state/assign.js +2 -2
  45. package/dist/likec4diagram/state/machine.d.ts +321 -93
  46. package/dist/likec4diagram/state/syncManualLayoutActor.d.ts +1 -1
  47. package/dist/likec4diagram/types.d.ts +1 -0
  48. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +4 -3
  49. package/dist/likec4diagram/useViewToNodesEdges.js +3 -1
  50. package/dist/overlays/element-details/ElementDetailsCard.css.d.ts +0 -1
  51. package/dist/overlays/element-details/ElementDetailsCard.css.js +1 -3
  52. package/dist/overlays/element-details/ElementDetailsCard.js +5 -30
  53. package/dist/overlays/relationship-details/RelationshipDetails.js +4 -4
  54. package/dist/overlays/relationship-details/custom/ElementActions.js +1 -3
  55. package/dist/overlays/relationship-details/custom/edgeTypes.d.ts +1 -1
  56. package/dist/overlays/relationship-details/hooks.d.ts +2 -2
  57. package/dist/overlays/relationship-details/hooks.js +7 -7
  58. package/dist/overlays/relationship-details/useLayoutedDetails.js +7 -10
  59. package/dist/overlays/relationships-browser/-useRelationshipsView.d.ts +4 -1
  60. package/dist/overlays/relationships-browser/-useRelationshipsView.js +12 -11
  61. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +81 -66
  62. package/dist/overlays/relationships-browser/_types.d.ts +2 -1
  63. package/dist/overlays/relationships-browser/actor.d.ts +15 -2
  64. package/dist/overlays/relationships-browser/actor.js +262 -65
  65. package/dist/overlays/relationships-browser/const.d.ts +1 -0
  66. package/dist/overlays/relationships-browser/const.js +4 -0
  67. package/dist/overlays/relationships-browser/custom/ElementActions.js +1 -1
  68. package/dist/overlays/relationships-browser/custom/edgeTypes.d.ts +3 -2
  69. package/dist/overlays/relationships-browser/custom/edgeTypes.js +24 -6
  70. package/dist/overlays/relationships-browser/custom/nodeTypes.js +3 -2
  71. package/dist/overlays/relationships-browser/hooks.d.ts +6 -3
  72. package/dist/overlays/relationships-browser/hooks.js +4 -2
  73. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +7 -2
  74. package/dist/style.css +1 -1
  75. package/dist/utils/xyflow.js +9 -1
  76. package/package.json +8 -8
  77. package/src/LikeC4Diagram.css.ts +8 -0
  78. package/src/base/BaseXYFlow.tsx +23 -14
  79. package/src/base/Link.css.ts +49 -0
  80. package/src/base/Link.tsx +66 -0
  81. package/src/base/const.ts +1 -0
  82. package/src/base/index.ts +2 -0
  83. package/src/base/primitives/compound/CompoundActionButton.tsx +2 -1
  84. package/src/base/primitives/compound/CompoundDetailsButton.tsx +2 -1
  85. package/src/base/primitives/compound/CompoundNodeContainer.css.ts +16 -10
  86. package/src/base/primitives/compound/CompoundNodeContainer.tsx +1 -1
  87. package/src/base/primitives/customEdge.ts +2 -0
  88. package/src/base/primitives/customNode.ts +3 -0
  89. package/src/base/primitives/edge/EdgeLabel.tsx +3 -1
  90. package/src/base/primitives/edge/EdgePath.tsx +3 -0
  91. package/src/base/primitives/element/ElementActionButtons.tsx +2 -1
  92. package/src/base/primitives/element/ElementDetailsButton.tsx +2 -1
  93. package/src/base/primitives/element/ElementNodeContainer.css.ts +9 -4
  94. package/src/base/primitives/element/ElementNodeContainer.tsx +1 -2
  95. package/src/base/primitives/element/ElementShape.tsx +4 -3
  96. package/src/base/primitives/element/ElementTitle.tsx +2 -1
  97. package/src/base/updateEdges.ts +3 -2
  98. package/src/base/updateNodes.ts +3 -2
  99. package/src/context/DiagramFeatures.tsx +1 -0
  100. package/src/context/FramerMotionConfig.tsx +1 -1
  101. package/src/hooks/useDiagramActor.ts +7 -5
  102. package/src/hooks/useDiagramContext.ts +4 -1
  103. package/src/hooks/useXYFlow.ts +6 -1
  104. package/src/likec4diagram/XYFlow.tsx +20 -9
  105. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +2 -2
  106. package/src/likec4diagram/custom/nodes/nodeTypes.tsx +2 -2
  107. package/src/likec4diagram/types.ts +1 -0
  108. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +4 -3
  109. package/src/likec4diagram/useViewToNodesEdges.ts +1 -0
  110. package/src/overlays/element-details/ElementDetailsCard.css.ts +0 -28
  111. package/src/overlays/element-details/ElementDetailsCard.tsx +2 -50
  112. package/src/overlays/relationship-details/RelationshipDetails.tsx +4 -4
  113. package/src/overlays/relationship-details/custom/ElementActions.tsx +1 -2
  114. package/src/overlays/relationship-details/custom/edgeTypes.tsx +0 -5
  115. package/src/overlays/relationship-details/hooks.ts +6 -6
  116. package/src/overlays/relationship-details/useLayoutedDetails.ts +7 -10
  117. package/src/overlays/relationships-browser/-useRelationshipsView.ts +16 -12
  118. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +105 -68
  119. package/src/overlays/relationships-browser/_types.ts +2 -1
  120. package/src/overlays/relationships-browser/actor.ts +321 -62
  121. package/src/overlays/relationships-browser/const.ts +1 -0
  122. package/src/overlays/relationships-browser/custom/ElementActions.tsx +1 -1
  123. package/src/overlays/relationships-browser/custom/edgeTypes.tsx +25 -6
  124. package/src/overlays/relationships-browser/custom/nodeTypes.tsx +3 -2
  125. package/src/overlays/relationships-browser/hooks.ts +3 -1
  126. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +8 -2
  127. package/dist/likec4diagram/ui/diagram-title/Link.js +0 -36
  128. package/dist/ui/Link.d.ts +0 -4
  129. package/src/ui/Link.tsx +0 -40
@@ -137,6 +137,7 @@ function viewToNodesEdge(opts: {
137
137
  style: node.style,
138
138
  icon: node.icon ?? null,
139
139
  position: node.position,
140
+ isMultiple: node.style?.multiple ?? false,
140
141
  } satisfies Types.LeafNodeData
141
142
 
142
143
  if (node.kind === ElementKind.Group) {
@@ -275,34 +275,6 @@ export const propertyLabel = style({
275
275
  justifySelf: 'end',
276
276
  })
277
277
 
278
- export const elementLink = style({
279
- transition: transitions.fast,
280
- border: `1px dashed ${mantine.colors.defaultBorder}`,
281
- borderRadius: mantine.radius.sm,
282
- minHeight: 30,
283
- padding: '3px 16px 3px 3px',
284
- cursor: 'pointer',
285
- color: mantine.colors.gray[7],
286
- ':hover': {
287
- transitionTimingFunction: easings.out,
288
- borderStyle: 'solid',
289
- color: mantine.colors.defaultColor,
290
- background: mantine.colors.defaultHover,
291
- },
292
- selectors: {
293
- [`${whereDark} &`]: {
294
- color: mantine.colors.dark[1],
295
- },
296
- },
297
- })
298
- globalStyle(`${elementLink} > *`, {
299
- transition: transitions.fast,
300
- })
301
- globalStyle(`${elementLink}:hover > *`, {
302
- transitionTimingFunction: easings.out,
303
- transform: 'translateX(1px)',
304
- })
305
-
306
278
  export const resizeHandle = style({
307
279
  position: 'absolute',
308
280
  width: 14,
@@ -3,7 +3,6 @@ import {
3
3
  type Element,
4
4
  type Fqn,
5
5
  type LikeC4View,
6
- type Link,
7
6
  ComputedNode,
8
7
  ComputedView,
9
8
  isDeploymentView,
@@ -43,6 +42,7 @@ import clsx from 'clsx'
43
42
  import { type PanInfo, m, useDragControls, useMotionValue } from 'framer-motion'
44
43
  import { type PropsWithChildren, memo, useCallback, useLayoutEffect, useRef } from 'react'
45
44
  import { clamp, isNullish, map, only, partition, pipe } from 'remeda'
45
+ import { Link } from '../../base'
46
46
  import { IconRenderer, IfEnabled } from '../../context'
47
47
  import { useUpdateEffect } from '../../hooks'
48
48
  import { useDiagram } from '../../hooks/useDiagram'
@@ -424,7 +424,7 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
424
424
  <>
425
425
  <PropertyLabel>links</PropertyLabel>
426
426
  <Stack gap={'xs'} align="flex-start">
427
- {elementModel.links.map((link, i) => <ElementLink key={i} value={link} />)}
427
+ {elementModel.links.map((link, i) => <Link key={i} value={link} />)}
428
428
  </Stack>
429
429
  </>
430
430
  )}
@@ -601,54 +601,6 @@ function ElementProperty({
601
601
  )
602
602
  }
603
603
 
604
- function ElementLink({
605
- value,
606
- }: {
607
- value: Link
608
- }) {
609
- const url = new URL(value.url, window.location.href).toString()
610
- return (
611
- <CopyButton value={url}>
612
- {({ copied, copy }) => (
613
- <Anchor href={url} target="_blank" underline="never" className={css.elementLink}>
614
- <Group gap={4} align="center" wrap="nowrap">
615
- <ActionIcon
616
- tabIndex={-1}
617
- size={24}
618
- variant={copied ? 'light' : 'subtle'}
619
- color={copied ? 'teal' : 'gray'}
620
- onClick={e => {
621
- e.stopPropagation()
622
- e.preventDefault()
623
- copy()
624
- }}
625
- >
626
- {copied ? <IconCheck /> : <IconCopy style={{ width: '65%', opacity: 0.8 }} />}
627
- </ActionIcon>
628
- <Box flex={1}>
629
- <Text fz={'sm'} truncate lh={1.3} fw={value.title ? 500 : 400}>
630
- {value.title || url}
631
- </Text>
632
- {value.title && (
633
- <Text component="div" fz={10} c={'dimmed'} lh={1.2} truncate>
634
- {url}
635
- </Text>
636
- )}
637
- </Box>
638
- </Group>
639
- </Anchor>
640
- )}
641
- </CopyButton>
642
- )
643
- // <Anchor href={value.url} fz={'sm'}>
644
- // {value.title || url}
645
- // </Anchor><Button variant='default' size='sm'>
646
- // {value.title || value.url}
647
- // </Button>
648
- // </Box>
649
- // )
650
- }
651
-
652
604
  function ElementMetata({
653
605
  value,
654
606
  }: {
@@ -17,7 +17,7 @@ import { nodeTypes } from './custom/nodeTypes'
17
17
  import {
18
18
  RelationshipDetailsActorContext,
19
19
  useRelationshipDetails,
20
- useRelationshipsBrowserState,
20
+ useRelationshipDetailsState,
21
21
  } from './hooks'
22
22
  import { SelectEdge } from './SelectEdge'
23
23
  import { useLayoutedDetails } from './useLayoutedDetails'
@@ -63,7 +63,7 @@ const RelationshipDetailsInner = memo(() => {
63
63
  edgeId,
64
64
  view,
65
65
  initialized,
66
- } = useRelationshipsBrowserState(selector)
66
+ } = useRelationshipDetailsState(selector)
67
67
 
68
68
  const {
69
69
  edge,
@@ -123,9 +123,9 @@ const RelationshipDetailsInner = memo(() => {
123
123
  e.stopPropagation()
124
124
  browser.send({ type: 'xyflow.paneClick' })
125
125
  })}
126
- onViewportResize={() => {
126
+ onViewportResize={useCallbackRef(() => {
127
127
  browser.send({ type: 'xyflow.resized' })
128
- }}
128
+ })}
129
129
  nodesDraggable={false}
130
130
  fitView={false}
131
131
  pannable
@@ -11,11 +11,10 @@ type ElementActionsProps = NodeProps<Types.ElementNodeData>
11
11
  export const ElementActions = (props: ElementActionsProps) => {
12
12
  const { enableNavigateTo } = useEnabledFeature('NavigateTo')
13
13
  const diagram = useDiagram()
14
- const browser = useRelationshipDetails()
15
14
 
16
15
  const buttons = [] as ElementActionButtons.Item[]
17
16
 
18
- const { navigateTo, fqn } = props.data
17
+ const { navigateTo } = props.data
19
18
  if (navigateTo && enableNavigateTo) {
20
19
  buttons.push({
21
20
  key: 'navigate',
@@ -36,11 +36,6 @@ export const edgeTypes = {
36
36
  }} />
37
37
  )}
38
38
  </EdgeLabel>
39
- {
40
- /* <EdgeLabel {...props}>
41
-
42
- </EdgeLabel> */
43
- }
44
39
  </EdgeContainer>
45
40
  )
46
41
  }),
@@ -7,21 +7,21 @@ import type { RelationshipDetailsActorRef, RelationshipDetailsSnapshot } from '.
7
7
 
8
8
  export const RelationshipDetailsActorContext = createContext<RelationshipDetailsActorRef | null>(null)
9
9
 
10
- export function useRelationshipsBrowserActor() {
11
- return nonNullable(useContext(RelationshipDetailsActorContext), 'No RelationshipsBrowserActorContext')
10
+ export function useRelationshipDetailsActor() {
11
+ return nonNullable(useContext(RelationshipDetailsActorContext), 'No RelationshipDetailsActorContext')
12
12
  }
13
13
 
14
- export function useRelationshipsBrowserState<T>(
14
+ export function useRelationshipDetailsState<T = unknown>(
15
15
  selector: (state: RelationshipDetailsSnapshot) => T,
16
- compare: (a: T, b: T) => boolean = shallowEqual,
16
+ compare: (a: NoInfer<T>, b: NoInfer<T>) => boolean = shallowEqual,
17
17
  ) {
18
18
  const select = useCallbackRef(selector)
19
- const actor = useRelationshipsBrowserActor()
19
+ const actor = useRelationshipDetailsActor()
20
20
  return useSelector(actor, select, compare)
21
21
  }
22
22
 
23
23
  export function useRelationshipDetails() {
24
- const actor = useRelationshipsBrowserActor()
24
+ const actor = useRelationshipDetailsActor()
25
25
  const [, startTransition] = useTransition()
26
26
  return useMemo(() => ({
27
27
  getState: () => actor.getSnapshot().context,
@@ -290,8 +290,8 @@ function layout(
290
290
  type: 'relationship',
291
291
  source: source.id,
292
292
  target: target.id,
293
- sourceHandle: target.id,
294
- targetHandle: source.id,
293
+ sourceHandle: source.id + '_out' + (source.data.ports.out.length - 1),
294
+ targetHandle: target.id + '_in' + (target.data.ports.in.length - 1),
295
295
  data: {
296
296
  relationId: relation.id,
297
297
  color: relation.color,
@@ -309,15 +309,12 @@ function layout(
309
309
  const nodebounds = applyDagreLayout(ctx.g)
310
310
 
311
311
  // Sort ports
312
- const sortedPorts = (ports: string[]) => {
313
- if (ports.length < 2) {
314
- return ports
315
- }
312
+ const sortedPorts = (nodeId: string, type: 'in' | 'out', ports: string[]) => {
316
313
  return pipe(
317
314
  ports,
318
- map(port => {
315
+ map((port, index) => {
319
316
  return {
320
- port,
317
+ port: nodeId + '_' + type + index,
321
318
  topY: nodebounds(port).position.y,
322
319
  }
323
320
  }),
@@ -328,8 +325,8 @@ function layout(
328
325
 
329
326
  const xynodes = [...ctx.xynodes.values()].map((node) => {
330
327
  const { position, width, height } = nodebounds(node.id, node.parentId)
331
- node.data.ports.in = sortedPorts(node.data.ports.in)
332
- node.data.ports.out = sortedPorts(node.data.ports.out)
328
+ node.data.ports.in = sortedPorts(node.id, 'in', node.data.ports.in)
329
+ node.data.ports.out = sortedPorts(node.id, 'out', node.data.ports.out)
333
330
  if (node.type === 'element') {
334
331
  node.data.width = width
335
332
  node.data.height = height
@@ -256,6 +256,8 @@ export namespace LayoutRelationshipsViewResult {
256
256
  ports: RelationshipsBrowserTypes.Ports
257
257
  }
258
258
  export type Edge = DiagramEdge & {
259
+ sourceHandle: string
260
+ targetHandle: string
259
261
  // column: RelationshipsBrowserTypes.Column
260
262
  }
261
263
  }
@@ -271,6 +273,8 @@ export function layoutRelationshipsView(data: RelationshipsViewData): LayoutRela
271
273
  name: string
272
274
  source: string // node id
273
275
  target: string // node id
276
+ sourceHandle: string
277
+ targetHandle: string
274
278
  relations: RelationshipModel[]
275
279
  }>
276
280
 
@@ -319,7 +323,9 @@ export function layoutRelationshipsView(data: RelationshipsViewData): LayoutRela
319
323
  edges.push({
320
324
  name,
321
325
  source: source.id,
326
+ sourceHandle: source.id + '_out' + (g.node(source.id).outPorts.length - 1),
322
327
  target: target.id,
328
+ targetHandle: target.id + '_in' + (g.node(target.id).inPorts.length - 1),
323
329
  relations: map(grouped, prop('relation')),
324
330
  })
325
331
  }),
@@ -446,15 +452,12 @@ export function layoutRelationshipsView(data: RelationshipsViewData): LayoutRela
446
452
  }
447
453
 
448
454
  // Sort ports in subject vertically
449
- const sortedPorts = (ports: string[]) => {
450
- if (ports.length < 2) {
451
- return ports
452
- }
455
+ const sortedPorts = (nodeId: string, type: 'in' | 'out', ports: string[]) => {
453
456
  return pipe(
454
457
  ports,
455
- map(port => {
458
+ map((port, index) => {
456
459
  return {
457
- port,
460
+ port: nodeId + '_' + type + index,
458
461
  topY: nodeBounds(port).position.y,
459
462
  }
460
463
  }),
@@ -462,7 +465,6 @@ export function layoutRelationshipsView(data: RelationshipsViewData): LayoutRela
462
465
  map(prop('port')),
463
466
  )
464
467
  }
465
-
466
468
  // In case we got negative positions
467
469
  let minX = 0
468
470
  let minY = 0
@@ -573,16 +575,16 @@ export function layoutRelationshipsView(data: RelationshipsViewData): LayoutRela
573
575
  height,
574
576
  column,
575
577
  ports: {
576
- in: sortedPorts(inPorts),
577
- out: sortedPorts(outPorts),
578
+ in: sortedPorts(id, 'in', inPorts),
579
+ out: sortedPorts(id, 'out', outPorts),
578
580
  },
579
581
  }
580
582
  })
581
583
 
582
584
  return {
583
585
  bounds: {
584
- x: minX,
585
- y: minY,
586
+ x: Math.min(minX, 0),
587
+ y: Math.min(minY, 0),
586
588
  width: g.graph().width ?? 100,
587
589
  height: g.graph().height ?? 100,
588
590
  },
@@ -593,7 +595,7 @@ export function layoutRelationshipsView(data: RelationshipsViewData): LayoutRela
593
595
  if (!ename) {
594
596
  return acc
595
597
  }
596
- const { name, source, target, relations } = find(edges, e => e.name === ename)!
598
+ const { name, source, target, relations, sourceHandle, targetHandle } = find(edges, e => e.name === ename)!
597
599
  const onlyRelation = only(relations)
598
600
  const label = onlyRelation?.title ?? 'untitled'
599
601
  const isMultiple = relations.length > 1
@@ -607,7 +609,9 @@ export function layoutRelationshipsView(data: RelationshipsViewData): LayoutRela
607
609
  acc.push({
608
610
  id: name as EdgeId,
609
611
  source: source as NodeId,
612
+ sourceHandle,
610
613
  target: target as NodeId,
614
+ targetHandle,
611
615
  label: isMultiple ? `${relations.length} relationships` : label,
612
616
  relations: relations.map(r => r.id),
613
617
  parent: null,
@@ -2,15 +2,17 @@ import type { Fqn } from '@likec4/core'
2
2
  import { ActionIcon, Box, Group } from '@mantine/core'
3
3
  import { useCallbackRef, useStateHistory } from '@mantine/hooks'
4
4
  import { IconChevronLeft, IconChevronRight, IconX } from '@tabler/icons-react'
5
- import { Panel, ReactFlowProvider } from '@xyflow/react'
5
+ import { Panel, ReactFlowProvider, useStoreApi } from '@xyflow/react'
6
6
  import clsx from 'clsx'
7
+ import { shallowEqual } from 'fast-equals'
7
8
  import { AnimatePresence, LayoutGroup, m } from 'framer-motion'
8
9
  import { memo, useEffect, useRef } from 'react'
9
10
  import type { SnapshotFrom } from 'xstate'
10
11
  import { BaseXYFlow } from '../../base/BaseXYFlow'
11
12
  import { useRelationshipsView } from './-useRelationshipsView'
12
- import type { RelationshipsBrowserTypes as Types } from './_types'
13
+ import type { RelationshipsBrowserTypes, RelationshipsBrowserTypes as Types } from './_types'
13
14
  import type { RelationshipsBrowserActorRef } from './actor'
15
+ import { ViewPadding } from './const'
14
16
  import { edgeTypes } from './custom/edgeTypes'
15
17
  import { nodeTypes } from './custom/nodeTypes'
16
18
  import {
@@ -29,69 +31,56 @@ export function RelationshipsBrowser({ actorRef }: RelationshipsBrowserProps) {
29
31
  // return null
30
32
  // }
31
33
  const initialRef = useRef<{
32
- defaultNodes: Types.Node[]
33
- defaultEdges: Types.Edge[]
34
+ initialNodes: Types.Node[]
35
+ initialEdges: Types.Edge[]
34
36
  }>(null)
35
37
 
36
38
  if (initialRef.current == null) {
37
39
  initialRef.current = {
38
- defaultNodes: [],
39
- defaultEdges: [],
40
+ initialNodes: [],
41
+ initialEdges: [],
40
42
  }
41
43
  }
42
44
 
43
45
  return (
44
46
  <RelationshipsBrowserActorContext.Provider value={actorRef}>
45
47
  <ReactFlowProvider {...initialRef.current}>
46
- <RelationshipsBrowserInner />
48
+ <RelationshipsBrowserXYFlow />
47
49
  </ReactFlowProvider>
48
50
  </RelationshipsBrowserActorContext.Provider>
49
51
  )
50
52
  }
51
53
 
52
54
  const selector = (state: SnapshotFrom<RelationshipsBrowserActorRef>) => ({
53
- subjectId: state.context.subject,
54
55
  initialized: state.context.initialized,
55
- scope: state.context.scope,
56
+ isActive: state.hasTag('active'),
56
57
  nodes: state.context.xynodes,
57
58
  edges: state.context.xyedges,
58
- closeable: state.context.closeable,
59
- enableNavigationMenu: state.context.enableNavigationMenu,
60
59
  })
60
+ const selectorEq = (a: ReturnType<typeof selector>, b: ReturnType<typeof selector>) =>
61
+ a.initialized === b.initialized &&
62
+ a.isActive === b.isActive &&
63
+ shallowEqual(a.nodes, b.nodes) &&
64
+ shallowEqual(a.edges, b.edges)
61
65
 
62
- const RelationshipsBrowserInner = memo(() => {
66
+ const RelationshipsBrowserXYFlow = memo(() => {
67
+ const xystore = useStoreApi<RelationshipsBrowserTypes.Node, RelationshipsBrowserTypes.Edge>()
63
68
  const browser = useRelationshipsBrowser()
64
69
  const {
65
- subjectId,
66
70
  initialized,
67
- scope,
71
+ isActive,
68
72
  nodes,
69
73
  edges,
70
- closeable,
71
- enableNavigationMenu,
72
- } = useRelationshipsBrowserState(selector)
73
-
74
- const layouted = useRelationshipsView(subjectId)
75
- const [historySubjectId, historyOps, { history, current }] = useStateHistory(subjectId)
76
-
77
- useEffect(() => {
78
- if (historySubjectId !== subjectId) {
79
- historyOps.set(subjectId)
80
- }
81
- }, [subjectId])
82
-
83
- useEffect(() => {
84
- if (historySubjectId !== subjectId) {
85
- browser.navigateTo(historySubjectId)
86
- }
87
- }, [historySubjectId])
74
+ } = useRelationshipsBrowserState(
75
+ selector,
76
+ selectorEq,
77
+ )
88
78
 
89
- useEffect(() => {
90
- browser.send({ type: 'update.view', layouted })
91
- }, [layouted])
92
-
93
- const hasStepBack = current > 0
94
- const hasStepForward = current + 1 < history.length
79
+ // useLifecycleLogger('RelationshipsBrowserXYFlow.browser', [browser])
80
+ // useLifecycleLogger('RelationshipsBrowserXYFlow.xystore', [xystore])
81
+ // useLifecycleLogger('RelationshipsBrowserXYFlow.initialized, isActive', [initialized, isActive])
82
+ // useLifecycleLogger('RelationshipsBrowserXYFlow.nodes', [nodes])
83
+ // useLifecycleLogger('RelationshipsBrowserXYFlow.edges', [edges])
95
84
 
96
85
  return (
97
86
  <LayoutGroup>
@@ -99,12 +88,12 @@ const RelationshipsBrowserInner = memo(() => {
99
88
  id="relationships-browser"
100
89
  nodes={nodes}
101
90
  edges={edges}
102
- className={clsx(initialized ? 'initialized' : 'not-initialized')}
91
+ className={clsx(initialized && isActive ? 'initialized' : 'not-initialized')}
103
92
  nodeTypes={nodeTypes}
104
93
  edgeTypes={edgeTypes}
105
- fitViewPadding={0.05}
94
+ fitViewPadding={ViewPadding}
106
95
  onInit={useCallbackRef((instance) => {
107
- browser.send({ type: 'xyflow.init', instance })
96
+ browser.send({ type: 'xyflow.init', instance, store: xystore })
108
97
  })}
109
98
  onNodeClick={useCallbackRef((e, node) => {
110
99
  e.stopPropagation()
@@ -122,9 +111,9 @@ const RelationshipsBrowserInner = memo(() => {
122
111
  e.stopPropagation()
123
112
  browser.send({ type: 'xyflow.paneDblClick' })
124
113
  })}
125
- onViewportResize={() => {
114
+ onViewportResize={useCallbackRef(() => {
126
115
  browser.send({ type: 'xyflow.resized' })
127
- }}
116
+ })}
128
117
  onNodesChange={useCallbackRef((changes) => {
129
118
  browser.send({ type: 'xyflow.applyNodeChages', changes })
130
119
  })}
@@ -136,36 +125,84 @@ const RelationshipsBrowserInner = memo(() => {
136
125
  pannable
137
126
  zoomable
138
127
  >
139
- <TopLeftPanel
140
- enableNavigationMenu={enableNavigationMenu}
141
- subjectId={subjectId}
142
- hasStepBack={hasStepBack}
143
- hasStepForward={hasStepForward}
144
- onStepBack={() => historyOps.back()}
145
- onStepForward={() => historyOps.forward()}
146
- />
147
- {closeable && (
148
- <Panel position="top-right">
149
- <ActionIcon
150
- variant="default"
151
- color="gray"
152
- // color="gray"
153
- // size={'lg'}
154
- // data-autofocus
155
- // autoFocus
156
- onClick={(e) => {
157
- e.stopPropagation()
158
- browser.close()
159
- }}>
160
- <IconX />
161
- </ActionIcon>
162
- </Panel>
163
- )}
128
+ {initialized && <RelationshipsBrowserInner />}
164
129
  </BaseXYFlow>
165
130
  </LayoutGroup>
166
131
  )
167
132
  })
168
133
 
134
+ const selector2 = (state: SnapshotFrom<RelationshipsBrowserActorRef>) => ({
135
+ subjectId: state.context.subject,
136
+ closeable: state.context.closeable,
137
+ enableNavigationMenu: state.context.enableNavigationMenu,
138
+ })
139
+
140
+ const RelationshipsBrowserInner = memo(() => {
141
+ const browser = useRelationshipsBrowser()
142
+ const {
143
+ subjectId,
144
+ closeable,
145
+ enableNavigationMenu,
146
+ } = useRelationshipsBrowserState(selector2)
147
+
148
+ const layouted = useRelationshipsView(subjectId)
149
+ const [historySubjectId, historyOps, { history, current }] = useStateHistory(subjectId)
150
+
151
+ useEffect(() => {
152
+ if (historySubjectId !== subjectId) {
153
+ historyOps.set(subjectId)
154
+ }
155
+ }, [subjectId])
156
+
157
+ useEffect(() => {
158
+ if (historySubjectId !== subjectId) {
159
+ browser.navigateTo(historySubjectId)
160
+ }
161
+ }, [historySubjectId])
162
+
163
+ useEffect(() => {
164
+ browser.send({ type: 'update.view', layouted })
165
+ }, [layouted])
166
+
167
+ const hasStepBack = current > 0
168
+ const hasStepForward = current + 1 < history.length
169
+
170
+ // useLifecycleLogger('RelationshipsBrowserInner.browser', [browser])
171
+ // useLifecycleLogger('RelationshipsBrowserInner.layouted', [layouted])
172
+ // useLifecycleLogger('RelationshipsBrowserInner', [
173
+ // historySubjectId,
174
+ // subjectId,
175
+ // closeable,
176
+ // enableNavigationMenu,
177
+ // ])
178
+
179
+ return (
180
+ <>
181
+ <TopLeftPanel
182
+ enableNavigationMenu={enableNavigationMenu}
183
+ subjectId={subjectId}
184
+ hasStepBack={hasStepBack}
185
+ hasStepForward={hasStepForward}
186
+ onStepBack={() => historyOps.back()}
187
+ onStepForward={() => historyOps.forward()}
188
+ />
189
+ {closeable && (
190
+ <Panel position="top-right">
191
+ <ActionIcon
192
+ variant="default"
193
+ color="gray"
194
+ onClick={(e) => {
195
+ e.stopPropagation()
196
+ browser.close()
197
+ }}>
198
+ <IconX />
199
+ </ActionIcon>
200
+ </Panel>
201
+ )}
202
+ </>
203
+ )
204
+ })
205
+
169
206
  type TopLeftPanelProps = {
170
207
  enableNavigationMenu: boolean
171
208
  subjectId: Fqn
@@ -1,4 +1,4 @@
1
- import type { Color, DiagramNode, Fqn, RelationshipLineType, ViewId } from '@likec4/core'
1
+ import type { Color, DiagramNode, Fqn, NonEmptyArray, RelationId, RelationshipLineType, ViewId } from '@likec4/core'
2
2
  import type { Base, NonOptional, ReactFlowEdge, ReactFlowNode } from '../../base/types'
3
3
 
4
4
  export namespace RelationshipsBrowserTypes {
@@ -79,6 +79,7 @@ export namespace RelationshipsBrowserTypes {
79
79
  // Extend the edge types provided by SharedFlowTypes with RelationshipsOfEdgeData
80
80
 
81
81
  export type EdgeData = Base.EdgeData<{
82
+ relations: NonEmptyArray<RelationId>
82
83
  color: Color | undefined
83
84
  label: string | null
84
85
  navigateTo: ViewId | null