@likec4/diagram 1.17.1 → 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.
Files changed (49) hide show
  1. package/package.json +17 -15
  2. package/src/LikeC4Diagram.props.ts +21 -12
  3. package/src/LikeC4Diagram.tsx +5 -7
  4. package/src/LikeC4Search.tsx +54 -51
  5. package/src/hooks/useDiagramState.ts +8 -3
  6. package/src/hooks/useXYFlow.ts +2 -1
  7. package/src/likec4model/useLikeC4ElementsTree.ts +6 -13
  8. package/src/likec4model/useLikeC4Model.ts +21 -11
  9. package/src/overlays/Overlays.tsx +96 -63
  10. package/src/overlays/edge-details/EdgeDetailsXYFlow.tsx +3 -1
  11. package/src/overlays/edge-details/_types.ts +7 -3
  12. package/src/overlays/edge-details/use-layouted-edge-details.ts +21 -14
  13. package/src/overlays/edge-details/xyflow/ElementNode.tsx +4 -2
  14. package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +4 -4
  15. package/src/overlays/element-details/ElementDetailsCard.tsx +91 -78
  16. package/src/overlays/element-details/TabPanelDeployments.css.ts +69 -0
  17. package/src/overlays/element-details/TabPanelDeployments.tsx +217 -0
  18. package/src/overlays/element-details/TabPanelRelationships.tsx +8 -9
  19. package/src/overlays/element-details/TabPanelStructure.tsx +10 -10
  20. package/src/overlays/relationships-of/RelationshipsOverlay.tsx +1 -1
  21. package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +3 -11
  22. package/src/overlays/relationships-of/SelectElement.tsx +2 -2
  23. package/src/overlays/relationships-of/_types.ts +2 -2
  24. package/src/overlays/relationships-of/use-layouted-relationships.ts +18 -19
  25. package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +1 -3
  26. package/src/overlays/relationships-of/xyflow/ElementNode.tsx +12 -6
  27. package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +0 -1
  28. package/src/state/diagramStore.ts +28 -11
  29. package/src/ui/DiagramTitlePanel.tsx +2 -29
  30. package/src/ui/Link.tsx +40 -0
  31. package/src/ui/notation/NotationPanel.tsx +4 -2
  32. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +2 -2
  33. package/src/ui/top-left/TopLeftPanel.tsx +5 -5
  34. package/src/xyflow/edges/EdgeLabel.tsx +4 -4
  35. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +20 -7
  36. package/src/xyflow/nodes/compound/CompoundNode.css.ts +28 -13
  37. package/src/xyflow/nodes/compound/CompoundNode.tsx +51 -19
  38. package/src/xyflow/nodes/element/ElementNode.tsx +147 -124
  39. package/src/xyflow/nodes/element/element.css.ts +52 -19
  40. package/src/xyflow/nodes/shared/ElementIcon.tsx +1 -1
  41. package/src/xyflow/nodes/shared/Toolbar.tsx +24 -8
  42. package/src/xyflow/useLayoutConstraints.ts +1 -1
  43. package/src/xyflow/useXYFlowEvents.ts +18 -5
  44. package/src/ui/OptionsPanel.css.ts +0 -9
  45. package/src/ui/OptionsPanel.tsx +0 -70
  46. package/src/ui/options/EdgeOptions.tsx +0 -142
  47. package/src/ui/options/NodeOptions.tsx +0 -362
  48. package/src/xyflow/nodes/element/ElementDetails.css.ts +0 -101
  49. 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.17.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",
@@ -26,14 +26,15 @@
26
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
31
  "clean": "rm -r -f -g 'dist/*' .tsbuildinfo",
31
32
  "test": "vitest run --no-isolate"
32
33
  },
