likec4 1.8.2-next.0 → 1.9.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/README.md CHANGED
@@ -12,7 +12,7 @@ Features:
12
12
  ## Install
13
13
 
14
14
  > **Compatibility Note:**\
15
- > LikeC4 requires [Node.js](https://nodejs.org/en/) version 18+, 20+
15
+ > LikeC4 requires [Node.js](https://nodejs.org/en/) version 20+
16
16
 
17
17
  ### Local installation
18
18
 
@@ -154,6 +154,72 @@ likec4 codegen ts ...
154
154
  > Output file should have `.ts` extension\
155
155
  > By default, it generates `likec4.generated.ts` in current directory
156
156
 
157
+ ## API Usage
158
+
159
+ You can access and traverse your architecture model programmatically using the LikeC4 Model API.
160
+
161
+ ### From workspace
162
+
163
+ Recursively searches and parses source files from the wokrkspace directory:
164
+
165
+ ```ts
166
+ import { LikeC4 } from 'likec4'
167
+
168
+ const likec4 = await LikeC4.fromWorkspace('path to workspace', opts)
169
+ ```
170
+
171
+ ### From source
172
+
173
+ Parses the model from the string:
174
+
175
+ ```ts
176
+ import { LikeC4 } from "likec4"
177
+
178
+ const likec4 = await LikeC4.fromSource(`
179
+ specification {
180
+ element system
181
+ element user
182
+ }
183
+ model {
184
+ customer = user 'Customer'
185
+ cloud = system 'System'
186
+ }
187
+ views {
188
+ view index {
189
+ include *
190
+ }
191
+ }
192
+ `, opts)
193
+ ```
194
+
195
+ ### Example
196
+
197
+ When the model is initialized, you can use the following methods to query and traverse it.
198
+
199
+ ```ts
200
+ import { LikeC4 } from "likec4"
201
+
202
+ const likec4 = await LikeC4.fromSource(`....`)
203
+
204
+ // Validation errors
205
+ console.log(likec4.getErrors())
206
+
207
+ // Traverse the model
208
+ const model = likec4.model()
209
+ model
210
+ .element('cloud.backend.api')
211
+ .incoming() // relationships incoming to the element
212
+ .filter(r => r.tags.includes('http')) // filter by tags
213
+ .map(r => r.source) // get source elements
214
+
215
+ // Layouted views
216
+ const diagrams = await likec4.diagrams()
217
+
218
+
219
+ ```
220
+
221
+ Check Typescript definitions for available methods.
222
+
157
223
  ## Development
158
224
 
159
225
  In root workspace:
@@ -23,7 +23,7 @@ const RenderIcon = ({ node }: IconRendererProps) => {
23
23
  return IconComponent ? <IconComponent /> : null
24
24
  }
25
25
 
26
- export { isLikeC4ViewId }
26
+ export { isLikeC4ViewId, LikeC4Views }
27
27
 
28
28
  export type LikeC4ViewProps = LikeC4ViewBaseProps<LikeC4ViewId, LikeC4Tag, LikeC4ElementKind>
29
29
 
@@ -0,0 +1,317 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { useOverviewGraph } from "virtual:likec4/overview-graph";
3
+ import { u, c as createReactComponent, I as IconFolderFilled, e, a as useUpdateEffect, n as nonexhaustive } from "./main-BZtdykGF.js";
4
+ import { u as useRouter } from "./tanstack-router-Bc_WYOzY.js";
5
+ import { B as BaseEdge, H as Handle, P as Position, u as useNodesState, a as useEdgesState, i as index, b as Background, c as BackgroundVariant } from "./likec4-BqIZe8Y0.js";
6
+ import { memo, useRef, useMemo } from "react";
7
+ import { P as Paper, c as clsx, G as Group, T as ThemeIcon, a as Text, C as Card, b as CardSection, d as Center, I as Image, B as Box, u as useMantineColorScheme } from "./mantine-Bhi3pgTf.js";
8
+ import { usePreviewUrl } from "virtual:likec4/previews";
9
+ function a(...n) {
10
+ return u(c, n);
11
+ }
12
+ function c(n, r) {
13
+ let o = {};
14
+ for (let e2 of r) e2 in n && (o[e2] = n[e2]);
15
+ return o;
16
+ }
17
+ /**
18
+ * @license @tabler/icons-react v3.14.0 - MIT
19
+ *
20
+ * This source code is licensed under the MIT license.
21
+ * See the LICENSE file in the root directory of this source tree.
22
+ */
23
+ var IconLoader = createReactComponent("outline", "loader", "IconLoader", [["path", { d: "M12 6l0 -3", key: "svg-0" }], ["path", { d: "M16.25 7.75l2.15 -2.15", key: "svg-1" }], ["path", { d: "M18 12l3 0", key: "svg-2" }], ["path", { d: "M16.25 16.25l2.15 2.15", key: "svg-3" }], ["path", { d: "M12 18l0 3", key: "svg-4" }], ["path", { d: "M7.75 16.25l-2.15 2.15", key: "svg-5" }], ["path", { d: "M6 12l-3 0", key: "svg-6" }], ["path", { d: "M7.75 7.75l-2.15 -2.15", key: "svg-7" }]]);
24
+ /**
25
+ * @license @tabler/icons-react v3.14.0 - MIT
26
+ *
27
+ * This source code is licensed under the MIT license.
28
+ * See the LICENSE file in the root directory of this source tree.
29
+ */
30
+ var IconFileFilled = createReactComponent("filled", "file-filled", "IconFileFilled", [["path", { d: "M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005h5z", key: "svg-0" }], ["path", { d: "M19 7h-4l-.001 -4.001z", key: "svg-1" }]]), root = "mxt2a80";
31
+ function edgePath(points) {
32
+ return points.reduce((acc, [x, y], i) => acc + `${i === 0 ? "M" : " L"} ${x},${y}`, "");
33
+ }
34
+ function LinkEdge({
35
+ id,
36
+ data,
37
+ ...props
38
+ }) {
39
+ if (!data)
40
+ return null;
41
+ const path = edgePath(data.points);
42
+ return /* @__PURE__ */ jsx(
43
+ BaseEdge,
44
+ {
45
+ id,
46
+ path,
47
+ ...props
48
+ }
49
+ );
50
+ }
51
+ var handleCenter = "_1n8mzjc0", toplevelNode = "_1n8mzjc1", nestedNode = "_1n8mzjc2", dimmed = "_1n8mzjc3", folderNode = "_1n8mzjc4", fileNode = "_1n8mzjc5", viewNode = "_1n8mzjc6", viewNodeImageSection = "_1n8mzjc7", viewTitle = "_1n8mzjc8";
52
+ const FolderNode = /* @__PURE__ */ memo(function({
53
+ data,
54
+ parentId,
55
+ id
56
+ }) {
57
+ const isTopLevel = e(parentId);
58
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
59
+ /* @__PURE__ */ jsx(Handle, { type: "target", position: Position.Top, className: handleCenter }),
60
+ /* @__PURE__ */ jsx(
61
+ Paper,
62
+ {
63
+ p: "sm",
64
+ pt: "xs",
65
+ radius: "md",
66
+ withBorder: !0,
67
+ shadow: isTopLevel ? "lg" : "xs",
68
+ className: clsx(
69
+ folderNode,
70
+ isTopLevel ? toplevelNode : nestedNode,
71
+ data.dimmed && dimmed
72
+ ),
73
+ children: /* @__PURE__ */ jsxs(Group, { gap: 8, children: [
74
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 24, variant: "transparent", color: "dark.4", children: /* @__PURE__ */ jsx(IconFolderFilled, { size: "100%" }) }),
75
+ /* @__PURE__ */ jsx(Text, { size: "lg", fw: 500, children: data.label })
76
+ ] })
77
+ }
78
+ ),
79
+ /* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Bottom, className: handleCenter })
80
+ ] });
81
+ }), FileNode = /* @__PURE__ */ memo(function({
82
+ data,
83
+ parentId,
84
+ id
85
+ }) {
86
+ const isTopLevel = e(parentId);
87
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
88
+ /* @__PURE__ */ jsx(Handle, { type: "target", position: Position.Top, className: handleCenter }),
89
+ /* @__PURE__ */ jsx(
90
+ Paper,
91
+ {
92
+ p: "sm",
93
+ pt: "xs",
94
+ radius: "md",
95
+ withBorder: !0,
96
+ shadow: isTopLevel ? "lg" : "xs",
97
+ className: clsx(
98
+ fileNode,
99
+ isTopLevel ? toplevelNode : nestedNode,
100
+ data.dimmed && dimmed
101
+ ),
102
+ children: /* @__PURE__ */ jsxs(Group, { gap: 8, children: [
103
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 24, variant: "transparent", color: "dark.3", children: /* @__PURE__ */ jsx(IconFileFilled, { size: "100%" }) }),
104
+ /* @__PURE__ */ jsx(Text, { size: "lg", fw: 500, children: data.label })
105
+ ] })
106
+ }
107
+ ),
108
+ /* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Bottom, className: handleCenter })
109
+ ] });
110
+ }), ViewNode = /* @__PURE__ */ memo(function({
111
+ data,
112
+ height = 320
113
+ }) {
114
+ const imageUrl = usePreviewUrl(data.viewId);
115
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
116
+ /* @__PURE__ */ jsx(Handle, { type: "target", position: Position.Top, className: handleCenter }),
117
+ /* @__PURE__ */ jsxs(
118
+ Card,
119
+ {
120
+ className: clsx(
121
+ viewNode,
122
+ data.dimmed && dimmed
123
+ ),
124
+ withBorder: !0,
125
+ shadow: "xs",
126
+ padding: 0,
127
+ children: [
128
+ /* @__PURE__ */ jsx(CardSection, { className: viewNodeImageSection, children: imageUrl ? /* @__PURE__ */ jsx(
129
+ Image,
130
+ {
131
+ src: imageUrl,
132
+ fit: "contain",
133
+ h: height - 60
134
+ }
135
+ ) : /* @__PURE__ */ jsx(Center, { h: height - 60, children: /* @__PURE__ */ jsxs(Group, { children: [
136
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 60, variant: "transparent", color: "dark", children: /* @__PURE__ */ jsx(IconLoader, { stroke: 1.5, size: "100%" }) }),
137
+ /* @__PURE__ */ jsx(Text, { size: "xl", fw: 500, c: "dimmed", children: "Preview not available" })
138
+ ] }) }) }),
139
+ /* @__PURE__ */ jsx(Box, { className: viewTitle, h: 60, p: "sm", pl: "md", children: /* @__PURE__ */ jsx(Text, { component: "div", size: "lg", fw: 500, children: data.label }) })
140
+ ]
141
+ }
142
+ ),
143
+ /* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Bottom, className: handleCenter })
144
+ ] });
145
+ }), nodeTypes = {
146
+ folder: FolderNode,
147
+ file: FileNode,
148
+ view: ViewNode
149
+ }, edgeTypes = {
150
+ link: LinkEdge
151
+ }, overviewGraphToXYFlowData = (graph) => ({
152
+ nodes: graph.nodes.map(({ id, parentId, position, width, height, ...node }) => {
153
+ const parent = parentId ? graph.nodes.find((n) => n.id === parentId) : null, rect = {
154
+ ...position,
155
+ width,
156
+ height
157
+ };
158
+ parent && (position = {
159
+ x: position.x - parent.position.x,
160
+ y: position.y - parent.position.y
161
+ });
162
+ const xyparent = parent ? { parentId: parent.id } : {};
163
+ switch (node.type) {
164
+ case "file":
165
+ case "folder":
166
+ return {
167
+ id,
168
+ type: node.type,
169
+ data: {
170
+ dimmed: !1,
171
+ label: node.label,
172
+ path: node.path,
173
+ rect
174
+ },
175
+ deletable: !1,
176
+ position,
177
+ width,
178
+ height,
179
+ zIndex: 1,
180
+ ...xyparent
181
+ };
182
+ case "view":
183
+ return {
184
+ id,
185
+ type: "view",
186
+ data: {
187
+ dimmed: !1,
188
+ label: node.label,
189
+ viewId: node.viewId,
190
+ rect
191
+ },
192
+ selectable: !1,
193
+ deletable: !1,
194
+ position,
195
+ width,
196
+ height,
197
+ zIndex: 3,
198
+ ...xyparent
199
+ };
200
+ default:
201
+ nonexhaustive(node);
202
+ }
203
+ }),
204
+ edges: graph.edges.map((edge) => ({
205
+ id: edge.id,
206
+ source: edge.source,
207
+ target: edge.target,
208
+ type: "link",
209
+ zIndex: 2,
210
+ hidden: !0,
211
+ data: {
212
+ points: edge.points
213
+ }
214
+ }))
215
+ });
216
+ function OverviewDiagrams({
217
+ graph,
218
+ fitViewPadding = 0.1,
219
+ zoomable = !0,
220
+ pannable = !0
221
+ }) {
222
+ const router = useRouter(), xyflowRef = useRef(), { colorScheme } = useMantineColorScheme(), xyflowdata = useMemo(() => overviewGraphToXYFlowData(graph), [graph]), [nodes, setNodes, onNodesChange] = useNodesState(xyflowdata.nodes), [edges, setEdges, onEdgeChanges] = useEdgesState(xyflowdata.edges);
223
+ useUpdateEffect(() => {
224
+ setNodes(
225
+ (nodes2) => xyflowdata.nodes.map((n) => {
226
+ const current = nodes2.find((node) => node.id === n.id);
227
+ return current ? { ...a(current, ["selected", "hidden"]), ...n } : n;
228
+ })
229
+ ), setEdges(xyflowdata.edges);
230
+ }, [xyflowdata.nodes, xyflowdata.edges]);
231
+ const focusedNode = nodes.find((node) => node.selected);
232
+ return useUpdateEffect(() => {
233
+ const xyflow = xyflowRef.current;
234
+ xyflow && (focusedNode ? xyflow.fitView({
235
+ maxZoom: 1,
236
+ padding: fitViewPadding,
237
+ nodes: [focusedNode],
238
+ duration: 450
239
+ }) : xyflow.fitView({
240
+ maxZoom: 1,
241
+ padding: fitViewPadding,
242
+ duration: 800
243
+ }));
244
+ }, [focusedNode?.id ?? null]), /* @__PURE__ */ jsx(
245
+ index,
246
+ {
247
+ colorMode: colorScheme === "auto" ? "system" : colorScheme,
248
+ className: root,
249
+ nodeTypes,
250
+ edgeTypes,
251
+ nodes,
252
+ onNodesChange,
253
+ edges,
254
+ onEdgesChange: onEdgeChanges,
255
+ fitView: !0,
256
+ fitViewOptions: useMemo(() => ({
257
+ minZoom: 0.05,
258
+ maxZoom: 1,
259
+ padding: fitViewPadding,
260
+ includeHiddenNodes: !0
261
+ }), [fitViewPadding]),
262
+ nodesDraggable: !1,
263
+ nodesConnectable: !1,
264
+ nodesFocusable: !0,
265
+ edgesReconnectable: !1,
266
+ edgesFocusable: !1,
267
+ multiSelectionKeyCode: null,
268
+ zoomOnPinch: zoomable,
269
+ zoomOnScroll: !pannable && zoomable,
270
+ zoomOnDoubleClick: !1,
271
+ ...!zoomable && {
272
+ zoomActivationKeyCode: null
273
+ },
274
+ maxZoom: zoomable ? 2 : 1,
275
+ minZoom: zoomable ? 0.01 : 1,
276
+ preventScrolling: zoomable || pannable,
277
+ noDragClassName: "nodrag",
278
+ noPanClassName: "nopan",
279
+ panOnScroll: pannable,
280
+ panOnDrag: pannable,
281
+ ...!pannable && {
282
+ selectionKeyCode: null
283
+ },
284
+ onInit: (instance) => xyflowRef.current = instance,
285
+ onNodeClick: (event, node) => {
286
+ if (node.type === "view") {
287
+ event.stopPropagation(), setNodes((nodes2) => nodes2.map(({ data, ...n }) => ({ ...n, data: { ...data, dimmed: n.id !== node.id } }))), xyflowRef.current?.fitView({
288
+ maxZoom: 10,
289
+ padding: 0,
290
+ nodes: [node],
291
+ duration: 1200
292
+ }), setTimeout(() => {
293
+ xyflowRef.current?.updateNodeData(node.id, { dimmed: !0 });
294
+ }, 400), setTimeout(() => {
295
+ router.navigate({
296
+ to: "/view/$viewId/",
297
+ params: {
298
+ viewId: node.data.viewId
299
+ },
300
+ search: !0
301
+ });
302
+ }, 800);
303
+ return;
304
+ }
305
+ node.selected && (event.stopPropagation(), setNodes((nodes2) => nodes2.map((n) => n.id === node.id ? { ...n, selected: !1 } : n)));
306
+ },
307
+ children: /* @__PURE__ */ jsx(Background, { variant: BackgroundVariant.Dots, size: 4, gap: 50 })
308
+ }
309
+ );
310
+ }
311
+ function OverviewPage() {
312
+ const graph = useOverviewGraph();
313
+ return /* @__PURE__ */ jsx(Box, { pos: "fixed", inset: 0, children: /* @__PURE__ */ jsx(OverviewDiagrams, { graph }) });
314
+ }
315
+ export {
316
+ OverviewPage as default
317
+ };
@@ -5947,31 +5947,31 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
5947
5947
  }
