likec4 0.37.1 → 0.40.0-build.1
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/app/favicon.svg +6 -0
- package/app/index.html +14 -0
- package/app/likec4.css +85 -0
- package/app/postcss.config.cjs +11 -0
- package/app/robots.txt +2 -0
- package/app/src/App.tsx +42 -0
- package/app/src/components/DiagramNotFound.tsx +30 -0
- package/app/src/components/ThemePanelToggle.tsx +15 -0
- package/app/src/components/index.ts +4 -0
- package/app/src/components/sidebar/DiagramsTree.module.css +83 -0
- package/app/src/components/sidebar/DiagramsTree.tsx +77 -0
- package/app/src/components/sidebar/Sidebar.tsx +67 -0
- package/app/src/components/sidebar/styles.module.css +85 -0
- package/app/src/components/view-page/ShareDialog.tsx +148 -0
- package/app/src/components/view-page/ViewActionsToolbar.tsx +76 -0
- package/app/src/data/atoms.ts +108 -0
- package/app/src/data/hooks.ts +16 -0
- package/app/src/data/index.ts +1 -0
- package/app/src/data/likec4.d.ts +5 -0
- package/app/src/data/sidebar-diagram-tree.ts +52 -0
- package/app/src/main.tsx +12 -0
- package/app/src/pages/export.module.css +4 -0
- package/app/src/pages/export.page.tsx +37 -0
- package/app/src/pages/index.module.css +11 -0
- package/app/src/pages/index.page.tsx +103 -0
- package/app/src/pages/index.ts +3 -0
- package/app/src/pages/view.page.tsx +67 -0
- package/app/src/router.ts +67 -0
- package/app/src/utils/index.ts +1 -0
- package/app/src/utils/utils.ts +6 -0
- package/app/tailwind.config.cjs +19 -0
- package/app/tsconfig.json +41 -0
- package/bin/likec4.mjs +3 -0
- package/dist/cli/index.js +348 -0
- package/package.json +66 -9
package/app/favicon.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M152.266 152.674V14H29.2171C24.1477 14 20 18.3487 20 23.6637V153.158H152.266V152.674Z" fill="#5E98AF"/>
|
|
3
|
+
<path d="M170.237 152.673H236.6V92.7575C236.6 87.4424 232.452 83.0938 227.383 83.0938H170.237V152.673Z" fill="#5E98AF"/>
|
|
4
|
+
<path d="M152.267 171.515H86.3647V231.43C86.3647 236.745 90.5125 241.094 95.5819 241.094H152.728V171.515H152.267Z" fill="#5E98AF"/>
|
|
5
|
+
<path d="M170.237 171.515V241.094H227.383C232.452 241.094 236.6 236.745 236.6 231.43V171.515H170.237Z" fill="#5E98AF"/>
|
|
6
|
+
</svg>
|
package/app/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="wwidth=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
7
|
+
<title>LikeC4</title>
|
|
8
|
+
<link rel="stylesheet" type="text/css" href="/likec4.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="like4-root" class="w-screen h-screen m-0 p-0"></div>
|
|
12
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
package/app/likec4.css
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
@import '@radix-ui/themes/styles.css';
|
|
2
|
+
|
|
3
|
+
@tailwind base;
|
|
4
|
+
@tailwind components;
|
|
5
|
+
@tailwind utilities;
|
|
6
|
+
|
|
7
|
+
/* @layer base {
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
--background: 0 0% 100%;
|
|
11
|
+
--foreground: 222.2 84% 4.9%;
|
|
12
|
+
|
|
13
|
+
--card: 0 0% 100%;
|
|
14
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
15
|
+
|
|
16
|
+
--popover: 0 0% 100%;
|
|
17
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
18
|
+
|
|
19
|
+
--primary: 222.2 47.4% 11.2%;
|
|
20
|
+
--primary-foreground: 210 40% 98%;
|
|
21
|
+
|
|
22
|
+
--secondary: 210 40% 96.1%;
|
|
23
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
24
|
+
yarn add @fontsource-variable/noto-sans-tc
|
|
25
|
+
--muted: 210 40% 96.1%;
|
|
26
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
27
|
+
|
|
28
|
+
--accent: 210 40% 96.1%;
|
|
29
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
30
|
+
|
|
31
|
+
--destructive: 0 84.2% 60.2%;
|
|
32
|
+
--destructive-foreground: 210 40% 98%;
|
|
33
|
+
|
|
34
|
+
--border: 214.3 31.8% 91.4%;
|
|
35
|
+
--input: 214.3 31.8% 91.4%;
|
|
36
|
+
--ring: 222.2 84% 4.9%;
|
|
37
|
+
|
|
38
|
+
--radius: 0.5rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.dark {
|
|
42
|
+
--background: 222.2 84% 4.9%;
|
|
43
|
+
--foreground: 210 40% 98%;
|
|
44
|
+
|
|
45
|
+
--card: 222.2 84% 4.9%;
|
|
46
|
+
--card-foreground: 210 40% 98%;
|
|
47
|
+
|
|
48
|
+
--popover: 222.2 84% 4.9%;
|
|
49
|
+
--popover-foreground: 210 40% 98%;
|
|
50
|
+
|
|
51
|
+
--primary: 210 40% 98%;
|
|
52
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
53
|
+
|
|
54
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
55
|
+
--secondary-foreground: 210 40% 98%;
|
|
56
|
+
|
|
57
|
+
--muted: 217.2 32.6% 17.5%;
|
|
58
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
59
|
+
|
|
60
|
+
--accent: 217.2 32.6% 17.5%;
|
|
61
|
+
--accent-foreground: 210 40% 98%;
|
|
62
|
+
|
|
63
|
+
--destructive: 0 62.8% 30.6%;
|
|
64
|
+
--destructive-foreground: 210 40% 98%;
|
|
65
|
+
|
|
66
|
+
--border: 217.2 32.6% 17.5%;
|
|
67
|
+
--input: 217.2 32.6% 17.5%;
|
|
68
|
+
--ring: 212.7 26.8% 83.9%;
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
} */
|
|
73
|
+
|
|
74
|
+
.radix-themes {
|
|
75
|
+
/* --default-font-family: 'Rubik Variable',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji'; */
|
|
76
|
+
--font-weight-light: 200;
|
|
77
|
+
--font-weight-regular: 400;
|
|
78
|
+
--font-weight-medium: 500;
|
|
79
|
+
--font-weight-bold: 600;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
html, body {
|
|
83
|
+
margin: 0;
|
|
84
|
+
padding: 0;
|
|
85
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const autoprefixer = require('autoprefixer');
|
|
2
|
+
const nesting = require('tailwindcss/nesting/index.js');
|
|
3
|
+
const tailwindcss = require('tailwindcss');
|
|
4
|
+
const { resolve } = require('path');
|
|
5
|
+
|
|
6
|
+
const tailwindCfg = resolve(__dirname, './tailwind.config.cjs');
|
|
7
|
+
|
|
8
|
+
/* @type {import('postcss').Postcss} */
|
|
9
|
+
module.exports = {
|
|
10
|
+
plugins: [nesting, tailwindcss(tailwindCfg), autoprefixer],
|
|
11
|
+
};
|
package/app/robots.txt
ADDED
package/app/src/App.tsx
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Provider } from 'jotai'
|
|
2
|
+
import { useAtomsDevtools } from 'jotai-devtools'
|
|
3
|
+
import type { PropsWithChildren } from 'react'
|
|
4
|
+
import { Fragment } from 'react'
|
|
5
|
+
import { Sidebar } from './components'
|
|
6
|
+
import { ExportPage, IndexPage, ViewPage } from './pages'
|
|
7
|
+
import { useRoute } from './router'
|
|
8
|
+
|
|
9
|
+
const Routes = () => {
|
|
10
|
+
const r = useRoute()
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
{r.route === 'index' && <IndexPage key='index' />}
|
|
14
|
+
{r.route === 'view' && <ViewPage key='view' viewId={r.params.viewId} showUI={r.showUI} />}
|
|
15
|
+
{r.route === 'export' && (
|
|
16
|
+
<ExportPage key='export' viewId={r.params.viewId} padding={r.params.padding} />
|
|
17
|
+
)}
|
|
18
|
+
{r.showUI && (
|
|
19
|
+
<Fragment key='ui'>
|
|
20
|
+
<Sidebar />
|
|
21
|
+
</Fragment>
|
|
22
|
+
)}
|
|
23
|
+
</>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const AtomsDevTools = import.meta.env.DEV
|
|
28
|
+
? ({ children }: PropsWithChildren) => {
|
|
29
|
+
useAtomsDevtools('demo')
|
|
30
|
+
return <>{children}</>
|
|
31
|
+
}
|
|
32
|
+
: Fragment
|
|
33
|
+
|
|
34
|
+
export default function App() {
|
|
35
|
+
return (
|
|
36
|
+
<Provider>
|
|
37
|
+
<AtomsDevTools>
|
|
38
|
+
<Routes />
|
|
39
|
+
</AtomsDevTools>
|
|
40
|
+
</Provider>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
|
|
2
|
+
import { Box, Button, Card, Flex, Heading, IconButton, Text } from '@radix-ui/themes'
|
|
3
|
+
import { $pages } from '../router'
|
|
4
|
+
|
|
5
|
+
export const DiagramNotFound = () => {
|
|
6
|
+
return (
|
|
7
|
+
<Flex position='fixed' inset='0' align='center' justify='center'>
|
|
8
|
+
<Card color='red' size='3'>
|
|
9
|
+
<Flex gap='4' direction='row' align='start'>
|
|
10
|
+
<Box grow='0' shrink='0' pt='1'>
|
|
11
|
+
<IconButton variant='ghost' color='amber'>
|
|
12
|
+
<ExclamationTriangleIcon width={50} height={50} />
|
|
13
|
+
</IconButton>
|
|
14
|
+
</Box>
|
|
15
|
+
<Flex gap='3' direction='column'>
|
|
16
|
+
<Heading trim='both' color='amber' size='4'>
|
|
17
|
+
Diagram not found
|
|
18
|
+
</Heading>
|
|
19
|
+
<Text as='div'>The diagram you are looking for does not exist.</Text>
|
|
20
|
+
<Box>
|
|
21
|
+
<Button variant='soft' color='amber' onClick={() => $pages.index.open()}>
|
|
22
|
+
Home page
|
|
23
|
+
</Button>
|
|
24
|
+
</Box>
|
|
25
|
+
</Flex>
|
|
26
|
+
</Flex>
|
|
27
|
+
</Card>
|
|
28
|
+
</Flex>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MoonIcon } from '@radix-ui/react-icons'
|
|
2
|
+
import { IconButton, ThemePanel } from '@radix-ui/themes'
|
|
3
|
+
import { useToggle } from '@react-hookz/web/esm'
|
|
4
|
+
|
|
5
|
+
export const ThemePanelToggle = () => {
|
|
6
|
+
const [isOpened, toggle] = useToggle(false, true)
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
<IconButton color='gray' variant={isOpened ? 'solid' : 'soft'} onClick={toggle} size={'2'}>
|
|
10
|
+
<MoonIcon width={16} height={16} />
|
|
11
|
+
</IconButton>
|
|
12
|
+
{isOpened && <ThemePanel style={{ top: 50 }} />}
|
|
13
|
+
</>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
.treeview {
|
|
2
|
+
min-width: 200px;
|
|
3
|
+
max-width: 300px;
|
|
4
|
+
|
|
5
|
+
ul {
|
|
6
|
+
list-style: none;
|
|
7
|
+
padding: 0;
|
|
8
|
+
margin: 0;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: var(--space-1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
:global(.tree-node-group--expanded) {
|
|
15
|
+
margin-top: var(--space-1);
|
|
16
|
+
padding-left: var(--space-5);
|
|
17
|
+
|
|
18
|
+
&:has(> :global(.tree-branch-wrapper)) {
|
|
19
|
+
padding-left: var(--space-3);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* :global(.tree-branch-wrapper):where([aria-expanded='true']) > :global(.tree-branch-wrapper) {
|
|
24
|
+
margin-top: var(--space-1);
|
|
25
|
+
margin-left: var(--space-2);
|
|
26
|
+
} */
|
|
27
|
+
|
|
28
|
+
:global(.tree-leaf-list-item) {
|
|
29
|
+
/* margin-left: var(--space-2); */
|
|
30
|
+
|
|
31
|
+
& > [role='treeitem'] {
|
|
32
|
+
user-select: none;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
/* color: var(--gray-12); */
|
|
35
|
+
padding: calc(var(--space-1) * 0.75);
|
|
36
|
+
padding-left: var(--space-2);
|
|
37
|
+
padding-right: var(--space-2);
|
|
38
|
+
margin-top: calc(var(--space-1) * -0.15);
|
|
39
|
+
margin-bottom: calc(var(--space-1) * -0.15);
|
|
40
|
+
/* margin-left: calc(var(--space-1) * -2); */
|
|
41
|
+
/* margin-right: 0; */
|
|
42
|
+
transition-property: background-color;
|
|
43
|
+
transition-timing-function: cubic-bezier(0, 0.31, 0, 1.03);
|
|
44
|
+
transition-duration: 140ms;
|
|
45
|
+
|
|
46
|
+
border-radius: max(var(--radius-3), var(--radius-full));
|
|
47
|
+
|
|
48
|
+
svg {
|
|
49
|
+
color: var(--gray-a10);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&:hover:where(:not([aria-selected='true'])) {
|
|
53
|
+
background-color: var(--gray-a6);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&:where([aria-selected='true']) {
|
|
57
|
+
background-color: var(--accent-a7);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
:global(.tree-branch-wrapper) {
|
|
62
|
+
& > :global(.tree-node__branch) {
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
padding-top: calc(var(--space-1) * 0.75);
|
|
65
|
+
padding-bottom: calc(var(--space-1) * 0.75);
|
|
66
|
+
|
|
67
|
+
transition-property: background-color;
|
|
68
|
+
transition-timing-function: cubic-bezier(0, 0.31, 0, 1.03);
|
|
69
|
+
transition-duration: 120ms;
|
|
70
|
+
|
|
71
|
+
border-radius: max(var(--radius-3), var(--radius-full));
|
|
72
|
+
|
|
73
|
+
&:hover {
|
|
74
|
+
background-color: var(--gray-a5);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
:global(.tree-branch-wrapper) ~ :global(.tree-leaf-list-item) {
|
|
80
|
+
margin-left: var(--space-3);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DashboardIcon, TriangleRightIcon } from '@radix-ui/react-icons'
|
|
2
|
+
import { Box, Flex, Text } from '@radix-ui/themes'
|
|
3
|
+
import TreeView, { type INode } from 'react-accessible-treeview'
|
|
4
|
+
import { useDiagramsTree } from '../../data'
|
|
5
|
+
import { $pages, useRoute } from '../../router'
|
|
6
|
+
import { cn } from '../../utils'
|
|
7
|
+
import styles from './DiagramsTree.module.css'
|
|
8
|
+
|
|
9
|
+
function inTree(id: string, data: INode[]): boolean {
|
|
10
|
+
return data.some(d => d.id === id)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function DiagramsTree() {
|
|
14
|
+
const data = useDiagramsTree()
|
|
15
|
+
const r = useRoute()
|
|
16
|
+
|
|
17
|
+
const viewId = r.route === 'view' || r.route === 'export' ? r.params.viewId : null
|
|
18
|
+
const selectedId = viewId && inTree(viewId, data) ? [viewId] : []
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Box className={styles.treeview}>
|
|
22
|
+
<TreeView
|
|
23
|
+
data={data}
|
|
24
|
+
propagateSelect
|
|
25
|
+
propagateSelectUpwards
|
|
26
|
+
selectedIds={selectedId}
|
|
27
|
+
onNodeSelect={({ element, isBranch }) => {
|
|
28
|
+
if (isBranch) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
$pages.view.open('' + element.id)
|
|
32
|
+
}}
|
|
33
|
+
nodeRenderer={({
|
|
34
|
+
element,
|
|
35
|
+
isBranch,
|
|
36
|
+
isExpanded,
|
|
37
|
+
getNodeProps,
|
|
38
|
+
handleExpand,
|
|
39
|
+
handleSelect
|
|
40
|
+
}) => {
|
|
41
|
+
return (
|
|
42
|
+
<Flex
|
|
43
|
+
{...getNodeProps({ onClick: isBranch ? handleExpand : handleSelect })}
|
|
44
|
+
align={'center'}
|
|
45
|
+
gap={isBranch ? '1' : '2'}
|
|
46
|
+
>
|
|
47
|
+
{isBranch && (
|
|
48
|
+
<Box style={{ lineHeight: '15px' }}>
|
|
49
|
+
<TriangleRightIcon
|
|
50
|
+
width={15}
|
|
51
|
+
height={15}
|
|
52
|
+
className={cn('transition duration-200 ease-out', isExpanded && 'rotate-90')}
|
|
53
|
+
/>
|
|
54
|
+
</Box>
|
|
55
|
+
)}
|
|
56
|
+
{!isBranch && (
|
|
57
|
+
<Box style={{ lineHeight: '14px' }} width={'min-content'}>
|
|
58
|
+
<DashboardIcon width={14} height={14} />
|
|
59
|
+
</Box>
|
|
60
|
+
)}
|
|
61
|
+
<Box asChild grow={'1'}>
|
|
62
|
+
<Text
|
|
63
|
+
as='div'
|
|
64
|
+
size={'2'}
|
|
65
|
+
weight={isBranch ? 'bold' : undefined}
|
|
66
|
+
className='truncate'
|
|
67
|
+
>
|
|
68
|
+
{(isBranch ? '🗂️ ' : '') + element.name}
|
|
69
|
+
</Text>
|
|
70
|
+
</Box>
|
|
71
|
+
</Flex>
|
|
72
|
+
)
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
</Box>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Box, Button, Flex, IconButton, ScrollArea, Separator } from '@radix-ui/themes'
|
|
2
|
+
import { useClickOutside, useToggle } from '@react-hookz/web/esm'
|
|
3
|
+
|
|
4
|
+
import { HamburgerMenuIcon, ArrowLeftIcon } from '@radix-ui/react-icons'
|
|
5
|
+
import { useRef } from 'react'
|
|
6
|
+
import { cn } from '~/utils'
|
|
7
|
+
|
|
8
|
+
import { DiagramsTree } from './DiagramsTree'
|
|
9
|
+
import styles from './styles.module.css'
|
|
10
|
+
import { $pages } from '../../router'
|
|
11
|
+
|
|
12
|
+
export const Sidebar = () => {
|
|
13
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
14
|
+
const [isOpened, toggle] = useToggle(false, true)
|
|
15
|
+
|
|
16
|
+
useClickOutside(ref, () => isOpened && toggle())
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<Flex
|
|
21
|
+
position='fixed'
|
|
22
|
+
left='0'
|
|
23
|
+
p={'2'}
|
|
24
|
+
className={cn(
|
|
25
|
+
styles.trigger,
|
|
26
|
+
'inset-y-0 cursor-pointer items-start',
|
|
27
|
+
isOpened && 'display-none'
|
|
28
|
+
)}
|
|
29
|
+
onClick={toggle}
|
|
30
|
+
>
|
|
31
|
+
<IconButton size='2' color='gray' variant='soft'>
|
|
32
|
+
<HamburgerMenuIcon width={22} height={22} />
|
|
33
|
+
</IconButton>
|
|
34
|
+
</Flex>
|
|
35
|
+
<Flex
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={styles.navsidebar}
|
|
38
|
+
position='fixed'
|
|
39
|
+
left='0'
|
|
40
|
+
top='0'
|
|
41
|
+
bottom='0'
|
|
42
|
+
data-opened={isOpened}
|
|
43
|
+
>
|
|
44
|
+
<ScrollArea scrollbars='vertical' type='scroll'>
|
|
45
|
+
<Box p='4' pl='2'>
|
|
46
|
+
<Button
|
|
47
|
+
variant='ghost'
|
|
48
|
+
ml='2'
|
|
49
|
+
mt='1'
|
|
50
|
+
size='1'
|
|
51
|
+
color='gray'
|
|
52
|
+
onClick={_ => {
|
|
53
|
+
toggle()
|
|
54
|
+
$pages.index.open()
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<ArrowLeftIcon />
|
|
58
|
+
Back to dashboard
|
|
59
|
+
</Button>
|
|
60
|
+
<Separator orientation='horizontal' my='3' size={'4'} />
|
|
61
|
+
<DiagramsTree />
|
|
62
|
+
</Box>
|
|
63
|
+
</ScrollArea>
|
|
64
|
+
</Flex>
|
|
65
|
+
</>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
.trigger {
|
|
2
|
+
|
|
3
|
+
cursor: pointer;
|
|
4
|
+
|
|
5
|
+
&::before {
|
|
6
|
+
transition-property: all;
|
|
7
|
+
transition-timing-function: cubic-bezier(0, 0.31, 0, 1.03);
|
|
8
|
+
transition-duration: 140ms;
|
|
9
|
+
|
|
10
|
+
position: absolute;
|
|
11
|
+
content: '';
|
|
12
|
+
inset: 0;
|
|
13
|
+
background: var(--gray-7);
|
|
14
|
+
opacity: 0;
|
|
15
|
+
z-index: 1;
|
|
16
|
+
/* visibility: hidden; */
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&:hover::before {
|
|
20
|
+
visibility: visible;
|
|
21
|
+
opacity: 0.7;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
& > * {
|
|
25
|
+
position: relative;
|
|
26
|
+
z-index: 2;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.navsidebar {
|
|
31
|
+
backdrop-filter: blur(6px);
|
|
32
|
+
|
|
33
|
+
&::before {
|
|
34
|
+
transition: all 0.26s ease-in-out;
|
|
35
|
+
position: absolute;
|
|
36
|
+
content: '';
|
|
37
|
+
inset: 0;
|
|
38
|
+
background: var(--gray-7);
|
|
39
|
+
opacity: 0.7;
|
|
40
|
+
z-index: 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
& > div {
|
|
44
|
+
position: relative;
|
|
45
|
+
z-index: 2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
transition: transform 0.21s cubic-bezier(0.4, 0, 0.2, 1);
|
|
49
|
+
transform: translateX(-100%);
|
|
50
|
+
|
|
51
|
+
&[data-opened='true'] {
|
|
52
|
+
transform: translateX(0);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
:global(.rt-variant-soft),
|
|
57
|
+
:global(.rt-variant-solid) {
|
|
58
|
+
&.navitem {
|
|
59
|
+
@apply transition-colors;
|
|
60
|
+
/* color: var(--accent-10); */
|
|
61
|
+
/* background-color: transparent; */
|
|
62
|
+
justify-content: space-between;
|
|
63
|
+
align-items: center;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
&:hover, &[data-current="true"] {
|
|
68
|
+
color: var(--accent-11);
|
|
69
|
+
background-color: var(--accent-a3);
|
|
70
|
+
} */
|
|
71
|
+
|
|
72
|
+
.icon {
|
|
73
|
+
visibility: hidden;
|
|
74
|
+
transform: translateX(-25%);
|
|
75
|
+
opacity: 0.7;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&:hover .icon {
|
|
79
|
+
visibility: visible;
|
|
80
|
+
transition: all 0.15s ease-out;
|
|
81
|
+
transform: translateX(0);
|
|
82
|
+
opacity: 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { DiagramView } from '@likec4/diagrams'
|
|
2
|
+
import {
|
|
3
|
+
ExclamationTriangleIcon,
|
|
4
|
+
InfoCircledIcon,
|
|
5
|
+
OpenInNewWindowIcon
|
|
6
|
+
} from '@radix-ui/react-icons'
|
|
7
|
+
import {
|
|
8
|
+
Box,
|
|
9
|
+
Button,
|
|
10
|
+
Callout,
|
|
11
|
+
Code,
|
|
12
|
+
Dialog,
|
|
13
|
+
Flex,
|
|
14
|
+
Link,
|
|
15
|
+
Select,
|
|
16
|
+
Tabs,
|
|
17
|
+
Text
|
|
18
|
+
} from '@radix-ui/themes'
|
|
19
|
+
import { useState } from 'react'
|
|
20
|
+
|
|
21
|
+
const embedCode = (diagram: DiagramView, theme: string) => {
|
|
22
|
+
const padding = 20
|
|
23
|
+
const params = new URLSearchParams()
|
|
24
|
+
params.set('embed', diagram.id)
|
|
25
|
+
params.set('padding', `${padding}`)
|
|
26
|
+
if (theme !== 'system') {
|
|
27
|
+
params.set('theme', theme)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const width = diagram.width + padding * 2
|
|
31
|
+
const height = diagram.height + padding * 2
|
|
32
|
+
|
|
33
|
+
const url = new URL(window.location.href)
|
|
34
|
+
url.search = params.toString()
|
|
35
|
+
const iframe = `<iframe src="${url.href}" width="100%" height="100%" style="border:0;background:transparent;"></iframe>`
|
|
36
|
+
|
|
37
|
+
const code = `
|
|
38
|
+
<div style="aspect-ratio:${width}/${height};max-width:${width}px;width:100%;height:auto;padding:0;margin-left:auto;margin-right:auto">
|
|
39
|
+
${iframe}
|
|
40
|
+
</div>`.trim()
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
code,
|
|
44
|
+
href: url.href
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const ShareDialog = ({ diagram }: { diagram: DiagramView }) => {
|
|
49
|
+
const [theme, setTheme] = useState('system')
|
|
50
|
+
|
|
51
|
+
const { code, href } = embedCode(diagram, theme)
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Dialog.Content size={'2'} style={{ maxWidth: 700, minWidth: 300 }}>
|
|
55
|
+
<Tabs.Root defaultValue='embed'>
|
|
56
|
+
<Tabs.List>
|
|
57
|
+
<Tabs.Trigger value='embed'>Embed</Tabs.Trigger>
|
|
58
|
+
<Tabs.Trigger value='script'>Script</Tabs.Trigger>
|
|
59
|
+
<Tabs.Trigger value='public'>Public URL</Tabs.Trigger>
|
|
60
|
+
</Tabs.List>
|
|
61
|
+
|
|
62
|
+
<Box px='1' py='4'>
|
|
63
|
+
<Tabs.Content value='embed'>
|
|
64
|
+
<Flex direction='column' gap='4'>
|
|
65
|
+
{code.includes('http://localhost') && (
|
|
66
|
+
<Callout.Root size='1' color='amber'>
|
|
67
|
+
<Callout.Icon>
|
|
68
|
+
<ExclamationTriangleIcon />
|
|
69
|
+
</Callout.Icon>
|
|
70
|
+
<Callout.Text>
|
|
71
|
+
This is a local URL. You need to build and deploy your diagrams to a public URL
|
|
72
|
+
to make it available for embedding.
|
|
73
|
+
</Callout.Text>
|
|
74
|
+
</Callout.Root>
|
|
75
|
+
)}
|
|
76
|
+
<label>
|
|
77
|
+
<Flex direction='row' justify='between'>
|
|
78
|
+
<Text as='div' size='2' weight='medium'>
|
|
79
|
+
Code
|
|
80
|
+
</Text>
|
|
81
|
+
<Flex asChild display='inline-flex' gap='1' align='center'>
|
|
82
|
+
<Link size='2' href={href} target='_blank'>
|
|
83
|
+
<Text as='span'>Open in new tab</Text>
|
|
84
|
+
<Text as='span'>
|
|
85
|
+
<OpenInNewWindowIcon width={12} height={12} />
|
|
86
|
+
</Text>
|
|
87
|
+
</Link>
|
|
88
|
+
</Flex>
|
|
89
|
+
</Flex>
|
|
90
|
+
<Box
|
|
91
|
+
asChild
|
|
92
|
+
display={'block'}
|
|
93
|
+
my='2'
|
|
94
|
+
p='2'
|
|
95
|
+
className='whitespace-pre-wrap overflow-scroll select-all'
|
|
96
|
+
>
|
|
97
|
+
<Code variant='soft' autoFocus>
|
|
98
|
+
{code}
|
|
99
|
+
</Code>
|
|
100
|
+
</Box>
|
|
101
|
+
</label>
|
|
102
|
+
<Text as='div' size='2' color='gray' trim={'start'}>
|
|
103
|
+
Embeded view is an iframe with a static diagram
|
|
104
|
+
</Text>
|
|
105
|
+
<label>
|
|
106
|
+
<Text as='div' size='2' weight='medium' mb='1'>
|
|
107
|
+
Theme
|
|
108
|
+
</Text>
|
|
109
|
+
<Select.Root size='2' defaultValue={theme} onValueChange={v => setTheme(v)}>
|
|
110
|
+
<Select.Trigger variant='soft' />
|
|
111
|
+
<Select.Content>
|
|
112
|
+
<Select.Item value='system'>Same as system</Select.Item>
|
|
113
|
+
<Select.Item value='light'>Light</Select.Item>
|
|
114
|
+
<Select.Item value='dark'>Dark</Select.Item>
|
|
115
|
+
</Select.Content>
|
|
116
|
+
</Select.Root>
|
|
117
|
+
</label>
|
|
118
|
+
</Flex>
|
|
119
|
+
</Tabs.Content>
|
|
120
|
+
|
|
121
|
+
<Tabs.Content value='public'>
|
|
122
|
+
<Callout.Root color='amber'>
|
|
123
|
+
<Callout.Icon>
|
|
124
|
+
<InfoCircledIcon />
|
|
125
|
+
</Callout.Icon>
|
|
126
|
+
<Callout.Text>This feature is not implemented yet.</Callout.Text>
|
|
127
|
+
</Callout.Root>
|
|
128
|
+
</Tabs.Content>
|
|
129
|
+
<Tabs.Content value='script'>
|
|
130
|
+
<Callout.Root color='amber'>
|
|
131
|
+
<Callout.Icon>
|
|
132
|
+
<InfoCircledIcon />
|
|
133
|
+
</Callout.Icon>
|
|
134
|
+
<Callout.Text>This feature is not implemented yet.</Callout.Text>
|
|
135
|
+
</Callout.Root>
|
|
136
|
+
</Tabs.Content>
|
|
137
|
+
</Box>
|
|
138
|
+
</Tabs.Root>
|
|
139
|
+
<Flex gap='3' mt='1' justify='end'>
|
|
140
|
+
<Dialog.Close>
|
|
141
|
+
<Button variant='soft' color='gray'>
|
|
142
|
+
Close
|
|
143
|
+
</Button>
|
|
144
|
+
</Dialog.Close>
|
|
145
|
+
</Flex>
|
|
146
|
+
</Dialog.Content>
|
|
147
|
+
)
|
|
148
|
+
}
|