@likec4/diagram 1.20.1 → 1.20.3

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 (134) 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/useLayoutConstraints.d.ts +2 -1
  50. package/dist/likec4diagram/useLayoutConstraints.js +14 -15
  51. package/dist/likec4diagram/useViewToNodesEdges.js +3 -1
  52. package/dist/overlays/element-details/ElementDetailsCard.css.d.ts +0 -1
  53. package/dist/overlays/element-details/ElementDetailsCard.css.js +1 -3
  54. package/dist/overlays/element-details/ElementDetailsCard.js +5 -30
  55. package/dist/overlays/element-details/TabPanelRelationships.js +2 -2
  56. package/dist/overlays/relationship-details/RelationshipDetails.js +4 -4
  57. package/dist/overlays/relationship-details/custom/ElementActions.js +1 -3
  58. package/dist/overlays/relationship-details/custom/edgeTypes.d.ts +1 -1
  59. package/dist/overlays/relationship-details/hooks.d.ts +2 -2
  60. package/dist/overlays/relationship-details/hooks.js +7 -7
  61. package/dist/overlays/relationship-details/useLayoutedDetails.js +7 -10
  62. package/dist/overlays/relationships-browser/-useRelationshipsView.d.ts +4 -1
  63. package/dist/overlays/relationships-browser/-useRelationshipsView.js +12 -11
  64. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +81 -66
  65. package/dist/overlays/relationships-browser/_types.d.ts +2 -1
  66. package/dist/overlays/relationships-browser/actor.d.ts +15 -2
  67. package/dist/overlays/relationships-browser/actor.js +262 -65
  68. package/dist/overlays/relationships-browser/const.d.ts +1 -0
  69. package/dist/overlays/relationships-browser/const.js +4 -0
  70. package/dist/overlays/relationships-browser/custom/ElementActions.js +13 -3
  71. package/dist/overlays/relationships-browser/custom/edgeTypes.d.ts +3 -2
  72. package/dist/overlays/relationships-browser/custom/edgeTypes.js +24 -6
  73. package/dist/overlays/relationships-browser/custom/nodeTypes.js +3 -2
  74. package/dist/overlays/relationships-browser/hooks.d.ts +6 -3
  75. package/dist/overlays/relationships-browser/hooks.js +4 -2
  76. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +7 -2
  77. package/dist/style.css +1 -1
  78. package/dist/utils/xyflow.js +9 -1
  79. package/package.json +10 -10
  80. package/src/LikeC4Diagram.css.ts +8 -0
  81. package/src/base/BaseXYFlow.tsx +23 -14
  82. package/src/base/Link.css.ts +49 -0
  83. package/src/base/Link.tsx +66 -0
  84. package/src/base/const.ts +1 -0
  85. package/src/base/index.ts +2 -0
  86. package/src/base/primitives/compound/CompoundActionButton.tsx +2 -1
  87. package/src/base/primitives/compound/CompoundDetailsButton.tsx +2 -1
  88. package/src/base/primitives/compound/CompoundNodeContainer.css.ts +16 -10
  89. package/src/base/primitives/compound/CompoundNodeContainer.tsx +1 -1
  90. package/src/base/primitives/customEdge.ts +2 -0
  91. package/src/base/primitives/customNode.ts +3 -0
  92. package/src/base/primitives/edge/EdgeLabel.tsx +3 -1
  93. package/src/base/primitives/edge/EdgePath.tsx +3 -0
  94. package/src/base/primitives/element/ElementActionButtons.tsx +2 -1
  95. package/src/base/primitives/element/ElementDetailsButton.tsx +2 -1
  96. package/src/base/primitives/element/ElementNodeContainer.css.ts +9 -4
  97. package/src/base/primitives/element/ElementNodeContainer.tsx +1 -2
  98. package/src/base/primitives/element/ElementShape.tsx +4 -3
  99. package/src/base/primitives/element/ElementTitle.tsx +2 -1
  100. package/src/base/updateEdges.ts +3 -2
  101. package/src/base/updateNodes.ts +3 -2
  102. package/src/context/DiagramFeatures.tsx +1 -0
  103. package/src/context/FramerMotionConfig.tsx +1 -1
  104. package/src/hooks/useDiagramActor.ts +7 -5
  105. package/src/hooks/useDiagramContext.ts +4 -1
  106. package/src/hooks/useXYFlow.ts +6 -1
  107. package/src/likec4diagram/XYFlow.tsx +20 -9
  108. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +2 -2
  109. package/src/likec4diagram/custom/nodes/nodeTypes.tsx +2 -2
  110. package/src/likec4diagram/types.ts +1 -0
  111. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +4 -3
  112. package/src/likec4diagram/useLayoutConstraints.ts +23 -19
  113. package/src/likec4diagram/useViewToNodesEdges.ts +1 -0
  114. package/src/overlays/element-details/ElementDetailsCard.css.ts +0 -28
  115. package/src/overlays/element-details/ElementDetailsCard.tsx +2 -50
  116. package/src/overlays/element-details/TabPanelRelationships.tsx +2 -2
  117. package/src/overlays/relationship-details/RelationshipDetails.tsx +4 -4
  118. package/src/overlays/relationship-details/custom/ElementActions.tsx +1 -2
  119. package/src/overlays/relationship-details/custom/edgeTypes.tsx +0 -5
  120. package/src/overlays/relationship-details/hooks.ts +6 -6
  121. package/src/overlays/relationship-details/useLayoutedDetails.ts +7 -10
  122. package/src/overlays/relationships-browser/-useRelationshipsView.ts +16 -12
  123. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +105 -68
  124. package/src/overlays/relationships-browser/_types.ts +2 -1
  125. package/src/overlays/relationships-browser/actor.ts +321 -62
  126. package/src/overlays/relationships-browser/const.ts +1 -0
  127. package/src/overlays/relationships-browser/custom/ElementActions.tsx +13 -3
  128. package/src/overlays/relationships-browser/custom/edgeTypes.tsx +25 -6
  129. package/src/overlays/relationships-browser/custom/nodeTypes.tsx +3 -2
  130. package/src/overlays/relationships-browser/hooks.ts +3 -1
  131. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +8 -2
  132. package/dist/likec4diagram/ui/diagram-title/Link.js +0 -36
  133. package/dist/ui/Link.d.ts +0 -4
  134. package/src/ui/Link.tsx +0 -40
