@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/diagram",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
6
6
|
"homepage": "https://likec4.dev",
|
|
@@ -23,44 +23,49 @@
|
|
|
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
|
+
"build": "",
|
|
29
30
|
"lint": "run -T eslint src/ --fix",
|
|
30
|
-
"clean": "rm -r -f -g 'dist/*' .tsbuildinfo"
|
|
31
|
+
"clean": "rm -r -f -g 'dist/*' .tsbuildinfo",
|
|
32
|
+
"test": "vitest run --no-isolate"
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|
|
33
35
|
"@dagrejs/dagre": "^1.1.4",
|
|
34
|
-
"@likec4/core": "1.
|
|
35
|
-
"@mantine/vanilla-extract": "7.
|
|
36
|
+
"@likec4/core": "1.18.0",
|
|
37
|
+
"@mantine/vanilla-extract": "7.15.1",
|
|
36
38
|
"@react-hookz/web": "^24.0.4",
|
|
37
39
|
"@tabler/icons-react": "3.17.0",
|
|
38
|
-
"@vanilla-extract/css": "^1.16.
|
|
40
|
+
"@vanilla-extract/css": "^1.16.1",
|
|
39
41
|
"@vanilla-extract/css-utils": "^0.1.4",
|
|
40
42
|
"@vanilla-extract/dynamic": "^2.1.2",
|
|
41
|
-
"@xyflow/react": "12.3.
|
|
42
|
-
"@xyflow/system": "0.0.
|
|
43
|
+
"@xyflow/react": "12.3.5",
|
|
44
|
+
"@xyflow/system": "0.0.46",
|
|
43
45
|
"bezier-js": "^6.1.4",
|
|
44
46
|
"clsx": "^2.1.1",
|
|
45
47
|
"d3-shape": "^3.2.0",
|
|
46
|
-
"esm-env": "^1.1
|
|
48
|
+
"esm-env": "^1.2.1",
|
|
47
49
|
"fast-equals": "^5.0.1",
|
|
48
|
-
"framer-motion": "^11.
|
|
50
|
+
"framer-motion": "^11.14.1",
|
|
49
51
|
"khroma": "^2.1.0",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
+
"react-error-boundary": "^4.1.2",
|
|
53
|
+
"remeda": "^2.17.4",
|
|
54
|
+
"type-fest": "4.28.1",
|
|
52
55
|
"zustand": "^4.5.5"
|
|
53
56
|
},
|
|
54
57
|
"peerDependencies": {
|
|
55
|
-
"@mantine/core": "^7.
|
|
56
|
-
"@mantine/hooks": "^7.
|
|
58
|
+
"@mantine/core": "^7.15.1",
|
|
59
|
+
"@mantine/hooks": "^7.15.1",
|
|
60
|
+
"@mantine/spotlight": "^7.15.1",
|
|
57
61
|
"react": "^18.3.1",
|
|
58
62
|
"react-dom": "^18.3.1"
|
|
59
63
|
},
|
|
60
64
|
"devDependencies": {
|
|
61
|
-
"@likec4/tsconfig": "1.
|
|
62
|
-
"@mantine/core": "7.
|
|
63
|
-
"@mantine/hooks": "7.
|
|
65
|
+
"@likec4/tsconfig": "1.18.0",
|
|
66
|
+
"@mantine/core": "7.15.1",
|
|
67
|
+
"@mantine/hooks": "7.15.1",
|
|
68
|
+
"@mantine/spotlight": "7.15.1",
|
|
64
69
|
"@types/bezier-js": "^4.1.3",
|
|
65
70
|
"@types/d3-shape": "^3.1.6",
|
|
66
71
|
"@types/react": "^18.3.11",
|
|
@@ -68,6 +73,7 @@
|
|
|
68
73
|
"execa": "^9.3.1",
|
|
69
74
|
"react": "^18.3.1",
|
|
70
75
|
"react-dom": "^18.3.1",
|
|
71
|
-
"typescript": "^5.
|
|
76
|
+
"typescript": "^5.7.2",
|
|
77
|
+
"vitest": "^2.1.8"
|
|
72
78
|
}
|
|
73
79
|
}
|
|
@@ -3,9 +3,9 @@ import type {
|
|
|
3
3
|
DiagramNode,
|
|
4
4
|
DiagramView,
|
|
5
5
|
Fqn,
|
|
6
|
-
|
|
6
|
+
RelationId,
|
|
7
7
|
ViewChange,
|
|
8
|
-
|
|
8
|
+
ViewId,
|
|
9
9
|
WhereOperator
|
|
10
10
|
} from '@likec4/core'
|
|
11
11
|
import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react'
|
|
@@ -13,7 +13,7 @@ import type { XYBackground } from './xyflow/XYFlowBackground'
|
|
|
13
13
|
|
|
14
14
|
export type { WhereOperator }
|
|
15
15
|
|
|
16
|
-
export type DiagramNodeWithNavigate<ID extends string =
|
|
16
|
+
export type DiagramNodeWithNavigate<ID extends string = ViewId> = Omit<DiagramNode, 'navigateTo'> & {
|
|
17
17
|
navigateTo: ID
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -27,7 +27,7 @@ export type ElementIconRenderer = (props: {
|
|
|
27
27
|
node: ElementIconNodeProps
|
|
28
28
|
}) => ReactNode
|
|
29
29
|
|
|
30
|
-
export type OnNavigateTo<ID extends string =
|
|
30
|
+
export type OnNavigateTo<ID extends string = ViewId> = (
|
|
31
31
|
to: ID,
|
|
32
32
|
// These fields present if navigateTo triggered by a node click
|
|
33
33
|
event?: ReactMouseEvent,
|
|
@@ -148,6 +148,12 @@ export interface LikeC4DiagramProperties {
|
|
|
148
148
|
*/
|
|
149
149
|
enableFocusMode?: boolean | undefined
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Enable search popup for elements and views
|
|
153
|
+
* @default true
|
|
154
|
+
*/
|
|
155
|
+
enableSearch?: boolean | undefined
|
|
156
|
+
|
|
151
157
|
/**
|
|
152
158
|
* Enable modal with element details
|
|
153
159
|
* @default false
|
|
@@ -185,6 +191,22 @@ export interface LikeC4DiagramProperties {
|
|
|
185
191
|
where?: WhereOperator<string, string> | undefined
|
|
186
192
|
}
|
|
187
193
|
|
|
194
|
+
export type OpenSourceParams =
|
|
195
|
+
| {
|
|
196
|
+
element: Fqn
|
|
197
|
+
property?: string
|
|
198
|
+
}
|
|
199
|
+
| {
|
|
200
|
+
relation: RelationId
|
|
201
|
+
}
|
|
202
|
+
| {
|
|
203
|
+
deployment: Fqn
|
|
204
|
+
property?: string
|
|
205
|
+
}
|
|
206
|
+
| {
|
|
207
|
+
view: ViewId
|
|
208
|
+
}
|
|
209
|
+
|
|
188
210
|
export interface LikeC4DiagramEventHandlers {
|
|
189
211
|
onChange?: OnChange | null | undefined
|
|
190
212
|
onNavigateTo?: OnNavigateTo | null | undefined
|
|
@@ -199,12 +221,5 @@ export interface LikeC4DiagramEventHandlers {
|
|
|
199
221
|
// if set, will render a burger menu icon in the top left corner
|
|
200
222
|
onBurgerMenuClick?: null | undefined | (() => void)
|
|
201
223
|
|
|
202
|
-
|
|
203
|
-
onOpenSourceView?: null | undefined | (() => void)
|
|
204
|
-
|
|
205
|
-
// if set, will render an icon in properties webview for each element
|
|
206
|
-
onOpenSourceElement?: null | undefined | ((fqn: Fqn) => void)
|
|
207
|
-
|
|
208
|
-
// if set, will be called on edge click
|
|
209
|
-
onOpenSourceRelation?: null | undefined | ((id: RelationID) => void)
|
|
224
|
+
onOpenSource?: null | undefined | ((params: OpenSourceParams) => void)
|
|
210
225
|
}
|
package/src/LikeC4Diagram.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { ComputedView, isDeploymentView } from '@likec4/core'
|
|
1
2
|
import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
|
|
2
3
|
import clsx from 'clsx'
|
|
3
|
-
import { deepEqual
|
|
4
|
+
import { deepEqual } from 'fast-equals'
|
|
4
5
|
import { memo, useEffect, useRef } from 'react'
|
|
5
6
|
import { isEmpty } from 'remeda'
|
|
6
7
|
import { rootClassName } from './globals.css'
|
|
@@ -9,6 +10,7 @@ import { LikeC4CustomColors } from './LikeC4CustomColors'
|
|
|
9
10
|
import * as css from './LikeC4Diagram.css'
|
|
10
11
|
import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
|
|
11
12
|
import { useLikeC4Model } from './likec4model'
|
|
13
|
+
import { LikeC4Search } from './LikeC4Search'
|
|
12
14
|
import { Overlays } from './overlays'
|
|
13
15
|
import { DiagramContextProvider } from './state/DiagramContext'
|
|
14
16
|
import { EnsureMantine } from './ui/EnsureMantine'
|
|
@@ -39,6 +41,7 @@ export function LikeC4Diagram({
|
|
|
39
41
|
enableElementDetails = false,
|
|
40
42
|
enableRelationshipDetails = enableElementDetails,
|
|
41
43
|
enableRelationshipBrowser = enableRelationshipDetails,
|
|
44
|
+
enableSearch = true,
|
|
42
45
|
initialWidth,
|
|
43
46
|
initialHeight,
|
|
44
47
|
keepAspectRatio = false,
|
|
@@ -52,9 +55,7 @@ export function LikeC4Diagram({
|
|
|
52
55
|
onNavigateTo,
|
|
53
56
|
onNodeClick,
|
|
54
57
|
onNodeContextMenu,
|
|
55
|
-
|
|
56
|
-
onOpenSourceView,
|
|
57
|
-
onOpenSourceRelation,
|
|
58
|
+
onOpenSource,
|
|
58
59
|
onBurgerMenuClick,
|
|
59
60
|
renderIcon,
|
|
60
61
|
where,
|
|
@@ -110,10 +111,12 @@ export function LikeC4Diagram({
|
|
|
110
111
|
showNavigationButtons={showNavigationButtons && !!onNavigateTo}
|
|
111
112
|
showNotations={showNotations}
|
|
112
113
|
enableFocusMode={enableFocusMode}
|
|
113
|
-
|
|
114
|
+
enableSearch={hasLikec4model && enableSearch}
|
|
114
115
|
enableElementDetails={enableElementDetails && hasLikec4model}
|
|
115
116
|
enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
|
|
116
117
|
enableRelationshipBrowser={enableRelationshipBrowser && hasLikec4model}
|
|
118
|
+
// TODO: temporary disable relationship details for deployment views
|
|
119
|
+
enableRelationshipDetails={enableRelationshipDetails && hasLikec4model && !isDeploymentView(view)}
|
|
117
120
|
// Apply where filter only in readonly mode
|
|
118
121
|
whereFilter={readonly ? (where ?? null) : null}
|
|
119
122
|
renderIcon={renderIcon ?? null}
|
|
@@ -126,14 +129,13 @@ export function LikeC4Diagram({
|
|
|
126
129
|
onChange={onChange ?? null}
|
|
127
130
|
onNavigateTo={onNavigateTo ?? null}
|
|
128
131
|
onCanvasDblClick={onCanvasDblClick ?? null}
|
|
129
|
-
|
|
130
|
-
onOpenSourceElement={onOpenSourceElement ?? null}
|
|
131
|
-
onOpenSourceRelation={onOpenSourceRelation ?? null}
|
|
132
|
+
onOpenSource={onOpenSource ?? null}
|
|
132
133
|
onBurgerMenuClick={onBurgerMenuClick ?? null}
|
|
133
134
|
>
|
|
134
135
|
<LikeC4DiagramInnerMemo
|
|
135
136
|
background={background}
|
|
136
137
|
showDiagramTitle={showDiagramTitle}
|
|
138
|
+
enableSearch={hasLikec4model && enableSearch}
|
|
137
139
|
/>
|
|
138
140
|
</DiagramContextProvider>
|
|
139
141
|
</XYFlowProvider>
|
|
@@ -146,10 +148,12 @@ LikeC4Diagram.displayName = 'LikeC4Diagram'
|
|
|
146
148
|
type LikeC4DiagramInnerProps = {
|
|
147
149
|
background: NonNullable<LikeC4DiagramProperties['background']>
|
|
148
150
|
showDiagramTitle: boolean
|
|
151
|
+
enableSearch: boolean
|
|
149
152
|
}
|
|
150
153
|
const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(function LikeC4DiagramInner({
|
|
151
154
|
background,
|
|
152
|
-
showDiagramTitle
|
|
155
|
+
showDiagramTitle,
|
|
156
|
+
enableSearch
|
|
153
157
|
}) {
|
|
154
158
|
const {
|
|
155
159
|
isInitialized,
|
|
@@ -183,6 +187,7 @@ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(fun
|
|
|
183
187
|
/>
|
|
184
188
|
</XYFlow>
|
|
185
189
|
{enableOverlays && <Overlays />}
|
|
190
|
+
{enableSearch && <LikeC4Search />}
|
|
186
191
|
{isInitialized && (
|
|
187
192
|
<>
|
|
188
193
|
{fitView && <FitViewOnDiagramChange />}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Center, Group, Highlight, Text } from '@mantine/core'
|
|
2
|
+
import { Spotlight, type SpotlightActionData, SpotlightActionsGroup } from '@mantine/spotlight'
|
|
3
|
+
import { IconRectangularPrism, IconSearch, IconSitemap } 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
|
+
|
|
9
|
+
interface QueryMatch {
|
|
10
|
+
data: SpotlightActionData
|
|
11
|
+
keyword: string
|
|
12
|
+
match: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function filterAction(actionData: SpotlightActionData, query: string): QueryMatch | undefined {
|
|
16
|
+
const queryLower = query.toLowerCase()
|
|
17
|
+
const keywords = Array.isArray(actionData.keywords) ? actionData.keywords : []
|
|
18
|
+
const keywordMatch = keywords
|
|
19
|
+
.map(keyword => {
|
|
20
|
+
const index = keyword.toLowerCase().indexOf(queryLower)
|
|
21
|
+
return index !== -1
|
|
22
|
+
? {
|
|
23
|
+
keyword: keyword,
|
|
24
|
+
match: keyword.substring(index, index + query.length)
|
|
25
|
+
}
|
|
26
|
+
: null
|
|
27
|
+
})
|
|
28
|
+
.find(match => !!match)
|
|
29
|
+
return !!keywordMatch
|
|
30
|
+
? {
|
|
31
|
+
data: actionData,
|
|
32
|
+
keyword: keywordMatch.keyword,
|
|
33
|
+
match: keywordMatch.match
|
|
34
|
+
}
|
|
35
|
+
: undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildFilteredActions(actionsData: SpotlightActionData[], query: string): JSX.Element[] {
|
|
39
|
+
return pipe(
|
|
40
|
+
actionsData,
|
|
41
|
+
map(a => filterAction(a, query)),
|
|
42
|
+
filter(qm => !!qm),
|
|
43
|
+
map(qm => buildSpotlightAction(qm))
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function highlightMatch(match: QueryMatch): JSX.Element {
|
|
48
|
+
return <Highlight highlight={match.match}>{match.keyword}</Highlight>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildSpotlightAction(matchedAction: QueryMatch): JSX.Element {
|
|
52
|
+
const isMatchInLabel = matchedAction.keyword == matchedAction.data.label
|
|
53
|
+
return (
|
|
54
|
+
<Spotlight.Action key={matchedAction.data.id} onClick={matchedAction.data.onClick}>
|
|
55
|
+
<Center style={{ marginRight: '8px ' }}>
|
|
56
|
+
{matchedAction.data.leftSection}
|
|
57
|
+
</Center>
|
|
58
|
+
<Group wrap="nowrap" w="100%">
|
|
59
|
+
<div style={{ flex: 1 }}>
|
|
60
|
+
<Text>
|
|
61
|
+
{isMatchInLabel ? highlightMatch(matchedAction) : matchedAction.data.label}
|
|
62
|
+
</Text>
|
|
63
|
+
{!isMatchInLabel && <Text opacity={0.6} size="xs">{highlightMatch(matchedAction)}</Text>}
|
|
64
|
+
</div>
|
|
65
|
+
</Group>
|
|
66
|
+
</Spotlight.Action>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const LikeC4Search = memo(() => {
|
|
71
|
+
const view = useDiagramState(s => s.view)
|
|
72
|
+
const model = useLikeC4Model(true)
|
|
73
|
+
const store = useDiagramStoreApi()
|
|
74
|
+
const [query, setQuery] = useState('')
|
|
75
|
+
|
|
76
|
+
const getNodeActionsData = (): SpotlightActionData[] => {
|
|
77
|
+
const { focusOnNode } = store.getState()
|
|
78
|
+
|
|
79
|
+
return pipe(
|
|
80
|
+
view.nodes,
|
|
81
|
+
filter(n => !!n.title),
|
|
82
|
+
map(n => ({
|
|
83
|
+
id: n.id,
|
|
84
|
+
label: n.title,
|
|
85
|
+
keywords: [
|
|
86
|
+
n.title,
|
|
87
|
+
...(n.tags ?? []).map(t => `#${t}`),
|
|
88
|
+
...(n.description ? [n.description] : [])
|
|
89
|
+
].filter(k => k.toLowerCase()),
|
|
90
|
+
onClick: () => focusOnNode(n.id),
|
|
91
|
+
leftSection: <IconRectangularPrism />
|
|
92
|
+
}))
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const getViewActionsData = (): SpotlightActionData[] => {
|
|
97
|
+
const { onNavigateTo } = store.getState()
|
|
98
|
+
const views = model.views().toArray()
|
|
99
|
+
|
|
100
|
+
return map(views, v => ({
|
|
101
|
+
id: v.id,
|
|
102
|
+
label: v.title ?? v.id,
|
|
103
|
+
keywords: [
|
|
104
|
+
v.title ?? v.id,
|
|
105
|
+
...(v.tags ?? []),
|
|
106
|
+
...(v.$view.description ? [v.$view.description] : [])
|
|
107
|
+
],
|
|
108
|
+
onClick: () => {
|
|
109
|
+
store.setState({
|
|
110
|
+
hoveredNodeId: null,
|
|
111
|
+
lastOnNavigate: {
|
|
112
|
+
fromView: view.id,
|
|
113
|
+
toView: v.id,
|
|
114
|
+
fromNode: null
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
onNavigateTo?.(v.id)
|
|
118
|
+
},
|
|
119
|
+
leftSection: <IconSitemap />
|
|
120
|
+
}))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { nodeActions, viewActions } = useMemo(() => ({
|
|
124
|
+
nodeActions: buildFilteredActions(getNodeActionsData(), query),
|
|
125
|
+
viewActions: buildFilteredActions(getViewActionsData(), query)
|
|
126
|
+
}), [model, store, view, query])
|
|
127
|
+
|
|
128
|
+
const portalProps = useMantinePortalProps()
|
|
129
|
+
return (
|
|
130
|
+
<Spotlight.Root
|
|
131
|
+
{...portalProps}
|
|
132
|
+
shortcut={['mod + f', 'mod + k']}
|
|
133
|
+
query={query}
|
|
134
|
+
onQueryChange={setQuery}
|
|
135
|
+
scrollable
|
|
136
|
+
maxHeight={'calc(100vh - 100px)'}
|
|
137
|
+
>
|
|
138
|
+
<Spotlight.Search
|
|
139
|
+
placeholder="Search elements in current view and other views..."
|
|
140
|
+
leftSection={<IconSearch stroke={1.5} />}
|
|
141
|
+
/>
|
|
142
|
+
<Spotlight.ActionsList>
|
|
143
|
+
{nodeActions.length > 0 && <SpotlightActionsGroup label="Elements">{nodeActions}</SpotlightActionsGroup>}
|
|
144
|
+
{viewActions.length > 0 && <SpotlightActionsGroup label="Views">{viewActions}</SpotlightActionsGroup>}
|
|
145
|
+
{nodeActions.length == 0 && viewActions.length == 0 && <Spotlight.Empty>Nothing found...</Spotlight.Empty>}
|
|
146
|
+
</Spotlight.ActionsList>
|
|
147
|
+
</Spotlight.Root>
|
|
148
|
+
)
|
|
149
|
+
})
|
|
@@ -2,10 +2,16 @@ import { shallowEqual } from 'fast-equals'
|
|
|
2
2
|
import { useContext } from 'react'
|
|
3
3
|
import { useStoreWithEqualityFn as useZustandStore } from 'zustand/traditional'
|
|
4
4
|
|
|
5
|
+
import type { ViewId } from '@likec4/core'
|
|
5
6
|
import { DiagramContext } from '../state/DiagramContext'
|
|
6
7
|
import type { DiagramState, DiagramStoreApi } from '../state/diagramStore'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
const selectViewId = (state: DiagramState) => state.view.id
|
|
10
|
+
export function useCurrentViewId(): ViewId {
|
|
11
|
+
return useDiagramState(selectViewId)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useDiagramState<StateSlice = unknown>(
|
|
9
15
|
selector: (state: DiagramState) => StateSlice,
|
|
10
16
|
equalityFn?: (a: StateSlice, b: StateSlice) => boolean
|
|
11
17
|
): StateSlice {
|
|
@@ -18,7 +24,7 @@ function useDiagramState<StateSlice = unknown>(
|
|
|
18
24
|
return useZustandStore(store, selector, equalityFn ?? shallowEqual)
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
function useDiagramStoreApi(): DiagramStoreApi {
|
|
27
|
+
export function useDiagramStoreApi(): DiagramStoreApi {
|
|
22
28
|
const store = useContext(DiagramContext)
|
|
23
29
|
|
|
24
30
|
if (store === null) {
|
|
@@ -28,4 +34,3 @@ function useDiagramStoreApi(): DiagramStoreApi {
|
|
|
28
34
|
return store
|
|
29
35
|
}
|
|
30
36
|
export type { DiagramState, DiagramStoreApi }
|
|
31
|
-
export { useDiagramState, useDiagramStoreApi }
|
package/src/hooks/useXYFlow.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useNodesData, useReactFlow, useStore, useStoreApi } from '@xyflow/react'
|
|
1
|
+
import { useInternalNode, useNodesData, useReactFlow, useStore, useStoreApi } from '@xyflow/react'
|
|
2
2
|
import { deepEqual, shallowEqual } from 'fast-equals'
|
|
3
3
|
import { useCallback } from 'react'
|
|
4
4
|
import type { XYFlowEdge, XYFlowNode, XYFlowState } from '../xyflow/types'
|
|
@@ -6,6 +6,7 @@ import type { XYFlowEdge, XYFlowNode, XYFlowState } from '../xyflow/types'
|
|
|
6
6
|
export const useXYFlow = useReactFlow<XYFlowNode, XYFlowEdge>
|
|
7
7
|
|
|
8
8
|
export const useXYNodesData = useNodesData<XYFlowNode>
|
|
9
|
+
export const useXYInternalNode = useInternalNode<XYFlowNode>
|
|
9
10
|
|
|
10
11
|
export function useXYStore<StateSlice = unknown>(
|
|
11
12
|
selector: (state: XYFlowState) => StateSlice,
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
compareNatural,
|
|
3
|
-
type Fqn,
|
|
4
|
-
type LikeC4DiagramModel,
|
|
5
|
-
type LikeC4Model,
|
|
6
|
-
type LikeC4ViewModel,
|
|
7
|
-
type ViewID
|
|
8
|
-
} from '@likec4/core'
|
|
1
|
+
import { compareNatural, type Fqn, type LikeC4Model, type ViewId } from '@likec4/core'
|
|
9
2
|
import { useMemo } from 'react'
|
|
10
3
|
import { useLikeC4Model } from './useLikeC4Model'
|
|
11
4
|
|
|
@@ -18,12 +11,12 @@ interface LikeC4ModelTreeNodeData {
|
|
|
18
11
|
const sortByLabel = (a: LikeC4ModelTreeNodeData, b: LikeC4ModelTreeNodeData) => compareNatural(a.label, b.label)
|
|
19
12
|
|
|
20
13
|
function buildNode(
|
|
21
|
-
element: LikeC4Model.
|
|
14
|
+
element: LikeC4Model.Node | LikeC4Model.Element
|
|
22
15
|
): LikeC4ModelTreeNodeData {
|
|
23
16
|
return {
|
|
24
17
|
label: element.title,
|
|
25
18
|
value: element.id,
|
|
26
|
-
children: element.children().map(buildNode).sort(sortByLabel)
|
|
19
|
+
children: [...element.children()].map(buildNode).sort(sortByLabel)
|
|
27
20
|
}
|
|
28
21
|
}
|
|
29
22
|
|
|
@@ -31,14 +24,14 @@ function buildNode(
|
|
|
31
24
|
* Returns a tree of elements in the model.
|
|
32
25
|
* If `viewId` is provided, returns the tree of elements in the view.
|
|
33
26
|
*/
|
|
34
|
-
export function useLikeC4ElementsTree(viewId?:
|
|
27
|
+
export function useLikeC4ElementsTree(viewId?: ViewId): LikeC4ModelTreeNodeData[] {
|
|
35
28
|
const model = useLikeC4Model(true)
|
|
36
29
|
return useMemo(() => {
|
|
37
30
|
if (viewId) {
|
|
38
31
|
const view = model.view(viewId)
|
|
39
|
-
return view.roots().map(buildNode).sort(sortByLabel)
|
|
32
|
+
return [...view.roots()].map(buildNode).sort(sortByLabel)
|
|
40
33
|
} else {
|
|
41
|
-
return model.roots().map(buildNode).sort(sortByLabel)
|
|
34
|
+
return [...model.roots()].map(buildNode).sort(sortByLabel)
|
|
42
35
|
}
|
|
43
36
|
}, [model, viewId ?? null])
|
|
44
37
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type ComputedView, type DiagramView, type LikeC4Model, type
|
|
1
|
+
import { type ComputedView, type DiagramView, type Fqn, LikeC4Model, type ViewId } from '@likec4/core'
|
|
2
2
|
import { useContext } from 'react'
|
|
3
3
|
import { isDefined, isNonNullish, isString } from 'remeda'
|
|
4
4
|
import type { LiteralUnion } from 'type-fest'
|
|
5
|
+
import { useCurrentViewId } from '../hooks'
|
|
5
6
|
import { LikeC4ModelContext } from './LikeC4ModelContext'
|
|
6
7
|
|
|
7
8
|
export function useLikeC4Model(): LikeC4Model | null
|
|
@@ -9,11 +10,11 @@ export function useLikeC4Model(strict: true): LikeC4Model
|
|
|
9
10
|
export function useLikeC4Model(strict: boolean): LikeC4Model | null
|
|
10
11
|
export function useLikeC4Model(strict: true, type: 'layouted'): LikeC4Model.Layouted
|
|
11
12
|
export function useLikeC4Model(strict: true, type: 'computed'): LikeC4Model.Computed
|
|
12
|
-
export function useLikeC4Model(strict: true, type:
|
|
13
|
+
export function useLikeC4Model(strict: true, type: 'layouted' | 'computed' | undefined): LikeC4Model
|
|
13
14
|
export function useLikeC4Model(strict: boolean, type: 'layouted'): LikeC4Model.Layouted | null
|
|
14
15
|
export function useLikeC4Model(strict: boolean, type: 'computed'): LikeC4Model.Computed | null
|
|
15
|
-
export function useLikeC4Model(strict: boolean, type:
|
|
16
|
-
export function useLikeC4Model(strict?: boolean, type?:
|
|
16
|
+
export function useLikeC4Model(strict: boolean, type: 'layouted' | 'computed' | undefined): LikeC4Model | null
|
|
17
|
+
export function useLikeC4Model(strict?: boolean, type?: 'layouted' | 'computed') {
|
|
17
18
|
const model = useContext(LikeC4ModelContext)
|
|
18
19
|
|
|
19
20
|
if (isString(type) && isNonNullish(model) && model.type !== type) {
|
|
@@ -26,33 +27,42 @@ export function useLikeC4Model(strict?: boolean, type?: LikeC4Model['type']) {
|
|
|
26
27
|
return model
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export function useLikeC4Views():
|
|
30
|
-
return useLikeC4Model(true).
|
|
30
|
+
export function useLikeC4Views(): Record<ViewId, ComputedView | DiagramView> {
|
|
31
|
+
return useLikeC4Model(true).$model.views
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
export function useLikeC4ViewModel(viewId: LiteralUnion<
|
|
34
|
+
export function useLikeC4ViewModel(viewId: LiteralUnion<ViewId, string>): LikeC4Model.View {
|
|
35
|
+
return useLikeC4Model(true).view(viewId)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useLikeC4CurrentViewModel(): LikeC4Model.View {
|
|
39
|
+
const viewId = useCurrentViewId()
|
|
34
40
|
return useLikeC4Model(true).view(viewId)
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
/**
|
|
38
44
|
* Parsed view, computed or layouted
|
|
39
45
|
*/
|
|
40
|
-
export function useLikeC4View(viewId: LiteralUnion<
|
|
46
|
+
export function useLikeC4View(viewId: LiteralUnion<ViewId, string>): ComputedView | DiagramView | null {
|
|
41
47
|
const model = useLikeC4Model(true)
|
|
42
48
|
try {
|
|
43
|
-
return model.view(viewId)
|
|
49
|
+
return model.view(viewId).$view
|
|
44
50
|
} catch (error) {
|
|
45
51
|
console.warn(error)
|
|
46
52
|
return null
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
export function useLikeC4DiagramView(viewId: LiteralUnion<
|
|
56
|
+
export function useLikeC4DiagramView(viewId: LiteralUnion<ViewId, string>): DiagramView | null {
|
|
51
57
|
const model = useLikeC4Model(true, 'layouted')
|
|
52
58
|
try {
|
|
53
|
-
return model.view(viewId)
|
|
59
|
+
return model.view(viewId).$view
|
|
54
60
|
} catch (error) {
|
|
55
61
|
console.warn(error)
|
|
56
62
|
return null
|
|
57
63
|
}
|
|
58
64
|
}
|
|
65
|
+
|
|
66
|
+
export function useLikeC4ElementModel(fqn: LiteralUnion<Fqn, string>): LikeC4Model.Element {
|
|
67
|
+
return useLikeC4Model(true).element(fqn)
|
|
68
|
+
}
|