33
34
  "dependencies": {
34
35
  "@dagrejs/dagre": "^1.1.4",
35
- "@likec4/core": "1.17.1",
36
- "@mantine/vanilla-extract": "7.14.1",
36
+ "@likec4/core": "1.18.0",
37
+ "@mantine/vanilla-extract": "7.15.1",
37
38
  "@react-hookz/web": "^24.0.4",
38
39
  "@tabler/icons-react": "3.17.0",
39
40
  "@vanilla-extract/css": "^1.16.1",
@@ -44,26 +45,27 @@
44
45
  "bezier-js": "^6.1.4",
45
46
  "clsx": "^2.1.1",
46
47
  "d3-shape": "^3.2.0",
47
- "esm-env": "^1.1.4",
48
+ "esm-env": "^1.2.1",
48
49
  "fast-equals": "^5.0.1",
49
- "framer-motion": "^11.11.17",
50
+ "framer-motion": "^11.14.1",
50
51
  "khroma": "^2.1.0",
51
- "remeda": "^2.17.3",
52
- "type-fest": "4.27.1",
52
+ "react-error-boundary": "^4.1.2",
53
+ "remeda": "^2.17.4",
54
+ "type-fest": "4.28.1",
53
55
  "zustand": "^4.5.5"
54
56
  },
55
57
  "peerDependencies": {
56
- "@mantine/core": "^7.14.1",
57
- "@mantine/hooks": "^7.14.1",
58
- "@mantine/spotlight": "^7.14.1",
58
+ "@mantine/core": "^7.15.1",
59
+ "@mantine/hooks": "^7.15.1",
60
+ "@mantine/spotlight": "^7.15.1",
59
61
  "react": "^18.3.1",
60
62
  "react-dom": "^18.3.1"
61
63
  },
62
64
  "devDependencies": {
63
- "@likec4/tsconfig": "1.17.1",
64
- "@mantine/core": "7.14.1",
65
- "@mantine/hooks": "7.14.1",
66
- "@mantine/spotlight": "7.14.1",
65
+ "@likec4/tsconfig": "1.18.0",
66
+ "@mantine/core": "7.15.1",
67
+ "@mantine/hooks": "7.15.1",
68
+ "@mantine/spotlight": "7.15.1",
67
69
  "@types/bezier-js": "^4.1.3",
68
70
  "@types/d3-shape": "^3.1.6",
69
71
  "@types/react": "^18.3.11",
@@ -72,6 +74,6 @@
72
74
  "react": "^18.3.1",
73
75
  "react-dom": "^18.3.1",
74
76
  "typescript": "^5.7.2",
75
- "vitest": "^2.1.5"
77
+ "vitest": "^2.1.8"
76
78
  }
77
79
  }