@@ -39,7 +39,7 @@ const ElementDetailsButtonWithHandler = ({ fqn, ...props }: NodeProps<Types.Node
39
39
  export const nodeTypes = {
40
40
  element: customNode<Types.ElementNodeData>((props) => (
41
41
  <ElementNodeContainer nodeProps={props}>
42
- <ElementShape {...props} />
42
+ <ElementShape {...props} isMultiple={props.data.isMultiple} />
43
43
  <ElementTitle {...props} />
44
44
  <ElementActions {...props} />
45
45
  <ElementDetailsButtonWithHandler
@@ -53,7 +53,7 @@ export const nodeTypes = {
53
53
  )),
54
54
  deployment: customNode<Types.DeploymentElementNodeData>((props) => (
55
55
  <ElementNodeContainer nodeProps={props}>
56
- <ElementShape {...props} />
56
+ <ElementShape {...props} isMultiple={props.data.isMultiple} />
57
57
  <ElementTitle {...props} />
58
58
  <DeploymentElementActions {...props} />
59
59
  {!!props.data.modelFqn && (
@@ -32,6 +32,7 @@ export namespace Types {
32
32
  >
33
33
  >
34
34
  & {
35
+ isMultiple?: boolean | undefined
35
36
  icon: string | null
36
37
  }
37
38
  >
@@ -3,10 +3,10 @@ import { useLocalStorage } from '@mantine/hooks'
3
3
  import { IconMenu } from '@tabler/icons-react'
4
4
  import clsx from 'clsx'
5
5
  import { AnimatePresence, m } from 'framer-motion'
6
+ import { Link } from '../../../base'
6
7
  import { useDiagramContext } from '../../../hooks/useDiagramContext'
7
8
  import type { DiagramContext } from '../../state/machine'
8
9
  import * as css from './DiagramTitlePanel.css'
9
- import { Link } from './Link'
10
10
 
11
11
  const selector = (s: DiagramContext) => ({
12
12
  id: s.view.id,
@@ -150,10 +150,11 @@ export function DiagramTitlePanel() {
150
150
  )}
151
151
  {links && (
152
152
  <Stack
153
- gap={3}
153
+ mt={'xs'}
154
+ gap={4}
154
155
  justify="stretch"
155
156
  align="stretch">
156
- {links.map((link) => <Link link={link} key={link.url} />)}
157
+ {links.map((link) => <Link value={link} key={link.url} />)}
157
158
  </Stack>
158
159
  )}
159
160
  </>
@@ -60,42 +60,46 @@ abstract class Rect {
60
60
  }
61
61
  }
62
62
  protected abstract parent: Compound | null
63
- }
64
-
65
- class Compound extends Rect {
66
- public readonly children = [] as Rect[]
67
63
 
68
64
  constructor(
69
65
  xynode: InternalNode,
70
- protected readonly parent: Compound | null = null,
66
+ parent: Compound | null = null,
71
67
  ) {
72
- super()
73
68
  this.id = xynode.id
74
69
 
70
+ this.positionAbsolute = !parent ? xynode.position : {
71
+ x: xynode.position.x + parent.minX,
72
+ y: xynode.position.y + parent.minY,
73
+ }
74
+
75
+ const { width, height } = getNodeDimensions(xynode)
76
+
77
+ this.maxX = this.minX + Math.ceil(width)
78
+ this.maxY = this.minY + Math.ceil(height)
79
+
75
80
  if (parent) {
76
81
  parent.children.push(this)
77
82
  }
78
83
  }
79
84
  }
80
85
 
81
- class Leaf extends Rect {
86
+ class Compound extends Rect {
87
+ public readonly children = [] as Rect[]
88
+
82
89
  constructor(
83
90
  xynode: InternalNode,
84
91
  public readonly parent: Compound | null = null,
85
92
  ) {
86
- super()
87
- this.id = xynode.id
88
-
89
- this.positionAbsolute = xynode.internals.positionAbsolute
90
-
91
- const { width, height } = getNodeDimensions(xynode)
92
-
93
- this.maxX = this.minX + Math.ceil(width)
94
- this.maxY = this.minY + Math.ceil(height)
93
+ super(xynode, parent)
94
+ }
95
+ }
95
96
 
96
- if (parent) {
97
- parent.children.push(this)
98
- }
97
+ class Leaf extends Rect {
98
+ constructor(
99
+ xynode: InternalNode,
100
+ public readonly parent: Compound | null = null,
101
+ ) {
102
+ super(xynode, parent)
99
103
  }
100
104
  }
101
105
 
@@ -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
  }: {
@@ -28,8 +28,8 @@ export function TabPanelRelationships({
28
28
  const incoming = [...element.incoming()].map(r => r.id)
29
29
  const outgoing = [...element.outgoing()].map(r => r.id)
30
30
 
31
- const incomingInView = unique(node.incoming().flatMap(e => e.$edge.relations).toArray())
32
- const outgoingInView = unique(node.outgoing().flatMap(e => e.$edge.relations).toArray())
31
+ const incomingInView = unique([...node.incoming()].flatMap(e => e.$edge.relations))
32
+ const outgoingInView = unique([...node.outgoing()].flatMap(e => e.$edge.relations))
33
33
 
34
34
  const notIncludedRelations = [
35
35
  ...incoming,
@@ -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,