@likec4/diagram 1.16.0 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +22 -18
- package/src/LikeC4CustomColors.tsx +24 -21
- package/src/LikeC4Diagram.props.ts +15 -9
- package/src/LikeC4Diagram.tsx +33 -27
- package/src/LikeC4Search.tsx +146 -0
- package/src/StaticLikeC4Diagram.tsx +10 -5
- package/src/overlays/OverlayContext.tsx +1 -1
- package/src/overlays/Overlays.css.ts +28 -23
- package/src/overlays/Overlays.tsx +116 -64
- package/src/overlays/edge-details/xyflow/ElementNode.tsx +15 -12
- package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +7 -3
- package/src/overlays/edge-details/xyflow/styles.css.ts +22 -16
- package/src/overlays/element-details/ElementDetailsCard.css.ts +322 -0
- package/src/overlays/element-details/ElementDetailsCard.tsx +639 -0
- package/src/overlays/element-details/TabPanelRelationships.css.ts +106 -0
- package/src/overlays/element-details/TabPanelRelationships.tsx +201 -0
- package/src/overlays/element-details/TabPanelStructure.css.ts +36 -0
- package/src/overlays/element-details/TabPanelStructure.tsx +120 -0
- package/src/overlays/relationships-of/RelationshipsOverlay.tsx +185 -0
- package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +179 -213
- package/src/overlays/relationships-of/_types.ts +15 -4
- package/src/overlays/relationships-of/use-layouted-relationships.ts +301 -207
- package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +68 -14
- package/src/overlays/relationships-of/xyflow/ElementNode.tsx +54 -14
- package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +1 -1
- package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +51 -23
- package/src/overlays/relationships-of/xyflow/styles.css.ts +34 -27
- package/src/state/DiagramContext.tsx +15 -8
- package/src/state/aligners.ts +473 -0
- package/src/state/diagramStore.layout.ts +67 -0
- package/src/state/diagramStore.ts +18 -73
- package/src/theme-vars.ts +9 -0
- package/src/ui/FramerMotionConfig.tsx +18 -0
- package/src/ui/notation/NotationPanel.tsx +3 -3
- package/src/ui/top-left/ManualLayoutToolsButton.tsx +15 -0
- package/src/ui/top-left/TopLeftPanel.tsx +14 -1
- package/src/xyflow/XYFlow.tsx +18 -35
- package/src/xyflow/XYFlowInner.tsx +2 -2
- package/src/xyflow/edges/EdgeLabel.tsx +3 -3
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +25 -18
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +96 -30
- package/src/xyflow/nodes/compound/CompoundNode.tsx +149 -165
- package/src/xyflow/nodes/element/ElementDetails.css.ts +101 -0
- package/src/xyflow/nodes/element/ElementDetails.tsx +393 -0
- package/src/xyflow/nodes/element/ElementNode.tsx +165 -133
- package/src/xyflow/nodes/element/element.css.ts +46 -17
- package/src/xyflow/nodes/shared/ElementIcon.tsx +43 -0
- package/src/xyflow/nodes/shared/Toolbar.tsx +6 -6
- package/src/xyflow/{XYFlowEvents.tsx → useXYFlowEvents.ts} +26 -22
- package/src/xyflow/utils.ts +12 -3
- package/src/LikeC4CustomColors.props.ts +0 -5
- /package/src/{mantine → ui}/EnsureMantine.tsx +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/diagram",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
6
6
|
"homepage": "https://likec4.dev",
|
|
@@ -23,44 +23,47 @@
|
|
|
23
23
|
"exports": {
|
|
24
24
|
".": "./src/index.ts"
|
|
25
25
|
},
|
|
26
|
-
"packageManager": "yarn@4.5.
|
|
26
|
+
"packageManager": "yarn@4.5.3",
|
|
27
27
|
"scripts": {
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
29
|
"lint": "run -T eslint src/ --fix",
|
|
30
|
-
"clean": "rm -r -f -g 'dist/*' .tsbuildinfo"
|
|
30
|
+
"clean": "rm -r -f -g 'dist/*' .tsbuildinfo",
|
|
31
|
+
"test": "vitest run --no-isolate"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
34
|
"@dagrejs/dagre": "^1.1.4",
|
|
34
|
-
"@likec4/core": "1.
|
|
35
|
-
"@mantine/vanilla-extract": "7.
|
|
35
|
+
"@likec4/core": "1.17.1",
|
|
36
|
+
"@mantine/vanilla-extract": "7.14.1",
|
|
36
37
|
"@react-hookz/web": "^24.0.4",
|
|
37
38
|
"@tabler/icons-react": "3.17.0",
|
|
38
|
-
"@vanilla-extract/css": "^1.16.
|
|
39
|
+
"@vanilla-extract/css": "^1.16.1",
|
|
39
40
|
"@vanilla-extract/css-utils": "^0.1.4",
|
|
40
41
|
"@vanilla-extract/dynamic": "^2.1.2",
|
|
41
|
-
"@xyflow/react": "12.3.
|
|
42
|
-
"@xyflow/system": "0.0.
|
|
42
|
+
"@xyflow/react": "12.3.5",
|
|
43
|
+
"@xyflow/system": "0.0.46",
|
|
43
44
|
"bezier-js": "^6.1.4",
|
|
44
45
|
"clsx": "^2.1.1",
|
|
45
46
|
"d3-shape": "^3.2.0",
|
|
46
|
-
"esm-env": "^1.
|
|
47
|
+
"esm-env": "^1.1.4",
|
|
47
48
|
"fast-equals": "^5.0.1",
|
|
48
|
-
"framer-motion": "^11.11.
|
|
49
|
+
"framer-motion": "^11.11.17",
|
|
49
50
|
"khroma": "^2.1.0",
|
|
50
|
-
"remeda": "^2.
|
|
51
|
-
"type-fest": "4.
|
|
51
|
+
"remeda": "^2.17.3",
|
|
52
|
+
"type-fest": "4.27.1",
|
|
52
53
|
"zustand": "^4.5.5"
|
|
53
54
|
},
|
|
54
55
|
"peerDependencies": {
|
|
55
|
-
"@mantine/core": "^7.
|
|
56
|
-
"@mantine/hooks": "^7.
|
|
56
|
+
"@mantine/core": "^7.14.1",
|
|
57
|
+
"@mantine/hooks": "^7.14.1",
|
|
58
|
+
"@mantine/spotlight": "^7.14.1",
|
|
57
59
|
"react": "^18.3.1",
|
|
58
60
|
"react-dom": "^18.3.1"
|
|
59
61
|
},
|
|
60
62
|
"devDependencies": {
|
|
61
|
-
"@likec4/tsconfig": "1.
|
|
62
|
-
"@mantine/core": "7.
|
|
63
|
-
"@mantine/hooks": "7.
|
|
63
|
+
"@likec4/tsconfig": "1.17.1",
|
|
64
|
+
"@mantine/core": "7.14.1",
|
|
65
|
+
"@mantine/hooks": "7.14.1",
|
|
66
|
+
"@mantine/spotlight": "7.14.1",
|
|
64
67
|
"@types/bezier-js": "^4.1.3",
|
|
65
68
|
"@types/d3-shape": "^3.1.6",
|
|
66
69
|
"@types/react": "^18.3.11",
|
|
@@ -68,6 +71,7 @@
|
|
|
68
71
|
"execa": "^9.3.1",
|
|
69
72
|
"react": "^18.3.1",
|
|
70
73
|
"react-dom": "^18.3.1",
|
|
71
|
-
"typescript": "^5.
|
|
74
|
+
"typescript": "^5.7.2",
|
|
75
|
+
"vitest": "^2.1.5"
|
|
72
76
|
}
|
|
73
77
|
}
|
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
import { type ThemeColorValues } from '@likec4/core'
|
|
1
|
+
import { type CustomColorDefinitions, type ThemeColorValues } from '@likec4/core'
|
|
2
2
|
import { useMantineStyleNonce } from '@mantine/core'
|
|
3
3
|
import { deepEqual } from 'fast-equals'
|
|
4
4
|
import { memo } from 'react'
|
|
5
5
|
import { entries } from 'remeda'
|
|
6
|
-
import type { LikeC4CustomColorsProperties } from './LikeC4CustomColors.props'
|
|
7
6
|
import { vars } from './theme-vars'
|
|
8
7
|
|
|
9
8
|
type CSSVarFunction = `var(--${string})` | `var(--${string}, ${string | number} )`
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}`
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function stripCssVarReference(ref: CSSVarFunction): String {
|
|
27
|
-
const end = ref.indexOf(',')
|
|
28
|
-
return ref.substring(4, end == -1 ? ref.length - 1 : end)
|
|
29
|
-
}
|
|
10
|
+
interface LikeC4CustomColorsProperties {
|
|
11
|
+
customColors: CustomColorDefinitions
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function toStyle(name: String, colorValues: ThemeColorValues): String {
|
|
15
|
+
const rules = [
|
|
16
|
+
...entries(colorValues.elements)
|
|
17
|
+
.map(([key, value]) => `${stripCssVarReference(vars.element[key])}: ${value};`),
|
|
18
|
+
...entries(colorValues.relationships)
|
|
19
|
+
.map(([key, value]) => `${stripCssVarReference(vars.relation[key])}: ${value};`)
|
|
20
|
+
]
|
|
21
|
+
.join('\n')
|
|
30
22
|
|
|
23
|
+
return `:where([data-likec4-color=${name}]) {
|
|
24
|
+
${rules}
|
|
25
|
+
}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function stripCssVarReference(ref: CSSVarFunction): String {
|
|
29
|
+
const end = ref.indexOf(',')
|
|
30
|
+
return ref.substring(4, end == -1 ? ref.length - 1 : end)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const LikeC4CustomColors = memo<LikeC4CustomColorsProperties>(({ customColors }) => {
|
|
31
34
|
const styles = entries(customColors)
|
|
32
35
|
.map(([name, color]) => toStyle(name, color))
|
|
33
36
|
.join('\n')
|
|
@@ -118,12 +118,6 @@ export interface LikeC4DiagramProperties {
|
|
|
118
118
|
*/
|
|
119
119
|
background?: 'transparent' | 'solid' | XYBackground | undefined
|
|
120
120
|
|
|
121
|
-
/**
|
|
122
|
-
* Display hovercards with element links
|
|
123
|
-
* @default true
|
|
124
|
-
*/
|
|
125
|
-
showElementLinks?: boolean | undefined
|
|
126
|
-
|
|
127
121
|
/**
|
|
128
122
|
* Display webview with diagram title / description
|
|
129
123
|
* @default true
|
|
@@ -144,9 +138,9 @@ export interface LikeC4DiagramProperties {
|
|
|
144
138
|
|
|
145
139
|
/**
|
|
146
140
|
* Display dropdown with details on relationship's label click
|
|
147
|
-
* @default
|
|
141
|
+
* @default false
|
|
148
142
|
*/
|
|
149
|
-
|
|
143
|
+
enableRelationshipDetails?: boolean | undefined
|
|
150
144
|
|
|
151
145
|
/**
|
|
152
146
|
* If double click on a node should enable focus mode, i.e. highlight incoming/outgoing edges
|
|
@@ -154,12 +148,24 @@ export interface LikeC4DiagramProperties {
|
|
|
154
148
|
*/
|
|
155
149
|
enableFocusMode?: boolean | undefined
|
|
156
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Enable search popup for elements and views
|
|
153
|
+
* @default true
|
|
154
|
+
*/
|
|
155
|
+
enableSearch?: boolean | undefined
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Enable modal with element details
|
|
159
|
+
* @default false
|
|
160
|
+
*/
|
|
161
|
+
enableElementDetails?: boolean | undefined
|
|
162
|
+
|
|
157
163
|
/**
|
|
158
164
|
* Experimental feature to browse relationships
|
|
159
165
|
*
|
|
160
166
|
* @default false
|
|
161
167
|
*/
|
|
162
|
-
|
|
168
|
+
enableRelationshipBrowser?: boolean | undefined
|
|
163
169
|
|
|
164
170
|
/**
|
|
165
171
|
* If Walkthrough for dynamic views should be enabled
|
package/src/LikeC4Diagram.tsx
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import type { DiagramView } from '@likec4/core'
|
|
2
1
|
import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
|
|
3
2
|
import clsx from 'clsx'
|
|
4
|
-
import {
|
|
5
|
-
import { domMax, LazyMotion } from 'framer-motion'
|
|
3
|
+
import { deepEqual } from 'fast-equals'
|
|
6
4
|
import { memo, useEffect, useRef } from 'react'
|
|
5
|
+
import { isEmpty } from 'remeda'
|
|
7
6
|
import { rootClassName } from './globals.css'
|
|
8
7
|
import { useDiagramState } from './hooks/useDiagramState'
|
|
9
8
|
import { LikeC4CustomColors } from './LikeC4CustomColors'
|
|
10
9
|
import * as css from './LikeC4Diagram.css'
|
|
11
10
|
import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
|
|
12
11
|
import { useLikeC4Model } from './likec4model'
|
|
13
|
-
import {
|
|
12
|
+
import { LikeC4Search } from './LikeC4Search'
|
|
14
13
|
import { Overlays } from './overlays'
|
|
15
14
|
import { DiagramContextProvider } from './state/DiagramContext'
|
|
15
|
+
import { EnsureMantine } from './ui/EnsureMantine'
|
|
16
|
+
import { FramerMotionConfig } from './ui/FramerMotionConfig'
|
|
16
17
|
import { FitViewOnDiagramChange } from './xyflow/FitviewOnDiagramChange'
|
|
17
18
|
import { SelectEdgesOnNodeFocus } from './xyflow/SelectEdgesOnNodeFocus'
|
|
18
19
|
import type { XYFlowEdge, XYFlowNode } from './xyflow/types'
|
|
@@ -32,13 +33,14 @@ export function LikeC4Diagram({
|
|
|
32
33
|
nodesDraggable = !readonly,
|
|
33
34
|
background = 'dots',
|
|
34
35
|
controls = !readonly,
|
|
35
|
-
showElementLinks = true,
|
|
36
36
|
showDiagramTitle = true,
|
|
37
37
|
showNotations = true,
|
|
38
|
-
showRelationshipDetails = true,
|
|
39
38
|
enableDynamicViewWalkthrough = false,
|
|
40
39
|
enableFocusMode = false,
|
|
41
|
-
|
|
40
|
+
enableElementDetails = false,
|
|
41
|
+
enableRelationshipDetails = enableElementDetails,
|
|
42
|
+
enableRelationshipBrowser = enableRelationshipDetails,
|
|
43
|
+
enableSearch = true,
|
|
42
44
|
initialWidth,
|
|
43
45
|
initialHeight,
|
|
44
46
|
keepAspectRatio = false,
|
|
@@ -74,43 +76,47 @@ export function LikeC4Diagram({
|
|
|
74
76
|
if (hasLikec4model) {
|
|
75
77
|
return
|
|
76
78
|
}
|
|
77
|
-
if (
|
|
79
|
+
if (enableRelationshipDetails) {
|
|
78
80
|
console.warn('Invalid showRelationshipDetails=true, requires LikeC4ModelProvider')
|
|
79
81
|
}
|
|
80
|
-
if (
|
|
81
|
-
console.warn('Invalid
|
|
82
|
+
if (enableElementDetails) {
|
|
83
|
+
console.warn('Invalid enableElementDetails=true, requires LikeC4ModelProvider')
|
|
84
|
+
}
|
|
85
|
+
if (enableRelationshipBrowser) {
|
|
86
|
+
console.warn('Invalid enableRelationshipBrowser=true, requires LikeC4ModelProvider')
|
|
82
87
|
}
|
|
83
88
|
})
|
|
84
89
|
|
|
85
90
|
return (
|
|
86
91
|
<EnsureMantine>
|
|
87
|
-
<
|
|
92
|
+
<FramerMotionConfig>
|
|
88
93
|
<XYFlowProvider
|
|
89
94
|
fitView={fitView}
|
|
90
95
|
{...initialRef.current}
|
|
91
96
|
>
|
|
92
|
-
{
|
|
97
|
+
{!isEmpty(view.customColorDefinitions) && <LikeC4CustomColors customColors={view.customColorDefinitions} />}
|
|
93
98
|
<DiagramContextProvider
|
|
94
99
|
view={view}
|
|
95
100
|
keepAspectRatio={keepAspectRatio}
|
|
96
101
|
className={clsx(rootClassName, className)}
|
|
97
102
|
readonly={readonly}
|
|
103
|
+
experimentalEdgeEditing={!readonly && experimentalEdgeEditing}
|
|
98
104
|
pannable={pannable}
|
|
99
105
|
zoomable={zoomable}
|
|
106
|
+
nodesDraggable={nodesDraggable}
|
|
107
|
+
nodesSelectable={nodesSelectable || enableFocusMode}
|
|
100
108
|
hasLikeC4Model={hasLikec4model}
|
|
101
109
|
fitViewEnabled={fitView}
|
|
102
110
|
fitViewPadding={fitViewPadding}
|
|
103
111
|
controls={controls}
|
|
104
|
-
showElementLinks={showElementLinks}
|
|
105
112
|
showNavigationButtons={showNavigationButtons && !!onNavigateTo}
|
|
106
113
|
showNotations={showNotations}
|
|
107
|
-
showRelationshipDetails={showRelationshipDetails && hasLikec4model}
|
|
108
|
-
nodesDraggable={nodesDraggable}
|
|
109
|
-
nodesSelectable={nodesSelectable}
|
|
110
|
-
experimentalEdgeEditing={experimentalEdgeEditing}
|
|
111
|
-
enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
|
|
112
114
|
enableFocusMode={enableFocusMode}
|
|
113
|
-
|
|
115
|
+
enableRelationshipDetails={enableRelationshipDetails && hasLikec4model}
|
|
116
|
+
enableSearch={hasLikec4model && enableSearch}
|
|
117
|
+
enableElementDetails={enableElementDetails && hasLikec4model}
|
|
118
|
+
enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
|
|
119
|
+
enableRelationshipBrowser={enableRelationshipBrowser && hasLikec4model}
|
|
114
120
|
// Apply where filter only in readonly mode
|
|
115
121
|
whereFilter={readonly ? (where ?? null) : null}
|
|
116
122
|
renderIcon={renderIcon ?? null}
|
|
@@ -131,10 +137,11 @@ export function LikeC4Diagram({
|
|
|
131
137
|
<LikeC4DiagramInnerMemo
|
|
132
138
|
background={background}
|
|
133
139
|
showDiagramTitle={showDiagramTitle}
|
|
140
|
+
enableSearch={hasLikec4model && enableSearch}
|
|
134
141
|
/>
|
|
135
142
|
</DiagramContextProvider>
|
|
136
143
|
</XYFlowProvider>
|
|
137
|
-
</
|
|
144
|
+
</FramerMotionConfig>
|
|
138
145
|
</EnsureMantine>
|
|
139
146
|
)
|
|
140
147
|
}
|
|
@@ -143,10 +150,12 @@ LikeC4Diagram.displayName = 'LikeC4Diagram'
|
|
|
143
150
|
type LikeC4DiagramInnerProps = {
|
|
144
151
|
background: NonNullable<LikeC4DiagramProperties['background']>
|
|
145
152
|
showDiagramTitle: boolean
|
|
153
|
+
enableSearch: boolean
|
|
146
154
|
}
|
|
147
155
|
const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(function LikeC4DiagramInner({
|
|
148
156
|
background,
|
|
149
|
-
showDiagramTitle
|
|
157
|
+
showDiagramTitle,
|
|
158
|
+
enableSearch
|
|
150
159
|
}) {
|
|
151
160
|
const {
|
|
152
161
|
isInitialized,
|
|
@@ -159,7 +168,7 @@ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(fun
|
|
|
159
168
|
pannable: s.pannable,
|
|
160
169
|
fitView: s.fitViewEnabled,
|
|
161
170
|
enableFocusMode: s.enableFocusMode,
|
|
162
|
-
enableOverlays: s.hasLikeC4Model && (s.
|
|
171
|
+
enableOverlays: s.hasLikeC4Model && (s.enableRelationshipBrowser || s.enableElementDetails)
|
|
163
172
|
}))
|
|
164
173
|
|
|
165
174
|
return (
|
|
@@ -180,6 +189,7 @@ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(fun
|
|
|
180
189
|
/>
|
|
181
190
|
</XYFlow>
|
|
182
191
|
{enableOverlays && <Overlays />}
|
|
192
|
+
{enableSearch && <LikeC4Search />}
|
|
183
193
|
{isInitialized && (
|
|
184
194
|
<>
|
|
185
195
|
{fitView && <FitViewOnDiagramChange />}
|
|
@@ -188,9 +198,5 @@ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(fun
|
|
|
188
198
|
)}
|
|
189
199
|
</>
|
|
190
200
|
)
|
|
191
|
-
},
|
|
201
|
+
}, deepEqual)
|
|
192
202
|
LikeC4DiagramInnerMemo.displayName = 'LikeC4DiagramInnerMemo'
|
|
193
|
-
|
|
194
|
-
function customColorsDefined(view: DiagramView) {
|
|
195
|
-
return Object.keys(view.customColorDefinitions).length > 0
|
|
196
|
-
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { DiagramView } from '@likec4/core'
|
|
2
|
+
import { Spotlight, SpotlightActionsGroup, type SpotlightActionData } from '@mantine/spotlight'
|
|
3
|
+
import { IconRectangularPrism, IconSitemap, IconSearch } from '@tabler/icons-react'
|
|
4
|
+
import { memo, useMemo, useState } from 'react'
|
|
5
|
+
import { filter, map, pipe } from 'remeda'
|
|
6
|
+
import { useDiagramState, useDiagramStoreApi, useMantinePortalProps } from './hooks'
|
|
7
|
+
import { useLikeC4Model } from './likec4model'
|
|
8
|
+
import { Center, Group, Highlight, Text } from '@mantine/core'
|
|
9
|
+
|
|
10
|
+
interface QueryMatch {
|
|
11
|
+
data: SpotlightActionData,
|
|
12
|
+
keyword: string,
|
|
13
|
+
match: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function filterAction(actionData: SpotlightActionData, query: string) : QueryMatch | undefined {
|
|
17
|
+
const queryLower = query.toLowerCase();
|
|
18
|
+
const keywords = Array.isArray(actionData.keywords) ? actionData.keywords : [];
|
|
19
|
+
const keywordMatch = keywords
|
|
20
|
+
.map(keyword => {
|
|
21
|
+
const index = keyword.toLowerCase().indexOf(queryLower);
|
|
22
|
+
return index !== -1 ? {
|
|
23
|
+
keyword: keyword,
|
|
24
|
+
match: keyword.substring(index, index + query.length)
|
|
25
|
+
} : null;
|
|
26
|
+
})
|
|
27
|
+
.find(match => !!match);
|
|
28
|
+
return !!keywordMatch ? {
|
|
29
|
+
data: actionData,
|
|
30
|
+
keyword: keywordMatch.keyword,
|
|
31
|
+
match: keywordMatch.match
|
|
32
|
+
} : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildFilteredActions(actionsData: SpotlightActionData[], query: string): JSX.Element[] {
|
|
36
|
+
return pipe(
|
|
37
|
+
actionsData,
|
|
38
|
+
map(a => filterAction(a, query)),
|
|
39
|
+
filter(qm => !!qm),
|
|
40
|
+
map(qm => buildSpotlightAction(qm))
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function highlightMatch(match: QueryMatch): JSX.Element {
|
|
45
|
+
return <Highlight highlight={match.match}>{match.keyword}</Highlight>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function buildSpotlightAction(matchedAction: QueryMatch): JSX.Element {
|
|
49
|
+
const isMatchInLabel = matchedAction.keyword == matchedAction.data.label;
|
|
50
|
+
return <Spotlight.Action onClick={matchedAction.data.onClick}>
|
|
51
|
+
<Center style={{ marginRight: '8px '}}>
|
|
52
|
+
{matchedAction.data.leftSection}
|
|
53
|
+
</Center>
|
|
54
|
+
<Group wrap="nowrap" w="100%">
|
|
55
|
+
<div style={{ flex: 1 }}>
|
|
56
|
+
<Text>
|
|
57
|
+
{isMatchInLabel ? highlightMatch(matchedAction) : matchedAction.data.label}
|
|
58
|
+
</Text>
|
|
59
|
+
{!isMatchInLabel && <Text opacity={0.6} size="xs">{highlightMatch(matchedAction)}</Text>}
|
|
60
|
+
</div>
|
|
61
|
+
</Group>
|
|
62
|
+
</Spotlight.Action>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const LikeC4Search = memo(() => {
|
|
66
|
+
const view = useDiagramState(s => s.view)
|
|
67
|
+
const model = useLikeC4Model(true)
|
|
68
|
+
const store = useDiagramStoreApi()
|
|
69
|
+
const [ query, setQuery ] = useState('')
|
|
70
|
+
|
|
71
|
+
const getNodeActionsData = (): SpotlightActionData[] => {
|
|
72
|
+
const { focusOnNode } = store.getState()
|
|
73
|
+
|
|
74
|
+
return pipe(
|
|
75
|
+
view.nodes,
|
|
76
|
+
filter(n => !!n.title),
|
|
77
|
+
map(n => ({
|
|
78
|
+
id: n.id,
|
|
79
|
+
label: n.title,
|
|
80
|
+
keywords: [
|
|
81
|
+
n.title,
|
|
82
|
+
...(n.tags ?? []).map(t => `#${t}`),
|
|
83
|
+
...(n.description ? [n.description] : [])
|
|
84
|
+
].filter(k => k.toLowerCase()),
|
|
85
|
+
onClick: () => focusOnNode(n.id),
|
|
86
|
+
leftSection: <IconRectangularPrism />
|
|
87
|
+
}))
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const getViewActionsData = (): SpotlightActionData[] => {
|
|
92
|
+
const { onNavigateTo } = store.getState()
|
|
93
|
+
const views = model.views()
|
|
94
|
+
|
|
95
|
+
return map(views, v => ({
|
|
96
|
+
id: v.id,
|
|
97
|
+
label: v.title ?? v.id,
|
|
98
|
+
keywords: [
|
|
99
|
+
v.title ?? v.id,
|
|
100
|
+
...(v.tags ?? []),
|
|
101
|
+
...(v.view.description ? [v.view.description] : [])
|
|
102
|
+
],
|
|
103
|
+
onClick: () => {
|
|
104
|
+
store.setState({
|
|
105
|
+
hoveredNodeId: null,
|
|
106
|
+
lastOnNavigate: {
|
|
107
|
+
fromView: view.id,
|
|
108
|
+
toView: v.id,
|
|
109
|
+
fromNode: null
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
onNavigateTo?.(v.id)
|
|
113
|
+
},
|
|
114
|
+
leftSection: <IconSitemap />
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { nodeActions, viewActions } = useMemo(() => ({
|
|
119
|
+
nodeActions: buildFilteredActions(getNodeActionsData(), query),
|
|
120
|
+
viewActions: buildFilteredActions(getViewActionsData(), query)
|
|
121
|
+
}),
|
|
122
|
+
[model, store, view, query]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const portalProps = useMantinePortalProps();
|
|
126
|
+
return (
|
|
127
|
+
<Spotlight.Root
|
|
128
|
+
{...portalProps}
|
|
129
|
+
shortcut={['mod + f', 'mod + k']}
|
|
130
|
+
query={query}
|
|
131
|
+
onQueryChange={setQuery}
|
|
132
|
+
scrollable
|
|
133
|
+
maxHeight={'calc(100vh - 100px)'}
|
|
134
|
+
>
|
|
135
|
+
<Spotlight.Search
|
|
136
|
+
placeholder="Search elements in current view and other views..."
|
|
137
|
+
leftSection={<IconSearch stroke={1.5} />}
|
|
138
|
+
/>
|
|
139
|
+
<Spotlight.ActionsList>
|
|
140
|
+
{nodeActions.length > 0 && <SpotlightActionsGroup label="Elements">{nodeActions}</SpotlightActionsGroup> }
|
|
141
|
+
{viewActions.length > 0 && <SpotlightActionsGroup label="Views">{viewActions}</SpotlightActionsGroup> }
|
|
142
|
+
{nodeActions.length == 0 && viewActions.length == 0 && <Spotlight.Empty>Nothing found...</Spotlight.Empty> }
|
|
143
|
+
</Spotlight.ActionsList>
|
|
144
|
+
</Spotlight.Root>
|
|
145
|
+
);
|
|
146
|
+
});
|
|
@@ -9,7 +9,9 @@ export type StaticLikeC4DiagramProps = Pick<
|
|
|
9
9
|
| 'fitView'
|
|
10
10
|
| 'fitViewPadding'
|
|
11
11
|
| 'background'
|
|
12
|
-
| '
|
|
12
|
+
| 'enableElementDetails'
|
|
13
|
+
| 'enableRelationshipDetails'
|
|
14
|
+
| 'enableRelationshipBrowser'
|
|
13
15
|
| 'initialWidth'
|
|
14
16
|
| 'initialHeight'
|
|
15
17
|
| 'renderIcon'
|
|
@@ -20,7 +22,9 @@ export function StaticLikeC4Diagram({
|
|
|
20
22
|
view,
|
|
21
23
|
fitView = true,
|
|
22
24
|
fitViewPadding = 0,
|
|
23
|
-
|
|
25
|
+
enableElementDetails = false,
|
|
26
|
+
enableRelationshipDetails = false,
|
|
27
|
+
enableRelationshipBrowser = enableRelationshipDetails,
|
|
24
28
|
background = 'transparent',
|
|
25
29
|
...rest
|
|
26
30
|
}: StaticLikeC4DiagramProps) {
|
|
@@ -34,16 +38,17 @@ export function StaticLikeC4Diagram({
|
|
|
34
38
|
zoomable={false}
|
|
35
39
|
controls={false}
|
|
36
40
|
background={background}
|
|
37
|
-
showElementLinks={showElementLinks}
|
|
38
41
|
showDiagramTitle={false}
|
|
39
42
|
showNotations={false}
|
|
40
|
-
|
|
43
|
+
enableElementDetails={enableElementDetails}
|
|
44
|
+
enableRelationshipDetails={enableRelationshipDetails}
|
|
45
|
+
enableRelationshipBrowser={enableRelationshipBrowser}
|
|
41
46
|
enableDynamicViewWalkthrough={false}
|
|
42
|
-
enableRelationshipsBrowser={false}
|
|
43
47
|
experimentalEdgeEditing={false}
|
|
44
48
|
enableFocusMode={false}
|
|
45
49
|
nodesSelectable={false}
|
|
46
50
|
nodesDraggable={false}
|
|
51
|
+
enableSearch={false}
|
|
47
52
|
{...rest}
|
|
48
53
|
/>
|
|
49
54
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createVar, globalStyle,
|
|
2
|
-
import { mantine, vars, whereDark, xyvars } from '../theme-vars'
|
|
1
|
+
import { createVar, globalStyle, style } from '@vanilla-extract/css'
|
|
2
|
+
import { mantine, vars, whereDark, whereLight, xyvars } from '../theme-vars'
|
|
3
3
|
|
|
4
|
-
const mixColor = createVar('mix-color')
|
|
4
|
+
export const mixColor = createVar('mix-color')
|
|
5
5
|
export const container = style({
|
|
6
6
|
position: 'absolute',
|
|
7
7
|
top: 0,
|
|
@@ -16,41 +16,46 @@ export const container = style({
|
|
|
16
16
|
isolation: 'isolate',
|
|
17
17
|
WebkitBackdropFilter: 'blur(var(--backdrop-blur))',
|
|
18
18
|
backdropFilter: 'blur(var(--backdrop-blur))',
|
|
19
|
-
backgroundColor: `
|
|
19
|
+
backgroundColor: `rgb(200 200 200 / var(--backdrop-opacity))`,
|
|
20
20
|
vars: {
|
|
21
|
-
'--backdrop-blur': '
|
|
22
|
-
'--backdrop-opacity': '
|
|
23
|
-
[mixColor]: `black
|
|
24
|
-
[xyvars.edge.stroke]: vars.relation.lineColor,
|
|
25
|
-
[xyvars.edge.strokeSelected]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
|
|
26
|
-
[xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, rgba(255 255 255 / 0.85) 50%)`,
|
|
27
|
-
[xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 30%)`
|
|
21
|
+
'--backdrop-blur': '0px',
|
|
22
|
+
'--backdrop-opacity': '0%',
|
|
23
|
+
[mixColor]: `black`
|
|
28
24
|
},
|
|
29
25
|
selectors: {
|
|
30
26
|
[`${whereDark} &`]: {
|
|
27
|
+
backgroundColor: `rgb(36 36 36 / var(--backdrop-opacity))`,
|
|
31
28
|
vars: {
|
|
32
|
-
[mixColor]: `white
|
|
33
|
-
[xyvars.edge.labelColor]: vars.relation.labelColor,
|
|
34
|
-
[xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 50%)`
|
|
29
|
+
[mixColor]: `white`
|
|
35
30
|
}
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
33
|
})
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
export const cssReactflowMarker = style({})
|
|
36
|
+
globalStyle(`.react-flow${cssReactflowMarker}`, {
|
|
37
|
+
vars: {
|
|
38
|
+
// [xyvars.background.color]: vars.likec4.background.color,
|
|
39
|
+
[xyvars.background.pattern.color]: vars.likec4.background.pattern.color
|
|
40
|
+
}
|
|
42
41
|
})
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
|
|
43
|
+
globalStyle(`${whereDark} .react-flow${cssReactflowMarker}`, {
|
|
44
|
+
vars: {
|
|
45
|
+
[xyvars.edge.labelColor]: vars.relation.labelColor,
|
|
46
|
+
[xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 50%)`
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
globalStyle(`:where(${cssReactflowMarker}) .react-flow__attribution`, {
|
|
50
|
+
display: 'none'
|
|
45
51
|
})
|
|
46
|
-
globalStyle(`:where(${
|
|
47
|
-
|
|
52
|
+
globalStyle(`:where(${cssReactflowMarker}) .react-flow__edge-text`, {
|
|
53
|
+
fontSize: 16
|
|
48
54
|
})
|
|
49
|
-
|
|
50
|
-
globalStyle(`${container} .react-flow__edges > svg`, {
|
|
55
|
+
globalStyle(`${cssReactflowMarker} .react-flow__edges > svg`, {
|
|
51
56
|
mixBlendMode: 'plus-lighter'
|
|
52
57
|
})
|
|
53
|
-
globalStyle(
|
|
58
|
+
globalStyle(`${whereLight} ${cssReactflowMarker} .react-flow__edges > svg`, {
|
|
54
59
|
mixBlendMode: 'screen'
|
|
55
60
|
})
|
|
56
61
|
|