@@ -3,9 +3,9 @@ import type {
3
3
  DiagramNode,
4
4
  DiagramView,
5
5
  Fqn,
6
- RelationID,
6
+ RelationId,
7
7
  ViewChange,
8
- ViewID,
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 = ViewID> = Omit<DiagramNode, 'navigateTo'> & {
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 = ViewID> = (
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,
@@ -191,6 +191,22 @@ export interface LikeC4DiagramProperties {
191
191
  where?: WhereOperator<string, string> | undefined
192
192
  }
193
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
+
194
210
  export interface LikeC4DiagramEventHandlers {
195
211
  onChange?: OnChange | null | undefined
196
212
  onNavigateTo?: OnNavigateTo | null | undefined
@@ -205,12 +221,5 @@ export interface LikeC4DiagramEventHandlers {
205
221
  // if set, will render a burger menu icon in the top left corner
206
222
  onBurgerMenuClick?: null | undefined | (() => void)
207
223
 
208
- // if set, will render a source code icon in the top left corner
209
- onOpenSourceView?: null | undefined | (() => void)
210
-
211
- // if set, will render an icon in properties webview for each element
212
- onOpenSourceElement?: null | undefined | ((fqn: Fqn) => void)
213
-
214
- // if set, will be called on edge click
215
- onOpenSourceRelation?: null | undefined | ((id: RelationID) => void)
224
+ onOpenSource?: null | undefined | ((params: OpenSourceParams) => void)
216
225
  }
@@ -1,3 +1,4 @@
1
+ import { ComputedView, isDeploymentView } from '@likec4/core'
1
2
  import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
2
3
  import clsx from 'clsx'
3
4
  import { deepEqual } from 'fast-equals'
@@ -54,9 +55,7 @@ export function LikeC4Diagram({
54
55
  onNavigateTo,
55
56
  onNodeClick,
56
57
  onNodeContextMenu,
57
- onOpenSourceElement,
58
- onOpenSourceView,
59
- onOpenSourceRelation,
58
+ onOpenSource,
60
59
  onBurgerMenuClick,
61
60
  renderIcon,
62
61
  where,
@@ -112,11 +111,12 @@ export function LikeC4Diagram({
112
111
  showNavigationButtons={showNavigationButtons && !!onNavigateTo}
113
112
  showNotations={showNotations}
114
113
  enableFocusMode={enableFocusMode}
115
- enableRelationshipDetails={enableRelationshipDetails && hasLikec4model}
116
114
  enableSearch={hasLikec4model && enableSearch}
117
115
  enableElementDetails={enableElementDetails && hasLikec4model}
118
116
  enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
119
117
  enableRelationshipBrowser={enableRelationshipBrowser && hasLikec4model}
118
+ // TODO: temporary disable relationship details for deployment views
119
+ enableRelationshipDetails={enableRelationshipDetails && hasLikec4model && !isDeploymentView(view)}
120
120
  // Apply where filter only in readonly mode
121
121
  whereFilter={readonly ? (where ?? null) : null}
122
122
  renderIcon={renderIcon ?? null}
@@ -129,9 +129,7 @@ export function LikeC4Diagram({
129
129
  onChange={onChange ?? null}
130
130
  onNavigateTo={onNavigateTo ?? null}
131
131
  onCanvasDblClick={onCanvasDblClick ?? null}
132
- onOpenSourceView={onOpenSourceView ?? null}
133
- onOpenSourceElement={onOpenSourceElement ?? null}
134
- onOpenSourceRelation={onOpenSourceRelation ?? null}
132
+ onOpenSource={onOpenSource ?? null}
135
133
  onBurgerMenuClick={onBurgerMenuClick ?? null}
136
134
  >
137
135
  <LikeC4DiagramInnerMemo
@@ -1,35 +1,38 @@
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'
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
4
  import { memo, useMemo, useState } from 'react'
5
5
  import { filter, map, pipe } from 'remeda'
6
6
  import { useDiagramState, useDiagramStoreApi, useMantinePortalProps } from './hooks'
7
7
  import { useLikeC4Model } from './likec4model'
8
- import { Center, Group, Highlight, Text } from '@mantine/core'
9
8
 
10
9
  interface QueryMatch {
11
- data: SpotlightActionData,
12
- keyword: string,
10
+ data: SpotlightActionData
11
+ keyword: string
13
12
  match: string
14
13
  }
15
14
 
16
- function filterAction(actionData: SpotlightActionData, query: string) : QueryMatch | undefined {
17
- const queryLower = query.toLowerCase();
18
- const keywords = Array.isArray(actionData.keywords) ? actionData.keywords : [];
15
+ function filterAction(actionData: SpotlightActionData, query: string): QueryMatch | undefined {
16
+ const queryLower = query.toLowerCase()
17
+ const keywords = Array.isArray(actionData.keywords) ? actionData.keywords : []
19
18
  const keywordMatch = keywords
20
19
  .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;
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
26
27
  })
27
- .find(match => !!match);
28
- return !!keywordMatch ? {
29
- data: actionData,
30
- keyword: keywordMatch.keyword,
31
- match: keywordMatch.match
32
- } : undefined;
28
+ .find(match => !!match)
29
+ return !!keywordMatch
30
+ ? {
31
+ data: actionData,
32
+ keyword: keywordMatch.keyword,
33
+ match: keywordMatch.match
34
+ }
35
+ : undefined
33
36
  }
34
37
 
35
38
  function buildFilteredActions(actionsData: SpotlightActionData[], query: string): JSX.Element[] {
@@ -38,7 +41,7 @@ function buildFilteredActions(actionsData: SpotlightActionData[], query: string)
38
41
  map(a => filterAction(a, query)),
39
42
  filter(qm => !!qm),
40
43
  map(qm => buildSpotlightAction(qm))
41
- );
44
+ )
42
45
  }
43
46
 
44
47
  function highlightMatch(match: QueryMatch): JSX.Element {
@@ -46,27 +49,29 @@ function highlightMatch(match: QueryMatch): JSX.Element {
46
49
  }
47
50
 
48
51
  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>
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
+ )
63
68
  }
64
69
 
65
70
  export const LikeC4Search = memo(() => {
66
71
  const view = useDiagramState(s => s.view)
67
72
  const model = useLikeC4Model(true)
68
73
  const store = useDiagramStoreApi()
69
- const [ query, setQuery ] = useState('')
74
+ const [query, setQuery] = useState('')
70
75
 
71
76
  const getNodeActionsData = (): SpotlightActionData[] => {
72
77
  const { focusOnNode } = store.getState()
@@ -85,12 +90,12 @@ export const LikeC4Search = memo(() => {
85
90
  onClick: () => focusOnNode(n.id),
86
91
  leftSection: <IconRectangularPrism />
87
92
  }))
88
- );
93
+ )
89
94
  }
90
95
 
91
96
  const getViewActionsData = (): SpotlightActionData[] => {
92
97
  const { onNavigateTo } = store.getState()
93
- const views = model.views()
98
+ const views = model.views().toArray()
94
99
 
95
100
  return map(views, v => ({
96
101
  id: v.id,
@@ -98,7 +103,7 @@ export const LikeC4Search = memo(() => {
98
103
  keywords: [
99
104
  v.title ?? v.id,
100
105
  ...(v.tags ?? []),
101
- ...(v.view.description ? [v.view.description] : [])
106
+ ...(v.$view.description ? [v.$view.description] : [])
102
107
  ],
103
108
  onClick: () => {
104
109
  store.setState({
@@ -112,17 +117,15 @@ export const LikeC4Search = memo(() => {
112
117
  onNavigateTo?.(v.id)
113
118
  },
114
119
  leftSection: <IconSitemap />
115
- }));
120
+ }))
116
121
  }
117
122
 
118
123
  const { nodeActions, viewActions } = useMemo(() => ({
119
- nodeActions: buildFilteredActions(getNodeActionsData(), query),
120
- viewActions: buildFilteredActions(getViewActionsData(), query)
121
- }),
122
- [model, store, view, query]
123
- );
124
+ nodeActions: buildFilteredActions(getNodeActionsData(), query),
125
+ viewActions: buildFilteredActions(getViewActionsData(), query)
126
+ }), [model, store, view, query])
124
127
 
125
- const portalProps = useMantinePortalProps();
128
+ const portalProps = useMantinePortalProps()
126
129
  return (
127
130
  <Spotlight.Root
128
131
  {...portalProps}
@@ -137,10 +140,10 @@ export const LikeC4Search = memo(() => {
137
140
  leftSection={<IconSearch stroke={1.5} />}
138
141
  />
139
142
  <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
+ {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>}
143
146
  </Spotlight.ActionsList>
144
147
  </Spotlight.Root>
145
- );
146
- });
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
- function useDiagramState<StateSlice = unknown>(
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 }
@@ -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.ElementModel | LikeC4DiagramModel.Element | LikeC4ViewModel.Element
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?: ViewID): LikeC4ModelTreeNodeData[] {
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 ViewID } from '@likec4/core'
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: LikeC4Model['type'] | undefined): LikeC4Model
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: LikeC4Model['type'] | undefined): LikeC4Model | null
16
- export function useLikeC4Model(strict?: boolean, type?: LikeC4Model['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(): LikeC4Model.SourceModel['views'] {
30
- return useLikeC4Model(true).sourcemodel.views
30
+ export function useLikeC4Views(): Record<ViewId, ComputedView | DiagramView> {
31
+ return useLikeC4Model(true).$model.views
31
32
  }
32
33
 
33
- export function useLikeC4ViewModel(viewId: LiteralUnion<ViewID, string>): LikeC4Model.ViewModel {
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<ViewID, string>): ComputedView | DiagramView | null {
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).view
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<ViewID, string>): DiagramView | null {
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).view
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
+ }