@likec4/diagram 1.17.0 → 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 +24 -18
- package/src/LikeC4Diagram.props.ts +27 -12
- package/src/LikeC4Diagram.tsx +14 -9
- package/src/LikeC4Search.tsx +149 -0
- package/src/StaticLikeC4Diagram.tsx +1 -0
- 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 +94 -81
- 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/aligners.ts +473 -0
- package/src/state/diagramStore.layout.ts +67 -0
- package/src/state/diagramStore.ts +31 -77
- 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/ManualLayoutToolsButton.tsx +15 -0
- package/src/ui/top-left/TopLeftPanel.tsx +16 -3
- 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 +153 -92
- package/src/xyflow/nodes/element/element.css.ts +53 -11
- 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
|
@@ -7,27 +7,27 @@ import * as css from './TabPanelStructure.css'
|
|
|
7
7
|
interface ElementTreeNodeData {
|
|
8
8
|
label: ReactNode
|
|
9
9
|
value: string
|
|
10
|
-
element: LikeC4Model.
|
|
10
|
+
element: LikeC4Model.Element
|
|
11
11
|
type: 'ancestor' | 'current' | 'descedant'
|
|
12
12
|
children: TreeNodeData[]
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
interface MessageNodeData {
|
|
15
|
+
export interface MessageNodeData {
|
|
16
16
|
label: ReactNode
|
|
17
17
|
value: string
|
|
18
18
|
type: 'message'
|
|
19
19
|
children: TreeNodeData[]
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
type TreeNodeData = ElementTreeNodeData | MessageNodeData
|
|
22
|
+
export type TreeNodeData = ElementTreeNodeData | MessageNodeData
|
|
23
23
|
|
|
24
|
-
type
|
|
25
|
-
element: LikeC4Model.
|
|
24
|
+
type TabPanelStructureProps = {
|
|
25
|
+
element: LikeC4Model.Element
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const ElementLabel = ({
|
|
28
|
+
export const ElementLabel = ({
|
|
29
29
|
element
|
|
30
|
-
}: { element: LikeC4Model.
|
|
30
|
+
}: { element: LikeC4Model.Element; type: 'ancestor' | 'current' | 'descedant' }) => (
|
|
31
31
|
<Box className={css.elementLabel}>
|
|
32
32
|
<Text component="div" fz={'sm'} fw={'500'}>{element.title}</Text>
|
|
33
33
|
</Box>
|
|
@@ -38,9 +38,9 @@ const ElementLabel = ({
|
|
|
38
38
|
// }: RenderTreeNodePayload) => {
|
|
39
39
|
// }
|
|
40
40
|
|
|
41
|
-
export function
|
|
41
|
+
export function TabPanelStructure({
|
|
42
42
|
element
|
|
43
|
-
}:
|
|
43
|
+
}: TabPanelStructureProps) {
|
|
44
44
|
const tree = useTree({
|
|
45
45
|
multiple: false
|
|
46
46
|
})
|
|
@@ -60,7 +60,7 @@ export function TabElementStructure({
|
|
|
60
60
|
value: element.id,
|
|
61
61
|
element,
|
|
62
62
|
type: 'current',
|
|
63
|
-
children: element.children().map((child): TreeNodeData => ({
|
|
63
|
+
children: [...element.children()].map((child): TreeNodeData => ({
|
|
64
64
|
label: <ElementLabel type="descedant" element={child} />,
|
|
65
65
|
value: child.id,
|
|
66
66
|
element: child,
|
|
@@ -93,7 +93,7 @@ export const RelationshipsOverlay = memo<{ subjectId: Fqn }>(function Relationsh
|
|
|
93
93
|
<Box flex={'1 0 auto'}>
|
|
94
94
|
<SelectElement
|
|
95
95
|
scope={scope}
|
|
96
|
-
subject={subject
|
|
96
|
+
subject={subject.$element}
|
|
97
97
|
onSelect={fqn =>
|
|
98
98
|
overlay.openOverlay({
|
|
99
99
|
relationshipsOf: fqn
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { delay, type DiagramView, type Fqn, isAncestor, type Relation } from '@likec4/core'
|
|
2
|
-
import {
|
|
3
|
-
import { useId, useLocalStorage, useStateHistory } from '@mantine/hooks'
|
|
2
|
+
import { useId } from '@mantine/hooks'
|
|
4
3
|
import { useDebouncedCallback, useDeepCompareEffect, useSyncedRef } from '@react-hookz/web'
|
|
5
|
-
import { IconArrowLeft, IconArrowRight } from '@tabler/icons-react'
|
|
6
4
|
import {
|
|
7
|
-
Background,
|
|
8
|
-
BackgroundVariant,
|
|
9
5
|
getViewportForBounds,
|
|
10
|
-
Panel,
|
|
11
6
|
ReactFlow,
|
|
12
7
|
type ReactFlowInstance,
|
|
13
8
|
type ReactFlowProps,
|
|
@@ -19,16 +14,13 @@ import {
|
|
|
19
14
|
useStoreApi
|
|
20
15
|
} from '@xyflow/react'
|
|
21
16
|
import { getNodeDimensions } from '@xyflow/system'
|
|
22
|
-
import {
|
|
17
|
+
import { type PropsWithChildren, useRef, useState } from 'react'
|
|
23
18
|
import { isNullish, map, omit, only, prop, setPath, unique } from 'remeda'
|
|
24
19
|
import { useUpdateEffect } from '../../hooks'
|
|
25
|
-
import { useDiagramStoreApi } from '../../hooks/useDiagramState'
|
|
26
20
|
import { centerXYInternalNode } from '../../xyflow/utils'
|
|
27
|
-
import { useOverlayDialog } from '../OverlayContext'
|
|
28
21
|
import { cssReactflowMarker } from '../Overlays.css'
|
|
29
22
|
import type { XYFlowTypes } from './_types'
|
|
30
|
-
import {
|
|
31
|
-
import { useLayoutedRelationships, ZIndexes } from './use-layouted-relationships'
|
|
23
|
+
import { ZIndexes } from './use-layouted-relationships'
|
|
32
24
|
import { CompoundNode } from './xyflow/CompoundNode'
|
|
33
25
|
import { ElementNode } from './xyflow/ElementNode'
|
|
34
26
|
import { EmptyNode } from './xyflow/EmptyNode'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ancestorsFqn, type Element, type Fqn, type
|
|
1
|
+
import { ancestorsFqn, type Element, type Fqn, type ViewId } from '@likec4/core'
|
|
2
2
|
import {
|
|
3
3
|
ActionIcon,
|
|
4
4
|
Box,
|
|
@@ -25,7 +25,7 @@ export const SelectElement = ({
|
|
|
25
25
|
onSelect
|
|
26
26
|
}: {
|
|
27
27
|
subject: Element
|
|
28
|
-
viewId:
|
|
28
|
+
viewId: ViewId
|
|
29
29
|
scope: 'global' | 'view'
|
|
30
30
|
onSelect: (id: Fqn) => void
|
|
31
31
|
}) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComputedNode, Fqn, Relation,
|
|
1
|
+
import type { ComputedNode, Fqn, Relation, ViewId } from '@likec4/core'
|
|
2
2
|
import type {
|
|
3
3
|
Edge as ReactFlowEdge,
|
|
4
4
|
InternalNode as ReactFlowInternalNode,
|
|
@@ -26,7 +26,7 @@ export namespace XYFlowTypes {
|
|
|
26
26
|
left: Port[]
|
|
27
27
|
right: Port[]
|
|
28
28
|
}
|
|
29
|
-
navigateTo:
|
|
29
|
+
navigateTo: ViewId | null
|
|
30
30
|
hovered?: boolean
|
|
31
31
|
layoutId?: string
|
|
32
32
|
leaving?: boolean
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type LikeC4Model,
|
|
12
12
|
nonNullable,
|
|
13
13
|
parentFqn,
|
|
14
|
-
type
|
|
14
|
+
type RelationId
|
|
15
15
|
} from '@likec4/core'
|
|
16
16
|
import { MarkerType } from '@xyflow/system'
|
|
17
17
|
import { useMemo } from 'react'
|
|
@@ -118,15 +118,12 @@ function createGraph() {
|
|
|
118
118
|
return g
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
type LikeC4ModelElement = LikeC4Model.ElementModel
|
|
122
|
-
// type LikeC4ModelElement = LikeC4ViewModel.Element
|
|
123
|
-
|
|
124
121
|
type Context = {
|
|
125
122
|
scope: 'global' | 'view'
|
|
126
123
|
g: dagre.graphlib.Graph
|
|
127
124
|
subjectId: Fqn
|
|
128
125
|
diagramNodes: Map<Fqn, DiagramNode>
|
|
129
|
-
subjectElement:
|
|
126
|
+
subjectElement: LikeC4Model.Element
|
|
130
127
|
// likec4model: LikeC4Model
|
|
131
128
|
// subject: XYFlowTypes.ElementNode
|
|
132
129
|
// Model
|
|
@@ -157,7 +154,7 @@ const graphId = (node: XYFlowTypes.Node) => ({
|
|
|
157
154
|
})
|
|
158
155
|
|
|
159
156
|
function nodeData(
|
|
160
|
-
element: LikeC4Model.
|
|
157
|
+
element: LikeC4Model.Element,
|
|
161
158
|
ctx: Context
|
|
162
159
|
): Omit<XYFlowTypes.NonEmptyNode['data'], 'column'> {
|
|
163
160
|
// We try to inherit style from existing diagram node
|
|
@@ -167,7 +164,7 @@ function nodeData(
|
|
|
167
164
|
// color from it if there is no diagram node
|
|
168
165
|
const ancestor = diagramNode ?? (ctx.scope === 'view'
|
|
169
166
|
? pipe(
|
|
170
|
-
element.ancestors(),
|
|
167
|
+
element.ancestors().toArray(),
|
|
171
168
|
map(ancestor => ctx.diagramNodes.get(ancestor.id)),
|
|
172
169
|
filter(isTruthy),
|
|
173
170
|
first()
|
|
@@ -180,11 +177,11 @@ function nodeData(
|
|
|
180
177
|
element: {
|
|
181
178
|
kind: element.kind,
|
|
182
179
|
title: diagramNode?.title ?? element.title,
|
|
183
|
-
description: diagramNode?.description ?? element.
|
|
180
|
+
description: diagramNode?.description ?? element.description,
|
|
184
181
|
color: diagramNode?.color ?? ancestor?.color ?? element.color,
|
|
185
182
|
shape: diagramNode?.shape ?? element.shape
|
|
186
183
|
},
|
|
187
|
-
navigateTo: diagramNode?.navigateTo ??
|
|
184
|
+
navigateTo: diagramNode?.navigateTo ?? element.defaultView?.id ?? null,
|
|
188
185
|
ports: {
|
|
189
186
|
left: [],
|
|
190
187
|
right: []
|
|
@@ -228,7 +225,7 @@ function createEmptyNode(
|
|
|
228
225
|
function createNode(
|
|
229
226
|
column: ColumnKey,
|
|
230
227
|
nodeType: Exclude<XYFlowTypes.Node['type'], 'empty'>,
|
|
231
|
-
element: LikeC4Model.
|
|
228
|
+
element: LikeC4Model.Element,
|
|
232
229
|
ctx: Context,
|
|
233
230
|
depth: number = 0
|
|
234
231
|
): XYFlowTypes.ElementNode | XYFlowTypes.CompoundNode {
|
|
@@ -245,7 +242,7 @@ function createNode(
|
|
|
245
242
|
|
|
246
243
|
// Create parent node
|
|
247
244
|
const parent = pipe(
|
|
248
|
-
element.ancestors(),
|
|
245
|
+
element.ancestors().toArray(),
|
|
249
246
|
takeWhile(ancestor => !isAncestor(ancestor.id, ctx.subjectId)),
|
|
250
247
|
find(ancestor =>
|
|
251
248
|
ctx.diagramNodes.has(ancestor.id) || ctx.connected[column].has(ancestor.id)
|
|
@@ -363,7 +360,7 @@ function layout(
|
|
|
363
360
|
): {
|
|
364
361
|
viewIncludesSubject: boolean
|
|
365
362
|
notIncludedRelations: number
|
|
366
|
-
subject:
|
|
363
|
+
subject: LikeC4Model.Element
|
|
367
364
|
nodes: XYFlowTypes.Node[]
|
|
368
365
|
edges: XYFlowTypes.Edge[]
|
|
369
366
|
bounds: {
|
|
@@ -381,7 +378,7 @@ function layout(
|
|
|
381
378
|
view.edges.flatMap(e => e.relations)
|
|
382
379
|
)
|
|
383
380
|
// Relations that are not included in the view
|
|
384
|
-
const notIncludedRelations = new Set<
|
|
381
|
+
const notIncludedRelations = new Set<RelationId>()
|
|
385
382
|
|
|
386
383
|
if (!viewIncludesSubject) {
|
|
387
384
|
// Reset scope to global if subject is not in the view
|
|
@@ -401,16 +398,18 @@ function layout(
|
|
|
401
398
|
|
|
402
399
|
if (scope === 'global') {
|
|
403
400
|
relationships = {
|
|
404
|
-
incoming: subjectElement.incoming().map(r => r
|
|
405
|
-
outgoing: subjectElement.outgoing().map(r => r
|
|
401
|
+
incoming: subjectElement.incoming().map(r => r.$relationship).toArray(),
|
|
402
|
+
outgoing: subjectElement.outgoing().map(r => r.$relationship).toArray()
|
|
406
403
|
}
|
|
407
404
|
} else {
|
|
408
|
-
const subjectViewModel = likec4model.view(view.id).
|
|
405
|
+
const subjectViewModel = likec4model.view(view.id).node(subjectId)
|
|
409
406
|
relationships = {
|
|
410
407
|
incoming: subjectViewModel.incoming()
|
|
411
|
-
.flatMap(c => c.relationships().map(r => r
|
|
408
|
+
.flatMap(c => c.relationships('model').map(r => r.$relationship))
|
|
409
|
+
.toArray(),
|
|
412
410
|
outgoing: subjectViewModel.outgoing()
|
|
413
|
-
.flatMap(c => c.relationships().map(r => r
|
|
411
|
+
.flatMap(c => c.relationships('model').map(r => r.$relationship))
|
|
412
|
+
.toArray()
|
|
414
413
|
}
|
|
415
414
|
}
|
|
416
415
|
|
|
@@ -445,7 +444,7 @@ function layout(
|
|
|
445
444
|
})
|
|
446
445
|
|
|
447
446
|
if (viewIncludesSubject) {
|
|
448
|
-
const subjectViewModel = likec4model.view(view.id).
|
|
447
|
+
const subjectViewModel = likec4model.view(view.id).node(subjectId)
|
|
449
448
|
subjectViewModel.incomers().forEach(incomer => {
|
|
450
449
|
ctx.connected.incomers.add(incomer.id)
|
|
451
450
|
})
|
|
@@ -86,14 +86,12 @@ export const CompoundNode = memo<CompoundNodeProps>(({
|
|
|
86
86
|
{...(selectable && {
|
|
87
87
|
whileHover: {
|
|
88
88
|
...scale(12),
|
|
89
|
-
scaleX: (width + 12) / width,
|
|
90
|
-
scaleY: (height + 12) / height,
|
|
91
89
|
transition: {
|
|
92
90
|
delay: 0.1
|
|
93
91
|
}
|
|
94
92
|
},
|
|
95
93
|
whileTap: {
|
|
96
|
-
...scale(-
|
|
94
|
+
...scale(-8)
|
|
97
95
|
}
|
|
98
96
|
})}
|
|
99
97
|
>
|
|
@@ -30,7 +30,7 @@ function selector(s: DiagramState) {
|
|
|
30
30
|
return {
|
|
31
31
|
currentViewId: s.view.id,
|
|
32
32
|
onNavigateTo: s.onNavigateTo,
|
|
33
|
-
onOpenSource: s.
|
|
33
|
+
onOpenSource: s.onOpenSource
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -56,10 +56,14 @@ export const ElementNode = memo<ElementNodeProps>(({
|
|
|
56
56
|
onOpenSource
|
|
57
57
|
} = useDiagramState(selector)
|
|
58
58
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const maxWH = Math.max(w, h)
|
|
60
|
+
const scale = (diff: number) => {
|
|
61
|
+
const s = (maxWH + diff) / maxWH
|
|
62
|
+
return ({
|
|
63
|
+
scaleX: s,
|
|
64
|
+
scaleY: s
|
|
65
|
+
})
|
|
66
|
+
}
|
|
63
67
|
|
|
64
68
|
let opacity = 1
|
|
65
69
|
if (data.dimmed) {
|
|
@@ -155,7 +159,9 @@ export const ElementNode = memo<ElementNodeProps>(({
|
|
|
155
159
|
<Action
|
|
156
160
|
onClick={(event) => {
|
|
157
161
|
event.stopPropagation()
|
|
158
|
-
onOpenSource(
|
|
162
|
+
onOpenSource?.({
|
|
163
|
+
element: data.fqn
|
|
164
|
+
})
|
|
159
165
|
}}>
|
|
160
166
|
<IconFileSymlink stroke={1.8} style={{ width: '72%' }} />
|
|
161
167
|
</Action>
|
|
@@ -2,7 +2,6 @@ import { ActionIcon, Box, Group, Stack, Text, ThemeIcon, Tooltip as MantineToolt
|
|
|
2
2
|
import { IconBoxMultipleFilled, IconZoomScan } from '@tabler/icons-react'
|
|
3
3
|
import { BaseEdge, EdgeLabelRenderer, type EdgeProps, getBezierPath } from '@xyflow/react'
|
|
4
4
|
import clsx from 'clsx'
|
|
5
|
-
import { useEffect, useRef } from 'react'
|
|
6
5
|
import { only } from 'remeda'
|
|
7
6
|
import { useDiagramState } from '../../../hooks/useDiagramState'
|
|
8
7
|
import { stopPropagation } from '../../../xyflow/utils'
|