@likec4/diagram 1.12.2 → 1.13.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 +8 -8
- package/src/LikeC4CustomColors.tsx +2 -2
- package/src/LikeC4Diagram.props.ts +15 -7
- package/src/LikeC4Diagram.tsx +23 -14
- package/src/StaticLikeC4Diagram.tsx +2 -0
- package/src/components/Overlay.css.ts +78 -0
- package/src/components/Overlay.tsx +102 -0
- package/src/components/OverlayContext.tsx +11 -0
- package/src/components/index.ts +2 -0
- package/src/likec4model/index.ts +1 -0
- package/src/likec4model/useLikeC4ElementsTree.ts +44 -0
- package/src/overlays/edge-details/EdgeDetailsOverlay.css.ts +13 -0
- package/src/overlays/edge-details/EdgeDetailsOverlay.tsx +37 -0
- package/src/overlays/index.tsx +33 -0
- package/src/overlays/relationships-of/RelationshipsOverlay.tsx +33 -0
- package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +359 -0
- package/src/overlays/relationships-of/SelectElement.css.ts +18 -0
- package/src/overlays/relationships-of/SelectElement.tsx +133 -0
- package/src/overlays/relationships-of/_types.ts +56 -0
- package/src/overlays/relationships-of/styles.css.ts +34 -0
- package/src/overlays/relationships-of/use-layouted-relationships.ts +607 -0
- package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +76 -0
- package/src/overlays/relationships-of/xyflow/ElementNode.tsx +130 -0
- package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +22 -0
- package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +59 -0
- package/src/overlays/relationships-of/xyflow/styles.css.ts +170 -0
- package/src/state/diagram-to-xyflow.ts +1 -1
- package/src/state/diagramStore.ts +119 -27
- package/src/ui/top-left/ChangeAutoLayoutButton.tsx +1 -1
- package/src/ui/top-left/ManualLayoutToolsButton.tsx +110 -0
- package/src/ui/top-left/TopLeftPanel.tsx +15 -31
- package/src/ui/top-left/_shared.ts +2 -2
- package/src/xyflow/EdgeMarkers.tsx +19 -0
- package/src/xyflow/XYFlowEvents.tsx +21 -24
- package/src/xyflow/XYFlowInner.tsx +6 -10
- package/src/xyflow/edges/EdgeLabel.tsx +6 -5
- package/src/xyflow/edges/RelationshipEdge.tsx +4 -3
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +16 -0
- package/src/xyflow/edges/edges.css.ts +5 -11
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +17 -40
- package/src/xyflow/nodes/compound/CompoundNode.tsx +219 -28
- package/src/xyflow/nodes/element/ElementLink.tsx +11 -8
- package/src/xyflow/nodes/element/ElementNode.tsx +211 -140
- package/src/xyflow/nodes/element/element.css.ts +22 -33
- package/src/xyflow/nodes/shared/Toolbar.tsx +81 -27
- package/src/xyflow/nodes/use-animate-variants.tsx +64 -0
- package/src/xyflow/useLayoutConstraints.ts +95 -125
- package/src/xyflow/utils.ts +4 -0
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +0 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/diagram",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
6
6
|
"homepage": "https://likec4.dev",
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"clean": "rm -r -f -g 'dist/*' .tsbuildinfo"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@
|
|
33
|
+
"@dagrejs/dagre": "^1.1.4",
|
|
34
|
+
"@likec4/core": "1.13.0",
|
|
35
35
|
"@mantine/vanilla-extract": "7.13.2",
|
|
36
36
|
"@react-hookz/web": "^24.0.4",
|
|
37
37
|
"@tabler/icons-react": "3.17.0",
|
|
38
38
|
"@vanilla-extract/css": "^1.16.0",
|
|
39
39
|
"@vanilla-extract/css-utils": "^0.1.4",
|
|
40
40
|
"@vanilla-extract/dynamic": "^2.1.2",
|
|
41
|
-
"@xyflow/react": "12.3.
|
|
41
|
+
"@xyflow/react": "12.3.2",
|
|
42
42
|
"@xyflow/system": "0.0.43",
|
|
43
43
|
"bezier-js": "^6.1.4",
|
|
44
44
|
"clsx": "^2.1.1",
|
|
@@ -58,16 +58,16 @@
|
|
|
58
58
|
"react-dom": "^18.3.1"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@likec4/tsconfig": "1.
|
|
61
|
+
"@likec4/tsconfig": "1.13.0",
|
|
62
62
|
"@mantine/core": "7.13.2",
|
|
63
63
|
"@mantine/hooks": "7.13.2",
|
|
64
64
|
"@types/bezier-js": "^4.1.3",
|
|
65
65
|
"@types/d3-shape": "^3.1.6",
|
|
66
|
-
"@types/react": "^18.3.
|
|
67
|
-
"@types/react-dom": "^18.3.
|
|
66
|
+
"@types/react": "^18.3.11",
|
|
67
|
+
"@types/react-dom": "^18.3.1",
|
|
68
68
|
"execa": "^9.3.1",
|
|
69
69
|
"react": "^18.3.1",
|
|
70
70
|
"react-dom": "^18.3.1",
|
|
71
|
-
"typescript": "^5.6.
|
|
71
|
+
"typescript": "^5.6.3"
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ThemeColorValues } from '@likec4/core'
|
|
2
|
+
import { useMantineStyleNonce } from '@mantine/core'
|
|
2
3
|
import { entries } from 'remeda'
|
|
3
4
|
import type { LikeC4CustomColorsProperties } from './LikeC4CustomColors.props'
|
|
4
5
|
import { vars } from './theme-vars'
|
|
5
|
-
import { useMantineStyleNonce } from '@mantine/core'
|
|
6
6
|
|
|
7
7
|
type CSSVarFunction = `var(--${string})` | `var(--${string}, ${string | number} )`
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ export function LikeC4CustomColors({ customColors }: LikeC4CustomColorsPropertie
|
|
|
29
29
|
const styles = entries(customColors)
|
|
30
30
|
.map(([name, color]) => toStyle(name, color))
|
|
31
31
|
.join('\n')
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
const nonce = useMantineStyleNonce()?.()
|
|
34
34
|
|
|
35
35
|
return (
|
|
@@ -61,11 +61,6 @@ export interface LikeC4DiagramProperties {
|
|
|
61
61
|
|
|
62
62
|
className?: string | undefined
|
|
63
63
|
|
|
64
|
-
/**
|
|
65
|
-
* Show/hide ReactFlow controls menu
|
|
66
|
-
* @default false
|
|
67
|
-
*/
|
|
68
|
-
controls?: boolean | undefined
|
|
69
64
|
/**
|
|
70
65
|
* Enable/disable panning
|
|
71
66
|
* @default true
|
|
@@ -81,6 +76,12 @@ export interface LikeC4DiagramProperties {
|
|
|
81
76
|
* @default true
|
|
82
77
|
*/
|
|
83
78
|
readonly?: boolean | undefined
|
|
79
|
+
/**
|
|
80
|
+
* Show/hide panel with top left controls,
|
|
81
|
+
*
|
|
82
|
+
* @default true if not readonly
|
|
83
|
+
*/
|
|
84
|
+
controls?: boolean | undefined
|
|
84
85
|
/**
|
|
85
86
|
* If set, initial viewport will show all nodes & edges
|
|
86
87
|
* @default true
|
|
@@ -130,7 +131,7 @@ export interface LikeC4DiagramProperties {
|
|
|
130
131
|
showDiagramTitle?: boolean | undefined
|
|
131
132
|
|
|
132
133
|
/**
|
|
133
|
-
* Show back/forward navigation buttons
|
|
134
|
+
* Show back/forward history navigation buttons
|
|
134
135
|
* @default true if `onNavigateTo` is set
|
|
135
136
|
*/
|
|
136
137
|
showNavigationButtons?: undefined | boolean
|
|
@@ -149,10 +150,17 @@ export interface LikeC4DiagramProperties {
|
|
|
149
150
|
|
|
150
151
|
/**
|
|
151
152
|
* If double click on a node should enable focus mode, i.e. highlight incoming/outgoing edges
|
|
152
|
-
* @default
|
|
153
|
+
* @default false
|
|
153
154
|
*/
|
|
154
155
|
enableFocusMode?: boolean | undefined
|
|
155
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Experimental feature to browse relationships
|
|
159
|
+
*
|
|
160
|
+
* @default false
|
|
161
|
+
*/
|
|
162
|
+
enableRelationshipsBrowser?: boolean | undefined
|
|
163
|
+
|
|
156
164
|
/**
|
|
157
165
|
* If Walkthrough for dynamic views should be enabled
|
|
158
166
|
* @default false
|
package/src/LikeC4Diagram.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import * as css from './LikeC4Diagram.css'
|
|
|
11
11
|
import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
|
|
12
12
|
import { useLikeC4Model } from './likec4model'
|
|
13
13
|
import { EnsureMantine } from './mantine/EnsureMantine'
|
|
14
|
+
import { Overlays } from './overlays'
|
|
14
15
|
import { DiagramContextProvider } from './state/DiagramContext'
|
|
15
16
|
import { FitViewOnDiagramChange } from './xyflow/FitviewOnDiagramChange'
|
|
16
17
|
import { SelectEdgesOnNodeFocus } from './xyflow/SelectEdgesOnNodeFocus'
|
|
@@ -30,13 +31,14 @@ export function LikeC4Diagram({
|
|
|
30
31
|
nodesSelectable = !readonly,
|
|
31
32
|
nodesDraggable = !readonly,
|
|
32
33
|
background = 'dots',
|
|
33
|
-
controls =
|
|
34
|
+
controls = !readonly,
|
|
34
35
|
showElementLinks = true,
|
|
35
36
|
showDiagramTitle = true,
|
|
36
37
|
showNotations = true,
|
|
37
38
|
showRelationshipDetails = true,
|
|
38
39
|
enableDynamicViewWalkthrough = false,
|
|
39
40
|
enableFocusMode = false,
|
|
41
|
+
enableRelationshipsBrowser = false,
|
|
40
42
|
initialWidth,
|
|
41
43
|
initialHeight,
|
|
42
44
|
keepAspectRatio = false,
|
|
@@ -69,10 +71,16 @@ export function LikeC4Diagram({
|
|
|
69
71
|
console.warn('where filter is only supported in readonly mode')
|
|
70
72
|
}
|
|
71
73
|
useEffect(() => {
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
+
if (hasLikec4model) {
|
|
75
|
+
return
|
|
74
76
|
}
|
|
75
|
-
|
|
77
|
+
if (showRelationshipDetails) {
|
|
78
|
+
console.warn('Invalid showRelationshipDetails=true, requires LikeC4ModelProvider')
|
|
79
|
+
}
|
|
80
|
+
if (enableRelationshipsBrowser) {
|
|
81
|
+
console.warn('Invalid enableRelationshipsBrowser=true, requires LikeC4ModelProvider')
|
|
82
|
+
}
|
|
83
|
+
}, [])
|
|
76
84
|
|
|
77
85
|
return (
|
|
78
86
|
<EnsureMantine>
|
|
@@ -89,10 +97,12 @@ export function LikeC4Diagram({
|
|
|
89
97
|
readonly={readonly}
|
|
90
98
|
pannable={pannable}
|
|
91
99
|
zoomable={zoomable}
|
|
100
|
+
hasLikeC4Model={hasLikec4model}
|
|
92
101
|
fitViewEnabled={fitView}
|
|
93
102
|
fitViewPadding={fitViewPadding}
|
|
103
|
+
controls={controls}
|
|
94
104
|
showElementLinks={showElementLinks}
|
|
95
|
-
showNavigationButtons={showNavigationButtons}
|
|
105
|
+
showNavigationButtons={showNavigationButtons && !!onNavigateTo}
|
|
96
106
|
showNotations={showNotations}
|
|
97
107
|
showRelationshipDetails={showRelationshipDetails && hasLikec4model}
|
|
98
108
|
nodesDraggable={nodesDraggable}
|
|
@@ -100,7 +110,8 @@ export function LikeC4Diagram({
|
|
|
100
110
|
experimentalEdgeEditing={experimentalEdgeEditing}
|
|
101
111
|
enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
|
|
102
112
|
enableFocusMode={enableFocusMode}
|
|
103
|
-
|
|
113
|
+
enableRelationshipsBrowser={enableRelationshipsBrowser && hasLikec4model}
|
|
114
|
+
whereFilter={readonly !== true ? (where ?? null) : null}
|
|
104
115
|
renderIcon={renderIcon ?? null}
|
|
105
116
|
onCanvasClick={onCanvasClick ?? null}
|
|
106
117
|
onCanvasContextMenu={onCanvasContextMenu ?? null}
|
|
@@ -117,7 +128,6 @@ export function LikeC4Diagram({
|
|
|
117
128
|
onBurgerMenuClick={onBurgerMenuClick ?? null}
|
|
118
129
|
>
|
|
119
130
|
<LikeC4DiagramInnerMemo
|
|
120
|
-
controls={controls}
|
|
121
131
|
background={background}
|
|
122
132
|
showDiagramTitle={showDiagramTitle}
|
|
123
133
|
/>
|
|
@@ -131,25 +141,24 @@ LikeC4Diagram.displayName = 'LikeC4Diagram'
|
|
|
131
141
|
|
|
132
142
|
type LikeC4DiagramInnerProps = {
|
|
133
143
|
background: NonNullable<LikeC4DiagramProperties['background']>
|
|
134
|
-
controls: boolean
|
|
135
144
|
showDiagramTitle: boolean
|
|
136
145
|
}
|
|
137
146
|
const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(function LikeC4DiagramInner({
|
|
138
147
|
background,
|
|
139
|
-
controls,
|
|
140
148
|
showDiagramTitle
|
|
141
149
|
}) {
|
|
142
150
|
const {
|
|
143
151
|
isInitialized,
|
|
144
152
|
pannable,
|
|
145
153
|
fitView,
|
|
146
|
-
enableFocusMode
|
|
154
|
+
enableFocusMode,
|
|
155
|
+
enableOverlays
|
|
147
156
|
} = useDiagramState(s => ({
|
|
148
157
|
isInitialized: s.initialized,
|
|
149
|
-
zoomable: s.zoomable,
|
|
150
158
|
pannable: s.pannable,
|
|
151
159
|
fitView: s.fitViewEnabled,
|
|
152
|
-
enableFocusMode: s.enableFocusMode
|
|
160
|
+
enableFocusMode: s.enableFocusMode,
|
|
161
|
+
enableOverlays: s.enableRelationshipsBrowser
|
|
153
162
|
}))
|
|
154
163
|
|
|
155
164
|
return (
|
|
@@ -158,7 +167,7 @@ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(fun
|
|
|
158
167
|
className={clsx(
|
|
159
168
|
'likec4-diagram',
|
|
160
169
|
css.cssReactFlow,
|
|
161
|
-
|
|
170
|
+
css.cssNoControls,
|
|
162
171
|
pannable !== true && css.cssDisablePan,
|
|
163
172
|
background === 'transparent' && css.cssTransparentBg
|
|
164
173
|
)}
|
|
@@ -166,9 +175,9 @@ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(fun
|
|
|
166
175
|
<XYFlowInner
|
|
167
176
|
showDiagramTitle={showDiagramTitle}
|
|
168
177
|
background={background}
|
|
169
|
-
controls={controls}
|
|
170
178
|
/>
|
|
171
179
|
</XYFlow>
|
|
180
|
+
{enableOverlays && <Overlays />}
|
|
172
181
|
{isInitialized && (
|
|
173
182
|
<>
|
|
174
183
|
{fitView && <FitViewOnDiagramChange />}
|
|
@@ -37,6 +37,8 @@ export function StaticLikeC4Diagram({
|
|
|
37
37
|
showNotations={false}
|
|
38
38
|
showRelationshipDetails={false}
|
|
39
39
|
enableDynamicViewWalkthrough={false}
|
|
40
|
+
enableRelationshipsBrowser={false}
|
|
41
|
+
experimentalEdgeEditing={false}
|
|
40
42
|
enableFocusMode={false}
|
|
41
43
|
nodesSelectable={false}
|
|
42
44
|
nodesDraggable={false}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createVar, keyframes, style } from '@vanilla-extract/css'
|
|
2
|
+
import { whereDark } from '../theme-vars'
|
|
3
|
+
|
|
4
|
+
const dialogFadeIn = keyframes({
|
|
5
|
+
'0%': {
|
|
6
|
+
opacity: 0,
|
|
7
|
+
display: 'none'
|
|
8
|
+
},
|
|
9
|
+
'100%': {
|
|
10
|
+
opacity: 1,
|
|
11
|
+
display: 'block'
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const dialogFadeOut = keyframes({
|
|
16
|
+
'0%': {
|
|
17
|
+
opacity: 1,
|
|
18
|
+
transform: 'scale(1)',
|
|
19
|
+
display: 'block'
|
|
20
|
+
},
|
|
21
|
+
'100%': {
|
|
22
|
+
opacity: 0,
|
|
23
|
+
transform: 'scale(0.98)',
|
|
24
|
+
display: 'none'
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const backdropRgb = createVar('backdrop-rgb')
|
|
29
|
+
|
|
30
|
+
const dialogBackdropFadeIn = keyframes({
|
|
31
|
+
'0%': {
|
|
32
|
+
WebkitBackdropFilter: 'blur(1px)',
|
|
33
|
+
backdropFilter: 'blur(1px)',
|
|
34
|
+
backgroundColor: `rgb(${backdropRgb} / 30%)`
|
|
35
|
+
},
|
|
36
|
+
'100%': {
|
|
37
|
+
WebkitBackdropFilter: 'blur(8px)',
|
|
38
|
+
backdropFilter: 'blur(8px)',
|
|
39
|
+
backgroundColor: `rgb(${backdropRgb} / 85%)`
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const dialog = style({
|
|
44
|
+
top: 0,
|
|
45
|
+
left: 0,
|
|
46
|
+
padding: 0,
|
|
47
|
+
margin: 0,
|
|
48
|
+
boxSizing: 'border-box',
|
|
49
|
+
border: '0 solid transparent',
|
|
50
|
+
width: '100%',
|
|
51
|
+
maxWidth: '100dvw',
|
|
52
|
+
height: '100%',
|
|
53
|
+
maxHeight: '100dvh',
|
|
54
|
+
background: 'transparent',
|
|
55
|
+
animation: `${dialogFadeOut} 130ms cubic-bezier(0.50, 0, 1, 1)`,
|
|
56
|
+
transformOrigin: '50% 20%',
|
|
57
|
+
vars: {
|
|
58
|
+
[backdropRgb]: '255 255 255'
|
|
59
|
+
},
|
|
60
|
+
selectors: {
|
|
61
|
+
[`${whereDark} &`]: {
|
|
62
|
+
vars: {
|
|
63
|
+
[backdropRgb]: '36 36 36'
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
'&[open]': {
|
|
67
|
+
animation: `${dialogFadeIn} 200ms cubic-bezier(0, 0, 0.40, 1) forwards`
|
|
68
|
+
},
|
|
69
|
+
'&::backdrop': {
|
|
70
|
+
WebkitBackdropFilter: 'blur(1px)',
|
|
71
|
+
backdropFilter: 'blur(1px)',
|
|
72
|
+
backgroundColor: `rgb(${backdropRgb} / 30%)`
|
|
73
|
+
},
|
|
74
|
+
'&[open]::backdrop': {
|
|
75
|
+
animation: `${dialogBackdropFadeIn} 350ms cubic-bezier(0, 0, 0.40, 1) forwards`
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ActionIcon, Box } from '@mantine/core'
|
|
2
|
+
import { useMergedRef, useTimeout } from '@mantine/hooks'
|
|
3
|
+
import { useDebouncedEffect, useSyncedRef } from '@react-hookz/web'
|
|
4
|
+
import { IconX } from '@tabler/icons-react'
|
|
5
|
+
import clsx from 'clsx'
|
|
6
|
+
import { forwardRef, type HTMLAttributes, type ReactNode, useMemo, useRef, useState } from 'react'
|
|
7
|
+
import { useDiagramStoreApi } from '../hooks/useDiagramState'
|
|
8
|
+
import * as css from './Overlay.css'
|
|
9
|
+
import { OverlayContext, useOverlayDialog } from './OverlayContext'
|
|
10
|
+
|
|
11
|
+
type OverlayDialogProps = Pick<HTMLAttributes<HTMLDialogElement>, 'style' | 'className'> & {
|
|
12
|
+
onClose?: (() => void) | undefined
|
|
13
|
+
children: (renderProps: {
|
|
14
|
+
opened: boolean
|
|
15
|
+
close: () => void
|
|
16
|
+
}) => ReactNode
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const OverlayDialog = forwardRef<HTMLDialogElement, OverlayDialogProps>(({
|
|
20
|
+
className,
|
|
21
|
+
children,
|
|
22
|
+
onClose,
|
|
23
|
+
...props
|
|
24
|
+
}, forwardedRef) => {
|
|
25
|
+
const onCloseRef = useSyncedRef(onClose)
|
|
26
|
+
const api = useDiagramStoreApi()
|
|
27
|
+
const [opened, setOpened] = useState(false)
|
|
28
|
+
const dialogRef = useRef<HTMLDialogElement>(null)
|
|
29
|
+
const ref = useMergedRef(dialogRef, forwardedRef)
|
|
30
|
+
|
|
31
|
+
useDebouncedEffect(
|
|
32
|
+
() => {
|
|
33
|
+
dialogRef.current?.showModal()
|
|
34
|
+
},
|
|
35
|
+
[],
|
|
36
|
+
30
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
useDebouncedEffect(
|
|
40
|
+
() => {
|
|
41
|
+
setOpened(true)
|
|
42
|
+
},
|
|
43
|
+
[],
|
|
44
|
+
80
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const { start: triggerOnClose } = useTimeout(() => {
|
|
48
|
+
onCloseRef.current?.()
|
|
49
|
+
}, 300)
|
|
50
|
+
|
|
51
|
+
const ctxValue = useMemo(() => ({
|
|
52
|
+
openOverlay: api.getState().openOverlay,
|
|
53
|
+
close: () => {
|
|
54
|
+
dialogRef.current?.close()
|
|
55
|
+
}
|
|
56
|
+
}), [api])
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<OverlayContext.Provider value={ctxValue}>
|
|
60
|
+
<dialog
|
|
61
|
+
aria-modal="true"
|
|
62
|
+
ref={ref}
|
|
63
|
+
className={clsx(css.dialog, className)}
|
|
64
|
+
onClick={e => {
|
|
65
|
+
if ((e.target as any)?.nodeName?.toUpperCase() === 'DIALOG') {
|
|
66
|
+
e.stopPropagation()
|
|
67
|
+
dialogRef.current?.close()
|
|
68
|
+
}
|
|
69
|
+
}}
|
|
70
|
+
onClose={e => {
|
|
71
|
+
e.stopPropagation()
|
|
72
|
+
triggerOnClose()
|
|
73
|
+
}}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
{children({
|
|
77
|
+
opened,
|
|
78
|
+
...ctxValue
|
|
79
|
+
})}
|
|
80
|
+
</dialog>
|
|
81
|
+
</OverlayContext.Provider>
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
export const OverlayDialogCloseButton = () => {
|
|
86
|
+
const { close } = useOverlayDialog()
|
|
87
|
+
return (
|
|
88
|
+
<Box pos={'absolute'} top={'1rem'} right={'1rem'}>
|
|
89
|
+
<ActionIcon
|
|
90
|
+
variant="default"
|
|
91
|
+
// color="gray"
|
|
92
|
+
size={'lg'}
|
|
93
|
+
autoFocus
|
|
94
|
+
onClick={(e) => {
|
|
95
|
+
e.stopPropagation()
|
|
96
|
+
close()
|
|
97
|
+
}}>
|
|
98
|
+
<IconX />
|
|
99
|
+
</ActionIcon>
|
|
100
|
+
</Box>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
import type { DiagramState } from '../hooks'
|
|
3
|
+
|
|
4
|
+
export const OverlayContext = createContext({} as {
|
|
5
|
+
openOverlay: DiagramState['openOverlay']
|
|
6
|
+
close: () => void
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export function useOverlayDialog() {
|
|
10
|
+
return useContext(OverlayContext)
|
|
11
|
+
}
|
package/src/likec4model/index.ts
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compareNatural,
|
|
3
|
+
type Fqn,
|
|
4
|
+
type LikeC4DiagramModel,
|
|
5
|
+
type LikeC4Model,
|
|
6
|
+
type LikeC4ViewModel,
|
|
7
|
+
type ViewID
|
|
8
|
+
} from '@likec4/core'
|
|
9
|
+
import { useMemo } from 'react'
|
|
10
|
+
import { useLikeC4Model } from './useLikeC4Model'
|
|
11
|
+
|
|
12
|
+
interface LikeC4ModelTreeNodeData {
|
|
13
|
+
label: string
|
|
14
|
+
value: Fqn
|
|
15
|
+
children: LikeC4ModelTreeNodeData[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const sortByLabel = (a: LikeC4ModelTreeNodeData, b: LikeC4ModelTreeNodeData) => compareNatural(a.label, b.label)
|
|
19
|
+
|
|
20
|
+
function buildNode(
|
|
21
|
+
element: LikeC4Model.ElementModel | LikeC4DiagramModel.Element | LikeC4ViewModel.Element
|
|
22
|
+
): LikeC4ModelTreeNodeData {
|
|
23
|
+
return {
|
|
24
|
+
label: element.title,
|
|
25
|
+
value: element.id,
|
|
26
|
+
children: element.children().map(buildNode).sort(sortByLabel)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns a tree of elements in the model.
|
|
32
|
+
* If `viewId` is provided, returns the tree of elements in the view.
|
|
33
|
+
*/
|
|
34
|
+
export function useLikeC4ElementsTree(viewId?: ViewID): LikeC4ModelTreeNodeData[] {
|
|
35
|
+
const model = useLikeC4Model(true)
|
|
36
|
+
return useMemo(() => {
|
|
37
|
+
if (viewId) {
|
|
38
|
+
const view = model.view(viewId)
|
|
39
|
+
return view.roots().map(buildNode).sort(sortByLabel)
|
|
40
|
+
} else {
|
|
41
|
+
return model.roots().map(buildNode).sort(sortByLabel)
|
|
42
|
+
}
|
|
43
|
+
}, [model, viewId ?? null])
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { style } from '@vanilla-extract/css'
|
|
2
|
+
|
|
3
|
+
//
|
|
4
|
+
export const dependencyViewContainer = style({
|
|
5
|
+
width: '100%',
|
|
6
|
+
height: '100%'
|
|
7
|
+
// position: 'absolute',
|
|
8
|
+
// top: 20,
|
|
9
|
+
// left: 20,
|
|
10
|
+
// width: 'calc(100% - 40px)',
|
|
11
|
+
// height: 'calc(100% - 40px)',
|
|
12
|
+
// overflow: 'hidden',
|
|
13
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type EdgeId, invariant } from '@likec4/core'
|
|
2
|
+
import { ActionIcon, Code, Paper } from '@mantine/core'
|
|
3
|
+
import { IconX } from '@tabler/icons-react'
|
|
4
|
+
import { OverlayDialog } from '../../components'
|
|
5
|
+
import { useDiagramState, useDiagramStoreApi } from '../../hooks'
|
|
6
|
+
import * as css from './EdgeDetailsOverlay.css'
|
|
7
|
+
|
|
8
|
+
export function EdgeDetailsOverlay({ edgeId }: { edgeId: EdgeId }) {
|
|
9
|
+
const diagramApi = useDiagramStoreApi()
|
|
10
|
+
const edge = useDiagramState(s => s.xyedges.find(e => e.id === edgeId))
|
|
11
|
+
invariant(edge, `Edge with id ${edgeId} not found`)
|
|
12
|
+
return (
|
|
13
|
+
<OverlayDialog
|
|
14
|
+
onClose={() => {
|
|
15
|
+
diagramApi.getState().closeOverlay()
|
|
16
|
+
}}
|
|
17
|
+
data-likec4-color="gray"
|
|
18
|
+
>
|
|
19
|
+
{({ close }) => (
|
|
20
|
+
<Paper className={css.dependencyViewContainer}>
|
|
21
|
+
<div>edge</div>
|
|
22
|
+
<Code block>{JSON.stringify(edge, null, 2)}</Code>
|
|
23
|
+
<ActionIcon
|
|
24
|
+
variant="default"
|
|
25
|
+
color="gray"
|
|
26
|
+
autoFocus
|
|
27
|
+
onClick={(e) => {
|
|
28
|
+
e.stopPropagation()
|
|
29
|
+
close()
|
|
30
|
+
}}>
|
|
31
|
+
<IconX />
|
|
32
|
+
</ActionIcon>
|
|
33
|
+
</Paper>
|
|
34
|
+
)}
|
|
35
|
+
</OverlayDialog>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { memo } from 'react'
|
|
2
|
+
import { useDiagramState } from '../hooks/useDiagramState'
|
|
3
|
+
import { EdgeDetailsOverlay } from './edge-details/EdgeDetailsOverlay'
|
|
4
|
+
import { RelationshipsOfOverlay } from './relationships-of/RelationshipsOverlay'
|
|
5
|
+
|
|
6
|
+
export const Overlays = memo(() => {
|
|
7
|
+
const {
|
|
8
|
+
hasLikeC4Model,
|
|
9
|
+
activeOverlay
|
|
10
|
+
} = useDiagramState(s => ({
|
|
11
|
+
hasLikeC4Model: s.hasLikeC4Model,
|
|
12
|
+
activeOverlay: s.activeOverlay
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
if (!activeOverlay) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!hasLikeC4Model) {
|
|
20
|
+
console.warn('LikeC4Model is not available, but overlays are active')
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (activeOverlay.relationshipsOf) {
|
|
25
|
+
return <RelationshipsOfOverlay />
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (activeOverlay.edgeDetails) {
|
|
29
|
+
return <EdgeDetailsOverlay edgeId={activeOverlay.edgeDetails} />
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null
|
|
33
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
|
|
2
|
+
import { memo } from 'react'
|
|
3
|
+
import { OverlayDialog, OverlayDialogCloseButton } from '../../components'
|
|
4
|
+
import { useDiagramStoreApi } from '../../hooks'
|
|
5
|
+
import { RelationshipsXYFlow } from './RelationshipsXYFlow'
|
|
6
|
+
import * as css from './styles.css'
|
|
7
|
+
|
|
8
|
+
export const RelationshipsOfOverlay = memo(() => {
|
|
9
|
+
const diagramApi = useDiagramStoreApi()
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<OverlayDialog
|
|
13
|
+
className={css.overlay}
|
|
14
|
+
onClose={() => {
|
|
15
|
+
diagramApi.getState().closeOverlay()
|
|
16
|
+
}}
|
|
17
|
+
data-likec4-color="gray"
|
|
18
|
+
>
|
|
19
|
+
{({ opened }) => (
|
|
20
|
+
<>
|
|
21
|
+
{opened && (
|
|
22
|
+
<XYFlowProvider
|
|
23
|
+
defaultNodes={[]}
|
|
24
|
+
defaultEdges={[]}>
|
|
25
|
+
<RelationshipsXYFlow />
|
|
26
|
+
</XYFlowProvider>
|
|
27
|
+
)}
|
|
28
|
+
<OverlayDialogCloseButton />
|
|
29
|
+
</>
|
|
30
|
+
)}
|
|
31
|
+
</OverlayDialog>
|
|
32
|
+
)
|
|
33
|
+
})
|