likec4 0.50.0 → 0.52.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/dist/@likec4/core/utils/fqn.js +10 -4
- package/dist/@likec4/core/utils/relations.js +8 -1
- package/dist/@likec4/diagrams/diagram/Nodes.js +2 -2
- package/dist/@likec4/diagrams/diagram/shapes/Edge.js +8 -7
- package/dist/__app__/src/pages/view-page/index.js +1 -1
- package/dist/__app__/src/pages/view-page/other-formats/ViewAsD2.jsx +51 -12
- package/dist/__app__/src/pages/view-page/other-formats/ViewAsDot.jsx +3 -3
- package/dist/__app__/src/pages/view-page/other-formats/ViewAsMmd.jsx +43 -12
- package/dist/__app__/src/pages/view-page/other-formats.jsx +44 -22
- package/dist/__app__/src/pages/view-page/view-page.module.css +6 -1
- package/dist/cli/index.js +223 -226
- package/package.json +23 -21
|
@@ -67,10 +67,16 @@ export function ancestorsFqn(fqn) {
|
|
|
67
67
|
export function compareFqnHierarchically(a, b) {
|
|
68
68
|
const depthA = a.split(".").length;
|
|
69
69
|
const depthB = b.split(".").length;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
switch (true) {
|
|
71
|
+
case depthA > depthB: {
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
case depthA < depthB: {
|
|
75
|
+
return -1;
|
|
76
|
+
}
|
|
77
|
+
default: {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
export function compareByFqnHierarchically(a, b) {
|
|
@@ -10,7 +10,14 @@ export const compareRelations = (a, b) => {
|
|
|
10
10
|
return -1;
|
|
11
11
|
}
|
|
12
12
|
const compareParents = parentA && parentB ? compareFqnHierarchically(parentA, parentB) : 0;
|
|
13
|
-
|
|
13
|
+
if (compareParents === 0) {
|
|
14
|
+
const compareSource = compareFqnHierarchically(a.source, b.source);
|
|
15
|
+
if (compareSource !== 0) {
|
|
16
|
+
return compareSource;
|
|
17
|
+
}
|
|
18
|
+
return compareFqnHierarchically(a.target, b.target);
|
|
19
|
+
}
|
|
20
|
+
return compareParents;
|
|
14
21
|
};
|
|
15
22
|
const isInside = (parent) => {
|
|
16
23
|
const prefix = parent + ".";
|
|
@@ -3,8 +3,9 @@ import { nonexhaustive } from "@likec4/core";
|
|
|
3
3
|
import { isEqualSimple } from "@react-hookz/deep-equal/esnext";
|
|
4
4
|
import { useToggle } from "@react-hookz/web/esm";
|
|
5
5
|
import { useSpring, useTransition } from "@react-spring/konva";
|
|
6
|
-
import { mix, toHex
|
|
6
|
+
import { lighten, mix, toHex } from "khroma";
|
|
7
7
|
import { memo, useRef } from "react";
|
|
8
|
+
import { Group } from "react-konva";
|
|
8
9
|
import { AnimatedCircle, AnimatedGroup, Rect } from "../konva.js";
|
|
9
10
|
import { Portal } from "../konva-portal.js";
|
|
10
11
|
import { ZoomInIcon } from "./icons/index.js";
|
|
@@ -14,7 +15,6 @@ import { CompoundShape } from "./shapes/Compound.js";
|
|
|
14
15
|
import { mouseDefault, mousePointer } from "./shapes/utils.js";
|
|
15
16
|
import { isCompound, useNodeSpringsFn } from "./springs.js";
|
|
16
17
|
import { DiagramGesture, useHoveredEdge, useHoveredNodeId, useSetHoveredNode } from "./state/index.js";
|
|
17
|
-
import { Group } from "react-konva";
|
|
18
18
|
function nodeShape({ shape }) {
|
|
19
19
|
switch (shape) {
|
|
20
20
|
case "cylinder":
|
|
@@ -35,14 +35,15 @@ function EdgeLabelBg({
|
|
|
35
35
|
isHovered,
|
|
36
36
|
springs
|
|
37
37
|
}) {
|
|
38
|
-
const
|
|
38
|
+
const paddingX = 2;
|
|
39
|
+
const paddingY = 1;
|
|
39
40
|
const props = useSpring({
|
|
40
41
|
to: {
|
|
41
|
-
x: labelBBox.x -
|
|
42
|
-
y: labelBBox.y -
|
|
43
|
-
width: labelBBox.width +
|
|
44
|
-
height: labelBBox.height +
|
|
45
|
-
opacity: isHovered ? 0.
|
|
42
|
+
x: labelBBox.x - paddingX,
|
|
43
|
+
y: labelBBox.y - paddingY,
|
|
44
|
+
width: labelBBox.width + paddingX * 2,
|
|
45
|
+
height: labelBBox.height + paddingY * 2,
|
|
46
|
+
opacity: isHovered ? 0.8 : 0.55
|
|
46
47
|
},
|
|
47
48
|
immediate: !animate
|
|
48
49
|
});
|
|
@@ -52,7 +53,7 @@ function EdgeLabelBg({
|
|
|
52
53
|
...props,
|
|
53
54
|
perfectDrawEnabled: false,
|
|
54
55
|
fill: springs.labelBgColor,
|
|
55
|
-
cornerRadius:
|
|
56
|
+
cornerRadius: 3,
|
|
56
57
|
globalCompositeOperation: "lighten",
|
|
57
58
|
hitStrokeWidth: 20
|
|
58
59
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { lazy } from 'react';
|
|
3
3
|
export * from './ViewAsReact';
|
|
4
|
-
export const ViewAs = lazy(() => import('./other-formats'));
|
|
4
|
+
export const ViewAs = lazy(async () => await import('./other-formats'));
|
|
5
5
|
// export const ViewAs = {
|
|
6
6
|
// Dot: ViewAsDot,
|
|
7
7
|
// D2: ViewAsD2,
|
|
@@ -1,18 +1,57 @@
|
|
|
1
|
-
import { Box, Code, ScrollArea } from '@radix-ui/themes';
|
|
1
|
+
import { Box, Code, Flex, ScrollArea } from '@radix-ui/themes';
|
|
2
|
+
import useSWR from 'swr';
|
|
2
3
|
import { d2Source } from 'virtual:likec4/d2-sources';
|
|
3
4
|
import { CopyToClipboard } from '../../../components';
|
|
5
|
+
const fetchFromKroki = async (d2) => {
|
|
6
|
+
const res = await fetch('https://kroki.io/d2/svg', {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
cache: 'force-cache',
|
|
9
|
+
body: JSON.stringify({
|
|
10
|
+
diagram_source: d2,
|
|
11
|
+
// diagram_options: {
|
|
12
|
+
// theme: 'colorblind-clear'
|
|
13
|
+
// },
|
|
14
|
+
output_format: 'svg'
|
|
15
|
+
}),
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json'
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return await res.text();
|
|
21
|
+
};
|
|
4
22
|
export default function ViewAsD2({ viewId }) {
|
|
5
23
|
const src = d2Source(viewId);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
24
|
+
const { data: krokiSvg } = useSWR(src, fetchFromKroki, {
|
|
25
|
+
keepPreviousData: true,
|
|
26
|
+
revalidateIfStale: false
|
|
27
|
+
});
|
|
28
|
+
return (<Flex gap='2' shrink='1' grow='1' align={'stretch'} wrap={'nowrap'} style={{
|
|
29
|
+
overflow: 'hidden'
|
|
10
30
|
}}>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
31
|
+
<Box py={'2'} position={'relative'} style={{
|
|
32
|
+
overflow: 'scroll'
|
|
33
|
+
}}>
|
|
34
|
+
<ScrollArea scrollbars='both'>
|
|
35
|
+
<Box asChild display={'block'} p='2' style={{
|
|
36
|
+
whiteSpace: 'pre',
|
|
37
|
+
minHeight: '100%'
|
|
38
|
+
}}>
|
|
39
|
+
<Code variant='soft' autoFocus>
|
|
40
|
+
{src}
|
|
41
|
+
</Code>
|
|
42
|
+
</Box>
|
|
43
|
+
</ScrollArea>
|
|
44
|
+
<CopyToClipboard text={src}/>
|
|
45
|
+
</Box>
|
|
46
|
+
{krokiSvg && (<Box py={'2'} grow={'1'} shrink={'0'} style={{
|
|
47
|
+
width: '50%',
|
|
48
|
+
overflow: 'scroll'
|
|
49
|
+
}}>
|
|
50
|
+
<ScrollArea scrollbars='both'>
|
|
51
|
+
<Box grow={'1'} asChild className={'svg-container'}>
|
|
52
|
+
<div dangerouslySetInnerHTML={{ __html: krokiSvg }}></div>
|
|
53
|
+
</Box>
|
|
54
|
+
</ScrollArea>
|
|
55
|
+
</Box>)}
|
|
56
|
+
</Flex>);
|
|
18
57
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Box, Code, Grid, ScrollArea } from '@radix-ui/themes';
|
|
2
2
|
import { dotSource, svgSource } from 'virtual:likec4/dot-sources';
|
|
3
|
-
import styles from '../view-page.module.css';
|
|
4
3
|
import { CopyToClipboard } from '../../../components';
|
|
5
4
|
export default function ViewAsDot({ viewId }) {
|
|
6
5
|
const dot = dotSource(viewId);
|
|
@@ -12,7 +11,8 @@ export default function ViewAsDot({ viewId }) {
|
|
|
12
11
|
}}>
|
|
13
12
|
<ScrollArea scrollbars='both'>
|
|
14
13
|
<Box asChild display={'block'} p='2' style={{
|
|
15
|
-
whiteSpace: 'pre'
|
|
14
|
+
whiteSpace: 'pre',
|
|
15
|
+
minHeight: '100%'
|
|
16
16
|
}}>
|
|
17
17
|
<Code variant='soft' autoFocus>
|
|
18
18
|
{dot}
|
|
@@ -25,7 +25,7 @@ export default function ViewAsDot({ viewId }) {
|
|
|
25
25
|
overflow: 'scroll',
|
|
26
26
|
overscrollBehavior: 'none'
|
|
27
27
|
}}>
|
|
28
|
-
<Box asChild position={'relative'} className={
|
|
28
|
+
<Box asChild position={'relative'} className={'svg-container'}>
|
|
29
29
|
<div dangerouslySetInnerHTML={{ __html: svgSource(viewId) }}></div>
|
|
30
30
|
</Box>
|
|
31
31
|
</Box>
|
|
@@ -1,18 +1,49 @@
|
|
|
1
|
-
import { Box, Code, ScrollArea } from '@radix-ui/themes';
|
|
1
|
+
import { Box, Code, Flex, ScrollArea } from '@radix-ui/themes';
|
|
2
|
+
import { useAsync } from '@react-hookz/web/esm';
|
|
3
|
+
import { useEffect } from 'react';
|
|
2
4
|
import { mmdSource } from 'virtual:likec4/mmd-sources';
|
|
3
5
|
import { CopyToClipboard } from '../../../components';
|
|
6
|
+
const renderSvg = async (viewId, diagram) => {
|
|
7
|
+
const { default: mermaid } = await import('https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs');
|
|
8
|
+
mermaid.initialize({
|
|
9
|
+
theme: 'dark'
|
|
10
|
+
});
|
|
11
|
+
const { svg } = await mermaid.render(viewId, diagram);
|
|
12
|
+
return svg;
|
|
13
|
+
};
|
|
4
14
|
export default function ViewAsMmd({ viewId }) {
|
|
5
15
|
const src = mmdSource(viewId);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
16
|
+
const [mmdSvg, { execute }] = useAsync(renderSvg, null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
void execute(viewId, src);
|
|
19
|
+
}, [src]);
|
|
20
|
+
return (<Flex gap='2' shrink='1' grow='1' align={'stretch'} wrap={'nowrap'} style={{
|
|
21
|
+
overflow: 'hidden'
|
|
10
22
|
}}>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
<Box py={'2'} position={'relative'} style={{
|
|
24
|
+
overflow: 'scroll'
|
|
25
|
+
}}>
|
|
26
|
+
<ScrollArea scrollbars='both'>
|
|
27
|
+
<Box asChild display={'block'} p='2' style={{
|
|
28
|
+
whiteSpace: 'pre',
|
|
29
|
+
minHeight: '100%'
|
|
30
|
+
}}>
|
|
31
|
+
<Code variant='soft' autoFocus>
|
|
32
|
+
{src}
|
|
33
|
+
</Code>
|
|
34
|
+
</Box>
|
|
35
|
+
<CopyToClipboard text={src}/>
|
|
36
|
+
</ScrollArea>
|
|
37
|
+
</Box>
|
|
38
|
+
<Box py={'2'} position={'relative'} grow={'1'} shrink={'0'} style={{
|
|
39
|
+
minWidth: '50vw',
|
|
40
|
+
overflow: 'scroll'
|
|
41
|
+
}}>
|
|
42
|
+
<ScrollArea scrollbars='both'>
|
|
43
|
+
{mmdSvg.result && (<Box grow={'1'} asChild position={'relative'} className={'svg-container'}>
|
|
44
|
+
<div dangerouslySetInnerHTML={{ __html: mmdSvg.result }}></div>
|
|
45
|
+
</Box>)}
|
|
46
|
+
</ScrollArea>
|
|
47
|
+
</Box>
|
|
48
|
+
</Flex>);
|
|
18
49
|
}
|
|
@@ -4,32 +4,54 @@ import ViewAsD2 from './other-formats/ViewAsD2';
|
|
|
4
4
|
import ViewAsDot from './other-formats/ViewAsDot';
|
|
5
5
|
import ViewAsMmd from './other-formats/ViewAsMmd';
|
|
6
6
|
import styles from './view-page.module.css';
|
|
7
|
+
import { SWRConfig } from 'swr';
|
|
8
|
+
function localStorageProvider() {
|
|
9
|
+
// When initializing, we restore the data from `localStorage` into a map.
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
const map = new Map(JSON.parse(localStorage.getItem('swr-cache') || '[]'));
|
|
12
|
+
// Before unloading the app, we write back all the data into `localStorage`.
|
|
13
|
+
window.addEventListener('beforeunload', () => {
|
|
14
|
+
const appCache = JSON.stringify(Array.from(map.entries()));
|
|
15
|
+
localStorage.setItem('swr-cache', appCache);
|
|
16
|
+
});
|
|
17
|
+
// We still use the map for write & read for performance.
|
|
18
|
+
return map;
|
|
19
|
+
}
|
|
7
20
|
export default function ViewDiagramInOtherFormats({ viewId, viewMode }) {
|
|
8
|
-
return (<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
return (<SWRConfig value={{
|
|
22
|
+
keepPreviousData: true,
|
|
23
|
+
errorRetryCount: 5,
|
|
24
|
+
provider: localStorageProvider
|
|
25
|
+
}}>
|
|
26
|
+
<Flex asChild position={'fixed'} inset={'0'} pt={'8'} pl={'8'} pr={'2'} align={'stretch'} direction={'column'}>
|
|
27
|
+
<Tabs.Root value={viewMode} onValueChange={mode => mode !== viewMode && updateSearchParams({ mode: mode })}>
|
|
28
|
+
<Box asChild shrink={'0'} grow={'0'}>
|
|
29
|
+
<Tabs.List>
|
|
30
|
+
<Tabs.Trigger value='react'>React</Tabs.Trigger>
|
|
31
|
+
<Tabs.Trigger value='dot'>Graphviz</Tabs.Trigger>
|
|
32
|
+
<Tabs.Trigger value='mmd'>Mermaid</Tabs.Trigger>
|
|
33
|
+
<Tabs.Trigger value='d2'>D2</Tabs.Trigger>
|
|
34
|
+
</Tabs.List>
|
|
35
|
+
</Box>
|
|
36
|
+
|
|
37
|
+
<Box p='2' className={styles.otherFormats} position={'relative'}>
|
|
38
|
+
<Tabs.Content value='react'>{''}</Tabs.Content>
|
|
17
39
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</Tabs.Content>
|
|
40
|
+
<Tabs.Content value='dot'>
|
|
41
|
+
<ViewAsDot viewId={viewId}/>
|
|
42
|
+
</Tabs.Content>
|
|
22
43
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
44
|
+
<Tabs.Content value='mmd'>
|
|
45
|
+
<ViewAsMmd viewId={viewId}/>
|
|
46
|
+
</Tabs.Content>
|
|
26
47
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
<Tabs.Content value='d2'>
|
|
49
|
+
<ViewAsD2 viewId={viewId}/>
|
|
50
|
+
</Tabs.Content>
|
|
51
|
+
</Box>
|
|
52
|
+
</Tabs.Root>
|
|
53
|
+
</Flex>
|
|
54
|
+
</SWRConfig>);
|
|
33
55
|
// switch (viewMode) {
|
|
34
56
|
// case 'dot':
|
|
35
57
|
// return <ViewAsDot viewId={viewId} />
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
}
|
|
57
57
|
} */
|
|
58
58
|
|
|
59
|
-
.
|
|
59
|
+
:global(.svg-container) {
|
|
60
|
+
min-width: 300px;
|
|
60
61
|
& > svg {
|
|
61
62
|
width: 100%;
|
|
62
63
|
height: auto;
|
|
@@ -77,5 +78,9 @@
|
|
|
77
78
|
&[hidden] {
|
|
78
79
|
display: none;
|
|
79
80
|
}
|
|
81
|
+
|
|
82
|
+
& :global(.rt-ScrollAreaViewport) > div {
|
|
83
|
+
height: inherit;
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
86
|
}
|