5948
5948
  memo(ResizeControl);
5949
5949
  export {
5950
- Background as B,
5950
+ BaseEdge as B,
5951
5951
  Controls as C,
5952
5952
  EdgeLabelRenderer as E,
5953
5953
  Handle as H,
5954
5954
  Position as P,
5955
5955
  ReactFlowProvider as R,
5956
- useStoreApi as a,
5957
- useNodesData as b,
5958
- useStore as c,
5959
- createWithEqualityFn as d,
5960
- applyNodeChanges as e,
5961
- applyEdgeChanges as f,
5962
- getViewportForBounds as g,
5963
- getNodeDimensions as h,
5964
- getBoundsOfRects as i,
5965
- boxToRect as j,
5966
- useStoreWithEqualityFn as k,
5967
- useOnViewportChange as l,
5968
- index as m,
5969
- useOnSelectionChange as n,
5970
- BackgroundVariant as o,
5971
- BaseEdge as p,
5972
- useNodesState as q,
5973
- useEdgesState as r,
5956
+ useEdgesState as a,
5957
+ Background as b,
5958
+ BackgroundVariant as c,
5959
+ useReactFlow as d,
5960
+ useStoreApi as e,
5961
+ useNodesData as f,
5962
+ useStore as g,
5963
+ createWithEqualityFn as h,
5964
+ index as i,
5965
+ applyNodeChanges as j,
5966
+ applyEdgeChanges as k,
5967
+ getViewportForBounds as l,
5968
+ getNodeDimensions as m,
5969
+ getBoundsOfRects as n,
5970
+ boxToRect as o,
5971
+ useStoreWithEqualityFn as p,
5972
+ useOnViewportChange as q,
5973
+ useOnSelectionChange as r,
5974
5974
  shallow$1 as s,
5975
- useReactFlow as u,
5975
+ useNodesState as u,
5976
5976
  withSelectorExports as w
5977
5977
  };