likec4 0.44.2 → 0.46.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/relations.js +11 -2
- package/dist/@likec4/diagrams/components/primitives/fullscreen/FullscreenDiagram.js +8 -7
- package/dist/@likec4/diagrams/diagram/Diagram.js +63 -45
- package/dist/@likec4/diagrams/diagram/Edges.js +32 -16
- package/dist/@likec4/diagrams/diagram/Nodes.js +66 -70
- package/dist/@likec4/diagrams/diagram/icons/ZoomIn.js +2 -3
- package/dist/@likec4/diagrams/diagram/shapes/Browser.js +43 -9
- package/dist/@likec4/diagrams/diagram/shapes/Compound.js +8 -10
- package/dist/@likec4/diagrams/diagram/shapes/Cylinder.js +3 -1
- package/dist/@likec4/diagrams/diagram/shapes/Edge.js +10 -10
- package/dist/@likec4/diagrams/diagram/shapes/Mobile.js +19 -4
- package/dist/@likec4/diagrams/diagram/shapes/NodeIcon.js +47 -9
- package/dist/@likec4/diagrams/diagram/shapes/NodeLabel.js +31 -54
- package/dist/@likec4/diagrams/diagram/shapes/Person.js +3 -1
- package/dist/@likec4/diagrams/diagram/shapes/Queue.js +3 -1
- package/dist/@likec4/diagrams/diagram/shapes/Rectangle.js +3 -1
- package/dist/@likec4/diagrams/diagram/shapes/index.js +1 -1
- package/dist/@likec4/diagrams/diagram/shapes/utils.js +1 -1
- package/dist/@likec4/diagrams/diagram/state/atoms.js +6 -0
- package/dist/@likec4/diagrams/diagram/state/hooks.js +10 -1
- package/dist/@likec4/diagrams/hooks/useDiagramApi.js +19 -22
- package/dist/@likec4/diagrams/hooks/useImageLoader.js +7 -1
- package/dist/__app__/index.html +1 -1
- package/dist/__app__/likec4.css +23 -0
- package/dist/__app__/src/App.jsx +27 -5
- package/dist/__app__/src/components/DiagramNotFound.jsx +10 -4
- package/dist/__app__/src/components/sidebar/Sidebar.jsx +1 -1
- package/dist/__app__/src/components/view-page/DisplayModeSelector.jsx +1 -1
- package/dist/__app__/src/components/view-page/ExportDiagram.jsx +13 -7
- package/dist/__app__/src/components/view-page/ViewActionsToolbar.jsx +12 -3
- package/dist/__app__/src/data/atoms.js +0 -11
- package/dist/__app__/src/pages/embed.page.jsx +14 -0
- package/dist/__app__/src/pages/export.page.jsx +4 -13
- package/dist/__app__/src/pages/index.js +1 -0
- package/dist/__app__/src/pages/useTransparentBackground.js +16 -0
- package/dist/__app__/src/pages/view-page.module.css +30 -0
- package/dist/__app__/src/pages/view.page.jsx +44 -8
- package/dist/__app__/src/router.js +14 -12
- package/dist/__app__/tailwind.config.cjs +1 -3
- package/dist/__app__/tsconfig.json +1 -7
- package/dist/cli/index.js +199 -206
- package/package.json +9 -10
- package/dist/@likec4/diagrams/hooks/useDarkMode.js +0 -5
- package/dist/@likec4/diagrams/index.mjs +0 -1927
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Diagram, useDiagramApi } from '@likec4/diagrams';
|
|
2
2
|
import { Box, Portal } from '@radix-ui/themes';
|
|
3
3
|
import { useDebouncedEffect } from '@react-hookz/web/esm';
|
|
4
|
-
import { memo } from 'react';
|
|
4
|
+
import { memo, useRef } from 'react';
|
|
5
5
|
function downloadBlob(blob, name) {
|
|
6
6
|
// Convert your blob into a Blob URL (a special url that points to an object in the browser's memory)
|
|
7
7
|
const blobUrl = URL.createObjectURL(blob);
|
|
@@ -32,13 +32,19 @@ function downloadBlob(blob, name) {
|
|
|
32
32
|
}
|
|
33
33
|
const ExportDiagram = memo(({ diagram, onCompleted }) => {
|
|
34
34
|
const id = diagram.id;
|
|
35
|
-
const [ref,
|
|
35
|
+
const [ref, api] = useDiagramApi();
|
|
36
36
|
const padding = 20;
|
|
37
37
|
const width = diagram.width + padding * 2;
|
|
38
38
|
const height = diagram.height + padding * 2;
|
|
39
|
+
const onCompletedRef = useRef(onCompleted);
|
|
40
|
+
onCompletedRef.current = onCompleted;
|
|
39
41
|
// To avoid flickering and double rendering
|
|
40
42
|
useDebouncedEffect(() => {
|
|
41
|
-
|
|
43
|
+
const stage = api.stage;
|
|
44
|
+
if (!stage) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
void stage
|
|
42
48
|
.toBlob({
|
|
43
49
|
pixelRatio: 2,
|
|
44
50
|
mimeType: 'image/png',
|
|
@@ -46,17 +52,17 @@ const ExportDiagram = memo(({ diagram, onCompleted }) => {
|
|
|
46
52
|
if (blob) {
|
|
47
53
|
downloadBlob(blob, `${diagram.id}.png`);
|
|
48
54
|
}
|
|
49
|
-
|
|
55
|
+
onCompletedRef.current();
|
|
50
56
|
}
|
|
51
57
|
})
|
|
52
58
|
.catch(err => {
|
|
53
|
-
|
|
59
|
+
onCompletedRef.current();
|
|
54
60
|
// Show error after 100ms to avoid blocking the UI
|
|
55
61
|
setTimeout(() => {
|
|
56
62
|
window.alert(err);
|
|
57
63
|
}, 100);
|
|
58
64
|
});
|
|
59
|
-
}, [id], 400);
|
|
65
|
+
}, [id, api], 400);
|
|
60
66
|
return (<Portal>
|
|
61
67
|
<Box position={'fixed'} style={{
|
|
62
68
|
top: 0,
|
|
@@ -65,7 +71,7 @@ const ExportDiagram = memo(({ diagram, onCompleted }) => {
|
|
|
65
71
|
height,
|
|
66
72
|
transform: `translateY(${-height}px)`
|
|
67
73
|
}}>
|
|
68
|
-
<Diagram ref={ref} animate={false} pannable={false} zoomable={false} diagram={diagram} padding={padding} width={width} height={height}/>
|
|
74
|
+
<Diagram ref={ref} animate={false} pannable={false} zoomable={false} minZoom={1} maxZoom={1} diagram={diagram} padding={padding} width={width} height={height}/>
|
|
69
75
|
</Box>
|
|
70
76
|
</Portal>);
|
|
71
77
|
});
|
|
@@ -34,11 +34,17 @@ const ExportMenu = ({ onExport, children }) => (<DropdownMenu.Root>
|
|
|
34
34
|
</DropdownMenu.Root>);
|
|
35
35
|
export const ViewActionsToolbar = ({ diagram }) => {
|
|
36
36
|
const [exportTo, setExportTo] = useState(null);
|
|
37
|
-
return (<Flex position='fixed' top='0' right='0' p=
|
|
37
|
+
return (<Flex position='fixed' top='0' right='0' p={{
|
|
38
|
+
initial: '3',
|
|
39
|
+
md: '2'
|
|
40
|
+
}} gap={'3'} justify='end' align='center'>
|
|
38
41
|
<DisplayModeSelector />
|
|
39
42
|
<Dialog.Root>
|
|
40
43
|
<Dialog.Trigger>
|
|
41
|
-
<Button variant='solid'
|
|
44
|
+
<Button variant='solid' size={{
|
|
45
|
+
initial: '1',
|
|
46
|
+
md: '2'
|
|
47
|
+
}}>
|
|
42
48
|
<ShareIcon />
|
|
43
49
|
<Text>Share</Text>
|
|
44
50
|
</Button>
|
|
@@ -46,7 +52,10 @@ export const ViewActionsToolbar = ({ diagram }) => {
|
|
|
46
52
|
<ShareDialog diagram={diagram}/>
|
|
47
53
|
</Dialog.Root>
|
|
48
54
|
<ExportMenu onExport={setExportTo}>
|
|
49
|
-
<Button variant='soft' color='gray'
|
|
55
|
+
<Button variant='soft' color='gray' size={{
|
|
56
|
+
initial: '1',
|
|
57
|
+
md: '2'
|
|
58
|
+
}}>
|
|
50
59
|
<Text>Export</Text>
|
|
51
60
|
<CaretDownIcon />
|
|
52
61
|
</Button>
|
|
@@ -58,26 +58,15 @@ export const selectLikeC4ViewAtom = (viewId) => {
|
|
|
58
58
|
if (import.meta.hot) {
|
|
59
59
|
let $updateViews;
|
|
60
60
|
viewsAtom.onMount = set => {
|
|
61
|
-
console.log('mount viewsAtom');
|
|
62
61
|
$updateViews = set;
|
|
63
62
|
return () => {
|
|
64
|
-
console.log('unmount viewsAtom');
|
|
65
63
|
$updateViews = undefined;
|
|
66
64
|
};
|
|
67
65
|
};
|
|
68
66
|
import.meta.hot.accept('/@vite-plugin-likec4/likec4-generated', md => {
|
|
69
67
|
const update = md?.LikeC4Views;
|
|
70
|
-
console.debug('accept ./data update');
|
|
71
|
-
console.dir(update, {
|
|
72
|
-
colors: true,
|
|
73
|
-
compact: false,
|
|
74
|
-
depth: 10
|
|
75
|
-
});
|
|
76
68
|
if (update) {
|
|
77
69
|
$updateViews?.(update);
|
|
78
70
|
}
|
|
79
|
-
else {
|
|
80
|
-
console.warn('no update', md);
|
|
81
|
-
}
|
|
82
71
|
});
|
|
83
72
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Diagram } from '@likec4/diagrams';
|
|
2
|
+
import { useWindowSize } from '@react-hookz/web/esm';
|
|
3
|
+
import { DiagramNotFound } from '../components';
|
|
4
|
+
import { useLikeC4View } from '../data';
|
|
5
|
+
import { useTransparentBackground } from './useTransparentBackground';
|
|
6
|
+
export function EmbedPage({ viewId, padding, transparentBg = true }) {
|
|
7
|
+
const { width, height } = useWindowSize();
|
|
8
|
+
const diagram = useLikeC4View(viewId);
|
|
9
|
+
useTransparentBackground(transparentBg && !!diagram);
|
|
10
|
+
if (!diagram) {
|
|
11
|
+
return <DiagramNotFound viewId={viewId}/>;
|
|
12
|
+
}
|
|
13
|
+
return (<Diagram animate={false} pannable={false} zoomable={false} diagram={diagram} padding={padding} width={width} height={height}/>);
|
|
14
|
+
}
|
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
import { Diagram } from '@likec4/diagrams';
|
|
2
2
|
import { useWindowSize } from '@react-hookz/web/esm';
|
|
3
|
-
import { useLayoutEffect } from 'react';
|
|
4
3
|
import { DiagramNotFound } from '../components';
|
|
5
4
|
import { useLikeC4View } from '../data';
|
|
5
|
+
import { useTransparentBackground } from './useTransparentBackground';
|
|
6
6
|
export function ExportPage({ viewId, padding }) {
|
|
7
7
|
const { width, height } = useWindowSize();
|
|
8
8
|
const diagram = useLikeC4View(viewId);
|
|
9
|
-
|
|
10
|
-
// We need to add a class to the HTML element
|
|
11
|
-
useLayoutEffect(() => {
|
|
12
|
-
// see ../../likec4.css
|
|
13
|
-
const classname = 'transparent-bg';
|
|
14
|
-
document.body.parentElement?.classList.add(classname);
|
|
15
|
-
return () => {
|
|
16
|
-
document.body.parentElement?.classList.remove(classname);
|
|
17
|
-
};
|
|
18
|
-
}, []);
|
|
9
|
+
useTransparentBackground(!!diagram);
|
|
19
10
|
if (!diagram) {
|
|
20
|
-
return <DiagramNotFound />;
|
|
11
|
+
return <DiagramNotFound viewId={viewId}/>;
|
|
21
12
|
}
|
|
22
|
-
return (<Diagram animate={false} pannable={false} zoomable={false} diagram={diagram} padding={padding} width={width} height={height}/>);
|
|
13
|
+
return (<Diagram animate={false} pannable={false} zoomable={false} maxZoom={1} minZoom={1} diagram={diagram} padding={padding} width={width} height={height}/>);
|
|
23
14
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
// To get the transparent background
|
|
3
|
+
// We need to add a class to the HTML element
|
|
4
|
+
export function useTransparentBackground(enabled = true) {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const htmlEl = document.body.parentElement;
|
|
7
|
+
if (!htmlEl || !enabled)
|
|
8
|
+
return;
|
|
9
|
+
// see ../../likec4.css
|
|
10
|
+
const classname = 'transparent-bg';
|
|
11
|
+
htmlEl.classList.add(classname);
|
|
12
|
+
return () => {
|
|
13
|
+
htmlEl.classList.remove(classname);
|
|
14
|
+
};
|
|
15
|
+
}, [enabled]);
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.diagramBg {
|
|
2
|
+
overscroll-behavior: none;
|
|
3
|
+
overflow: hidden;
|
|
4
|
+
--diagram-bg-size: 24px;
|
|
5
|
+
--diagram-bg-position-x: 0;
|
|
6
|
+
--diagram-bg-position-y: 0;
|
|
7
|
+
|
|
8
|
+
:global(.konvajs-content) {
|
|
9
|
+
|
|
10
|
+
&::before {
|
|
11
|
+
content: '';
|
|
12
|
+
position: absolute;
|
|
13
|
+
padding: 0;
|
|
14
|
+
margin: 0;
|
|
15
|
+
top: 0;
|
|
16
|
+
left: 0;
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: 100%;
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
touch-action: none;
|
|
21
|
+
user-select: none;
|
|
22
|
+
background-origin: border-box;
|
|
23
|
+
background-attachment: fixed;
|
|
24
|
+
background-image: radial-gradient(var(--gray-a3) 12%, transparent 12%);
|
|
25
|
+
background-position: var(--diagram-bg-position-x) var(--diagram-bg-position-y);
|
|
26
|
+
background-size: var(--diagram-bg-size) var(--diagram-bg-size);
|
|
27
|
+
z-index: -1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,24 +1,60 @@
|
|
|
1
1
|
import { Diagram, useDiagramApi } from '@likec4/diagrams';
|
|
2
2
|
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
|
3
3
|
import { useWindowSize } from '@react-hookz/web/esm';
|
|
4
|
-
import { $pages } from '
|
|
4
|
+
import { $pages } from '../router';
|
|
5
5
|
import { DiagramNotFound, ViewActionsToolbar } from '../components';
|
|
6
6
|
import { useLikeC4View } from '../data';
|
|
7
|
-
|
|
7
|
+
import { Fragment, useCallback, useEffect, useRef } from 'react';
|
|
8
|
+
import styles from './view-page.module.css';
|
|
9
|
+
const Paddings = [70, 20, 20, 40];
|
|
10
|
+
function round(n, d = 1) {
|
|
11
|
+
const m = Math.pow(10, d);
|
|
12
|
+
return Math.round(n * m) / m;
|
|
13
|
+
}
|
|
8
14
|
export function ViewPage({ viewId, showUI = true }) {
|
|
9
15
|
const { width, height } = useWindowSize();
|
|
10
|
-
const [ref, diagramApi] = useDiagramApi();
|
|
11
16
|
const diagram = useLikeC4View(viewId);
|
|
17
|
+
const pageDivRef = useRef(null);
|
|
18
|
+
const [ref, api] = useDiagramApi();
|
|
19
|
+
const handleTransform = useCallback(() => {
|
|
20
|
+
const stage = api.stage;
|
|
21
|
+
const style = pageDivRef.current?.style;
|
|
22
|
+
if (!stage || !style) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const pos = stage.getAbsolutePosition();
|
|
26
|
+
let scale = stage.scaleX();
|
|
27
|
+
while (scale < 0.5) {
|
|
28
|
+
scale = scale / 0.5;
|
|
29
|
+
}
|
|
30
|
+
const size = round(24 * scale);
|
|
31
|
+
const x = round(pos.x);
|
|
32
|
+
const y = round(pos.y);
|
|
33
|
+
style.setProperty('--diagram-bg-size', `${size}px`);
|
|
34
|
+
style.setProperty('--diagram-bg-position-x', `${x}px`);
|
|
35
|
+
style.setProperty('--diagram-bg-position-y', `${y}px`);
|
|
36
|
+
}, [api, pageDivRef]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const stage = api.stage;
|
|
39
|
+
if (!stage) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
stage.on('absoluteTransformChange', handleTransform);
|
|
43
|
+
handleTransform();
|
|
44
|
+
return () => {
|
|
45
|
+
stage.off('absoluteTransformChange', handleTransform);
|
|
46
|
+
};
|
|
47
|
+
}, [api]);
|
|
12
48
|
if (!diagram) {
|
|
13
|
-
return <DiagramNotFound />;
|
|
49
|
+
return <DiagramNotFound viewId={viewId}/>;
|
|
14
50
|
}
|
|
15
|
-
return (<Box position={'fixed'} inset='0' className=
|
|
51
|
+
return (<Box position={'fixed'} inset='0' className={styles.diagramBg} ref={pageDivRef}>
|
|
16
52
|
<Diagram ref={ref} diagram={diagram} padding={showUI ? Paddings : undefined} width={width} height={height} onNodeClick={node => {
|
|
17
53
|
if (node.navigateTo) {
|
|
18
54
|
$pages.view.open(node.navigateTo);
|
|
19
55
|
}
|
|
20
56
|
}} onEdgeClick={_ => ({})}/>
|
|
21
|
-
{showUI && (
|
|
57
|
+
{showUI && (<Fragment key='ui'>
|
|
22
58
|
<Flex position={'fixed'} top='0' p='3' style={{
|
|
23
59
|
left: 54
|
|
24
60
|
}} direction={'column'}>
|
|
@@ -29,7 +65,7 @@ export function ViewPage({ viewId, showUI = true }) {
|
|
|
29
65
|
{diagram.title || 'Untitled'}
|
|
30
66
|
</Heading>
|
|
31
67
|
</Flex>
|
|
32
|
-
<ViewActionsToolbar
|
|
33
|
-
|
|
68
|
+
<ViewActionsToolbar diagram={diagram}/>
|
|
69
|
+
</Fragment>)}
|
|
34
70
|
</Box>);
|
|
35
71
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// stores/router.ts
|
|
2
|
-
import { logger } from '@nanostores/logger';
|
|
3
1
|
import { useStore } from '@nanostores/react';
|
|
4
2
|
import { createSearchParams, createRouter, openPage, getPagePath } from '@nanostores/router';
|
|
5
3
|
import { computed } from 'nanostores';
|
|
@@ -30,24 +28,28 @@ const $route = computed([$router, $searchParams], (r, v) => {
|
|
|
30
28
|
return {
|
|
31
29
|
route: 'view',
|
|
32
30
|
params: {
|
|
33
|
-
viewId: r.params.viewId ?? 'index'
|
|
31
|
+
viewId: r.params.viewId ?? 'index',
|
|
32
|
+
theme: asTheme(v.theme) ?? 'dark'
|
|
34
33
|
},
|
|
35
34
|
showUI: 'showUI' in v ? v.showUI === 'true' : true
|
|
36
35
|
};
|
|
37
36
|
}
|
|
38
37
|
if (r?.route === 'export' || r?.route === 'embed') {
|
|
39
38
|
return {
|
|
40
|
-
route:
|
|
39
|
+
route: r.route,
|
|
41
40
|
params: {
|
|
42
41
|
viewId: r.params.viewId,
|
|
43
42
|
padding: asPadding(v.padding),
|
|
44
|
-
theme: asTheme(v.theme)
|
|
43
|
+
theme: r.route === 'embed' ? asTheme(v.theme) : undefined
|
|
45
44
|
},
|
|
46
45
|
showUI: false
|
|
47
46
|
};
|
|
48
47
|
}
|
|
49
48
|
return {
|
|
50
49
|
route: 'index',
|
|
50
|
+
params: {
|
|
51
|
+
theme: asTheme(v.theme) ?? 'dark'
|
|
52
|
+
},
|
|
51
53
|
showUI: 'showUI' in v ? v.showUI === 'true' : true
|
|
52
54
|
};
|
|
53
55
|
});
|
|
@@ -67,10 +69,10 @@ export const $pages = {
|
|
|
67
69
|
path: (viewId) => getPagePath($router, 'embed', { viewId })
|
|
68
70
|
}
|
|
69
71
|
};
|
|
70
|
-
if (import.meta.env.DEV) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
72
|
+
// if (import.meta.env.DEV) {
|
|
73
|
+
// logger({
|
|
74
|
+
// $searchParams,
|
|
75
|
+
// $router,
|
|
76
|
+
// $route
|
|
77
|
+
// })
|
|
78
|
+
// }
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { radixThemePreset } = require('radix-themes-tw');
|
|
2
|
-
const tailwindcssAnimate = require('tailwindcss-animate');
|
|
3
2
|
const { resolve } = require('node:path');
|
|
4
3
|
|
|
5
4
|
/** @type {import('tailwindcss').Config} */
|
|
@@ -14,6 +13,5 @@ module.exports = {
|
|
|
14
13
|
// ...
|
|
15
14
|
// Preflight is enabled by default, we disable it here
|
|
16
15
|
preflight: false
|
|
17
|
-
}
|
|
18
|
-
plugins: [tailwindcssAnimate],
|
|
16
|
+
}
|
|
19
17
|
};
|
|
@@ -21,17 +21,11 @@
|
|
|
21
21
|
"noFallthroughCasesInSwitch": true,
|
|
22
22
|
"baseUrl": ".",
|
|
23
23
|
"rootDir": ".",
|
|
24
|
-
"paths": {
|
|
25
|
-
"~/*": [
|
|
26
|
-
"./src/*"
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
24
|
"types": [
|
|
30
25
|
"vite/client"
|
|
31
26
|
]
|
|
32
27
|
},
|
|
33
28
|
"include": [
|
|
34
|
-
"src"
|
|
35
|
-
"src/likec4-views.ts"
|
|
29
|
+
"src"
|
|
36
30
|
]
|
|
37
31
|
}
|