likec4 0.40.0-build.9 → 0.40.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 (55) hide show
  1. package/dist/@likec4/diagrams/diagram/Diagram.js +21 -12
  2. package/dist/@likec4/diagrams/diagram/Nodes.js +3 -3
  3. package/dist/@likec4/diagrams/diagram/icons/ExternalLink.js +0 -1
  4. package/dist/@likec4/diagrams/diagram/shapes/utils.js +4 -4
  5. package/dist/@likec4/diagrams/diagram/state/hooks.js +12 -12
  6. package/dist/@likec4/diagrams/diagram/state/provider.js +2 -2
  7. package/dist/@likec4/diagrams/hooks/index.js +1 -1
  8. package/dist/@likec4/diagrams/hooks/useDiagramApi.js +27 -0
  9. package/dist/@likec4/diagrams/konva-html.js +4 -4
  10. package/dist/@likec4/diagrams/konva-portal.js +2 -2
  11. package/dist/__app__/src/App.jsx +26 -0
  12. package/dist/__app__/src/components/{DiagramNotFound.tsx → DiagramNotFound.jsx} +7 -10
  13. package/dist/__app__/src/components/ThemePanelToggle.jsx +12 -0
  14. package/dist/__app__/src/components/index.js +4 -0
  15. package/dist/__app__/src/components/sidebar/DiagramsTree.jsx +38 -0
  16. package/dist/__app__/src/components/sidebar/Sidebar.jsx +35 -0
  17. package/dist/__app__/src/components/sidebar/styles.module.css +0 -31
  18. package/dist/__app__/src/components/view-page/{ShareDialog.tsx → ShareDialog.jsx} +34 -68
  19. package/dist/__app__/src/components/view-page/ViewActionsToolbar.jsx +60 -0
  20. package/dist/__app__/src/data/atoms.js +83 -0
  21. package/dist/__app__/src/data/hooks.js +11 -0
  22. package/dist/__app__/src/data/index.js +1 -0
  23. package/dist/__app__/src/data/sidebar-diagram-tree.js +38 -0
  24. package/dist/__app__/src/main.jsx +9 -0
  25. package/dist/__app__/src/pages/export.page.jsx +19 -0
  26. package/dist/__app__/src/pages/index.js +3 -0
  27. package/dist/__app__/src/pages/index.module.css +1 -1
  28. package/dist/__app__/src/pages/index.page.jsx +57 -0
  29. package/dist/__app__/src/pages/view.page.jsx +35 -0
  30. package/dist/__app__/src/router.js +60 -0
  31. package/dist/__app__/src/utils/index.js +1 -0
  32. package/dist/__app__/src/utils/utils.js +5 -0
  33. package/dist/__app__/tsconfig.json +16 -8
  34. package/dist/cli/index.js +266 -57675
  35. package/package.json +31 -15
  36. package/dist/@likec4/diagrams/hooks/useDiagramRef.js +0 -21
  37. package/dist/__app__/src/App.tsx +0 -37
  38. package/dist/__app__/src/components/ThemePanelToggle.tsx +0 -15
  39. package/dist/__app__/src/components/index.ts +0 -4
  40. package/dist/__app__/src/components/sidebar/DiagramsTree.tsx +0 -77
  41. package/dist/__app__/src/components/sidebar/Sidebar.tsx +0 -67
  42. package/dist/__app__/src/components/view-page/ViewActionsToolbar.tsx +0 -76
  43. package/dist/__app__/src/data/atoms.ts +0 -108
  44. package/dist/__app__/src/data/hooks.ts +0 -16
  45. package/dist/__app__/src/data/index.ts +0 -1
  46. package/dist/__app__/src/data/likec4.d.ts +0 -5
  47. package/dist/__app__/src/data/sidebar-diagram-tree.ts +0 -52
  48. package/dist/__app__/src/main.tsx +0 -12
  49. package/dist/__app__/src/pages/export.page.tsx +0 -37
  50. package/dist/__app__/src/pages/index.page.tsx +0 -103
  51. package/dist/__app__/src/pages/index.ts +0 -3
  52. package/dist/__app__/src/pages/view.page.tsx +0 -67
  53. package/dist/__app__/src/router.ts +0 -67
  54. package/dist/__app__/src/utils/index.ts +0 -1
  55. package/dist/__app__/src/utils/utils.ts +0 -6
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { nonNullable, defaultTheme as theme } from "@likec4/core";
3
3
  import { useHookableRef, useUpdateEffect } from "@react-hookz/web/esm";
4
- import { useSpring, easings } from "@react-spring/konva";
4
+ import { easings, useSpring } from "@react-spring/konva";
5
5
  import { clamp } from "rambdax";
6
6
  import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
7
7
  import { AnimatedStage, Layer } from "../konva.js";
@@ -125,9 +125,15 @@ export const Diagram = /* @__PURE__ */ forwardRef(
125
125
  useImperativeHandle(
126
126
  ref,
127
127
  () => ({
128
- stage: () => nonNullable(stageRef.current),
129
- diagramView: () => refs.current.diagram,
130
- container: () => nonNullable(stageRef.current?.container()),
128
+ get stage() {
129
+ return nonNullable(stageRef.current, "not mounted");
130
+ },
131
+ get diagramView() {
132
+ return refs.current.diagram;
133
+ },
134
+ get container() {
135
+ return nonNullable(stageRef.current?.container(), "not mounted");
136
+ },
131
137
  resetStageZoom: (_immediate) => {
132
138
  refs.current.resetStageZoom(_immediate);
133
139
  },
@@ -139,7 +145,7 @@ export const Diagram = /* @__PURE__ */ forwardRef(
139
145
  }),
140
146
  centerAndFit: () => refs.current.centerAndFit()
141
147
  }),
142
- [refs, stageRef]
148
+ [refs, id, stageRef]
143
149
  );
144
150
  useUpdateEffect(() => {
145
151
  refs.current.centerAndFit(80, 650);
@@ -186,20 +192,20 @@ export const Diagram = /* @__PURE__ */ forwardRef(
186
192
  onPinch: ({ memo, first, last, origin: [ox, oy], movement: [ms], offset: [scale] }) => {
187
193
  if (first) {
188
194
  const stage = nonNullable(stageRef.current);
189
- const { width: width2, height: height2, x: x2, y: y2 } = stage.container().getBoundingClientRect();
190
- const tx = ox - (x2 + width2 / 2);
191
- const ty = oy - (y2 + height2 / 2);
195
+ const { x: x2, y: y2 } = stage.getAbsolutePosition();
196
+ const tx = Math.round(ox - x2);
197
+ const ty = Math.round(oy - y2);
192
198
  memo = [stage.x(), stage.y(), tx, ty];
193
199
  }
194
- const x = memo[0] - (ms - 1) * memo[2];
195
- const y = memo[1] - (ms - 1) * memo[3];
200
+ const x = Math.round(memo[0] - (ms - 1) * memo[2]);
201
+ const y = Math.round(memo[1] - (ms - 1) * memo[3]);
196
202
  stageSpringApi.start({
197
203
  to: {
198
204
  x,
199
205
  y,
200
206
  scale
201
207
  },
202
- immediate: immediate || !last
208
+ immediate: immediate || !last || !first
203
209
  });
204
210
  return memo;
205
211
  }
@@ -216,9 +222,12 @@ export const Diagram = /* @__PURE__ */ forwardRef(
216
222
  }
217
223
  },
218
224
  pinch: {
225
+ pointer: {
226
+ touch: true
227
+ },
219
228
  enabled: zoomable,
220
- modifierKey: null,
221
229
  scaleBounds: { min: 0.2, max: 1.15 },
230
+ rubberband: 0.05,
222
231
  pinchOnWheel: true
223
232
  }
224
233
  }
@@ -127,7 +127,7 @@ export function Nodes({ animate, theme, diagram, onNodeClick }) {
127
127
  key
128
128
  ));
129
129
  }
130
- const NodeSnape = ({
130
+ function NodeSnape({
131
131
  animate,
132
132
  node,
133
133
  ctrl,
@@ -135,7 +135,7 @@ const NodeSnape = ({
135
135
  isHovered,
136
136
  expired,
137
137
  onNodeClick
138
- }) => {
138
+ }) {
139
139
  const setHoveredNode = useSetHoveredNode();
140
140
  const Shape = isCompound(node) ? CompoundShape : nodeShape(node);
141
141
  const springs = ctrl.springs;
@@ -173,4 +173,4 @@ const NodeSnape = ({
173
173
  children: /* @__PURE__ */ jsx(Shape, { node, theme, springs, isHovered })
174
174
  }
175
175
  ) });
176
- };
176
+ }
@@ -88,4 +88,3 @@ export const ExternalLink = ({
88
88
  )
89
89
  );
90
90
  };
91
- export {};
@@ -1,12 +1,12 @@
1
- export const mousePointer = (e) => {
1
+ export function mousePointer(e) {
2
2
  const container = e.target.getStage()?.container();
3
3
  if (container) {
4
4
  container.style.cursor = "pointer";
5
5
  }
6
- };
7
- export const mouseDefault = (e) => {
6
+ }
7
+ export function mouseDefault(e) {
8
8
  const container = e.target.getStage()?.container();
9
9
  if (container) {
10
10
  container.style.cursor = "auto";
11
11
  }
12
- };
12
+ }
@@ -2,16 +2,16 @@ import { useAtom, useAtomValue, useSetAtom } from "jotai";
2
2
  import { selectAtom } from "jotai/utils";
3
3
  import { hoveredEdgeAtom, hoveredEdgeIdAtom, hoveredNodeAtom, hoveredNodeIdAtom } from "./atoms.js";
4
4
  import { useCallback } from "react";
5
- export const useHoveredNode = () => {
5
+ export function useHoveredNode() {
6
6
  return useAtom(hoveredNodeAtom);
7
- };
8
- export const useHoveredNodeId = () => {
7
+ }
8
+ export function useHoveredNodeId() {
9
9
  return useAtomValue(hoveredNodeIdAtom);
10
- };
11
- export const useSetHoveredNode = () => {
10
+ }
11
+ export function useSetHoveredNode() {
12
12
  return useSetAtom(hoveredNodeAtom);
13
- };
14
- export const useGetNodeState = (nodeId) => {
13
+ }
14
+ export function useGetNodeState(nodeId) {
15
15
  const isHovered = useAtomValue(
16
16
  selectAtom(
17
17
  hoveredNodeAtom,
@@ -19,10 +19,10 @@ export const useGetNodeState = (nodeId) => {
19
19
  )
20
20
  );
21
21
  return { isHovered };
22
- };
23
- export const useHoveredEdgeId = () => {
22
+ }
23
+ export function useHoveredEdgeId() {
24
24
  return useAtomValue(hoveredEdgeIdAtom);
25
- };
26
- export const useSetHoveredEdge = () => {
25
+ }
26
+ export function useSetHoveredEdge() {
27
27
  return useSetAtom(hoveredEdgeAtom);
28
- };
28
+ }
@@ -1,7 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { Provider, createStore } from "jotai";
3
3
  import { useState } from "react";
4
- export const DiagramStateProvider = ({ children }) => {
4
+ export function DiagramStateProvider({ children }) {
5
5
  const [store] = useState(() => createStore());
6
6
  return /* @__PURE__ */ jsx(Provider, { store, children });
7
- };
7
+ }
@@ -1,4 +1,4 @@
1
1
  export * from "./useDarkMode.js";
2
- export * from "./useDiagramRef.js";
2
+ export * from "./useDiagramApi.js";
3
3
  export * from "./useImageLoader.js";
4
4
  export * from "./useViewIdFromHash.js";
@@ -0,0 +1,27 @@
1
+ import { useRef, useMemo } from "react";
2
+ import { nonNullable } from "@likec4/core";
3
+ export function useDiagramApi() {
4
+ const ref = useRef(null);
5
+ return useMemo(
6
+ () => [
7
+ ref,
8
+ {
9
+ get stage() {
10
+ return nonNullable(ref.current, "not mounted, use ref").stage;
11
+ },
12
+ get diagramView() {
13
+ return nonNullable(ref.current, "not mounted, use ref").diagramView;
14
+ },
15
+ get container() {
16
+ return nonNullable(ref.current, "not mounted, use ref").container;
17
+ },
18
+ resetStageZoom: (_immediate) => {
19
+ nonNullable(ref.current, "not mounted, use ref").resetStageZoom(_immediate);
20
+ },
21
+ centerOnNode: (node) => nonNullable(ref.current, "not mounted, use ref").centerOnNode(node),
22
+ centerAndFit: () => nonNullable(ref.current, "not mounted, use ref").centerAndFit()
23
+ }
24
+ ],
25
+ [ref]
26
+ );
27
+ }
@@ -2,12 +2,12 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import ReactDOM from "react-dom/client";
4
4
  import { Group } from "./konva.js";
5
- const needForceStyle = (el) => {
5
+ function needForceStyle(el) {
6
6
  const pos = window.getComputedStyle(el).position;
7
7
  const ok = pos === "absolute" || pos === "relative";
8
8
  return !ok;
9
- };
10
- export const KonvaHtml = ({ children, groupProps, divProps, transform, transformFunc }) => {
9
+ }
10
+ export function KonvaHtml({ children, groupProps, divProps, transform, transformFunc }) {
11
11
  const groupRef = useRef(null);
12
12
  const [div] = useState(() => document.createElement("div"));
13
13
  const root = useMemo(() => ReactDOM.createRoot(div), [div]);
@@ -71,4 +71,4 @@ export const KonvaHtml = ({ children, groupProps, divProps, transform, transform
71
71
  };
72
72
  }, []);
73
73
  return /* @__PURE__ */ jsx(Group, { ref: groupRef, ...groupProps });
74
- };
74
+ }
@@ -1,7 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useLayoutEffect, useRef } from "react";
3
3
  import { Group } from "./konva.js";
4
- export const Portal = ({ selector, enabled, children }) => {
4
+ export function Portal({ selector, enabled, children }) {
5
5
  const outer = useRef(null);
6
6
  const inner = useRef(null);
7
7
  const safeRef = useRef();
@@ -31,4 +31,4 @@ export const Portal = ({ selector, enabled, children }) => {
31
31
  };
32
32
  }, []);
33
33
  return /* @__PURE__ */ jsx(Group, { ref: outer, children: /* @__PURE__ */ jsx(Group, { ref: inner, children }) });
34
- };
34
+ }
@@ -0,0 +1,26 @@
1
+ import { Provider } from 'jotai';
2
+ // import { useAtomsDevtools } from 'jotai-devtools'
3
+ import { Fragment } from 'react';
4
+ import { Sidebar } from './components';
5
+ import { ExportPage, IndexPage, ViewPage } from './pages';
6
+ import { useRoute } from './router';
7
+ const Routes = () => {
8
+ const r = useRoute();
9
+ return (<>
10
+ {r.route === 'index' && <IndexPage key='index'/>}
11
+ {r.route === 'view' && <ViewPage key='view' viewId={r.params.viewId} showUI={r.showUI}/>}
12
+ {r.route === 'export' && (<ExportPage key='export' viewId={r.params.viewId} padding={r.params.padding}/>)}
13
+ {r.showUI && (<Fragment key='ui'>
14
+ <Sidebar />
15
+ </Fragment>)}
16
+ </>);
17
+ };
18
+ // const AtomsDevTools = import.meta.env.DEV ? ({ children }: PropsWithChildren) => {
19
+ // useAtomsDevtools('demo')
20
+ // return <>{children}</>
21
+ // } : Fragment
22
+ export default function App() {
23
+ return (<Provider>
24
+ <Routes />
25
+ </Provider>);
26
+ }
@@ -1,15 +1,13 @@
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
-
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';
5
4
  export const DiagramNotFound = () => {
6
- return (
7
- <Flex position='fixed' inset='0' align='center' justify='center'>
5
+ return (<Flex position='fixed' inset='0' align='center' justify='center'>
8
6
  <Card color='red' size='3'>
9
7
  <Flex gap='4' direction='row' align='start'>
10
8
  <Box grow='0' shrink='0' pt='1'>
11
9
  <IconButton variant='ghost' color='amber'>
12
- <ExclamationTriangleIcon width={50} height={50} />
10
+ <ExclamationTriangleIcon width={50} height={50}/>
13
11
  </IconButton>
14
12
  </Box>
15
13
  <Flex gap='3' direction='column'>
@@ -25,6 +23,5 @@ export const DiagramNotFound = () => {
25
23
  </Flex>
26
24
  </Flex>
27
25
  </Card>
28
- </Flex>
29
- )
30
- }
26
+ </Flex>);
27
+ };
@@ -0,0 +1,12 @@
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
+ export const ThemePanelToggle = () => {
5
+ const [isOpened, toggle] = useToggle(false, true);
6
+ return (<>
7
+ <IconButton color='gray' variant={isOpened ? 'solid' : 'soft'} onClick={toggle} size={'2'}>
8
+ <MoonIcon width={16} height={16}/>
9
+ </IconButton>
10
+ {isOpened && <ThemePanel style={{ top: 50 }}/>}
11
+ </>);
12
+ };
@@ -0,0 +1,4 @@
1
+ export { Sidebar } from './sidebar/Sidebar';
2
+ export { ViewActionsToolbar } from './view-page/ViewActionsToolbar';
3
+ export { ThemePanelToggle } from './ThemePanelToggle';
4
+ export { DiagramNotFound } from './DiagramNotFound';
@@ -0,0 +1,38 @@
1
+ import { DashboardIcon, TriangleRightIcon } from '@radix-ui/react-icons';
2
+ import { Box, Flex, Text } from '@radix-ui/themes';
3
+ import TreeView 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
+ function inTree(id, data) {
9
+ return data.some(d => d.id === id);
10
+ }
11
+ export function DiagramsTree() {
12
+ const data = useDiagramsTree();
13
+ const r = useRoute();
14
+ const viewId = r.route === 'view' || r.route === 'export' ? r.params.viewId : null;
15
+ const selectedId = viewId && inTree(viewId, data) ? [viewId] : [];
16
+ return (<Box className={styles.treeview}>
17
+ <TreeView data={data} propagateSelect propagateSelectUpwards selectedIds={selectedId} onNodeSelect={({ element, isBranch }) => {
18
+ if (isBranch) {
19
+ return;
20
+ }
21
+ $pages.view.open('' + element.id);
22
+ }} nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, handleExpand, handleSelect }) => {
23
+ return (<Flex {...getNodeProps({ onClick: isBranch ? handleExpand : handleSelect })} align={'center'} gap={isBranch ? '1' : '2'}>
24
+ {isBranch && (<Box style={{ lineHeight: '15px' }}>
25
+ <TriangleRightIcon width={15} height={15} className={cn('transition duration-200 ease-out', isExpanded && 'rotate-90')}/>
26
+ </Box>)}
27
+ {!isBranch && (<Box style={{ lineHeight: '14px' }} width={'min-content'}>
28
+ <DashboardIcon width={14} height={14}/>
29
+ </Box>)}
30
+ <Box asChild grow={'1'}>
31
+ <Text as='div' size={'2'} weight={isBranch ? 'bold' : undefined} className='truncate'>
32
+ {(isBranch ? '🗂️ ' : '') + element.name}
33
+ </Text>
34
+ </Box>
35
+ </Flex>);
36
+ }}/>
37
+ </Box>);
38
+ }
@@ -0,0 +1,35 @@
1
+ import { Box, Button, Flex, IconButton, ScrollArea, Separator } from '@radix-ui/themes';
2
+ import { useClickOutside, useToggle } from '@react-hookz/web/esm';
3
+ import { HamburgerMenuIcon, ArrowLeftIcon } from '@radix-ui/react-icons';
4
+ import { useRef } from 'react';
5
+ import { cn } from '~/utils';
6
+ import { DiagramsTree } from './DiagramsTree';
7
+ import styles from './styles.module.css';
8
+ import { $pages } from '../../router';
9
+ export const Sidebar = () => {
10
+ const ref = useRef(null);
11
+ const [isOpened, toggle] = useToggle(false, true);
12
+ useClickOutside(ref, () => isOpened && toggle());
13
+ return (<>
14
+ <Flex position='fixed' left='0' p={'2'} className={cn(styles.trigger, 'inset-y-0 cursor-pointer items-start', isOpened && 'display-none')} onClick={toggle}>
15
+ <IconButton size='2' color='gray' variant='soft'>
16
+ <HamburgerMenuIcon width={22} height={22}/>
17
+ </IconButton>
18
+ </Flex>
19
+ <Flex ref={ref} className={styles.navsidebar} position='fixed' left='0' top='0' bottom='0' data-opened={isOpened}>
20
+ <ScrollArea scrollbars='vertical' type='scroll'>
21
+ <Box p='4' pl='2'>
22
+ <Button variant='ghost' ml='2' mt='1' size='1' color='gray' onClick={_ => {
23
+ toggle();
24
+ $pages.index.open();
25
+ }}>
26
+ <ArrowLeftIcon />
27
+ Back to dashboard
28
+ </Button>
29
+ <Separator orientation='horizontal' my='3' size={'4'}/>
30
+ <DiagramsTree />
31
+ </Box>
32
+ </ScrollArea>
33
+ </Flex>
34
+ </>);
35
+ };
@@ -52,34 +52,3 @@
52
52
  transform: translateX(0);
53
53
  }
54
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
- }
@@ -1,57 +1,32 @@
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 = `
1
+ import { ExclamationTriangleIcon, InfoCircledIcon, OpenInNewWindowIcon } from '@radix-ui/react-icons';
2
+ import { Box, Button, Callout, Code, Dialog, Flex, Link, Select, Tabs, Text } from '@radix-ui/themes';
3
+ import { useState } from 'react';
4
+ const embedCode = (diagram, theme) => {
5
+ const padding = 20;
6
+ const params = new URLSearchParams();
7
+ params.set('embed', diagram.id);
8
+ params.set('padding', `${padding}`);
9
+ if (theme !== 'system') {
10
+ params.set('theme', theme);
11
+ }
12
+ const width = diagram.width + padding * 2;
13
+ const height = diagram.height + padding * 2;
14
+ const url = new URL(window.location.href);
15
+ url.search = params.toString();
16
+ const iframe = `<iframe src="${url.href}" width="100%" height="100%" style="border:0;background:transparent;"></iframe>`;
17
+ const code = `
38
18
  <div style="aspect-ratio:${width}/${height};max-width:${width}px;width:100%;height:auto;padding:0;margin-left:auto;margin-right:auto">
39
19
  ${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 }}>
20
+ </div>`.trim();
21
+ return {
22
+ code,
23
+ href: url.href
24
+ };
25
+ };
26
+ export const ShareDialog = ({ diagram }) => {
27
+ const [theme, setTheme] = useState('system');
28
+ const { code, href } = embedCode(diagram, theme);
29
+ return (<Dialog.Content size={'2'} style={{ maxWidth: 700, minWidth: 300 }}>
55
30
  <Tabs.Root defaultValue='embed'>
56
31
  <Tabs.List>
57
32
  <Tabs.Trigger value='embed'>Embed</Tabs.Trigger>
@@ -62,8 +37,7 @@ export const ShareDialog = ({ diagram }: { diagram: DiagramView }) => {
62
37
  <Box px='1' py='4'>
63
38
  <Tabs.Content value='embed'>
64
39
  <Flex direction='column' gap='4'>
65
- {code.includes('http://localhost') && (
66
- <Callout.Root size='1' color='amber'>
40
+ {code.includes('http://localhost') && (<Callout.Root size='1' color='amber'>
67
41
  <Callout.Icon>
68
42
  <ExclamationTriangleIcon />
69
43
  </Callout.Icon>
@@ -71,8 +45,7 @@ export const ShareDialog = ({ diagram }: { diagram: DiagramView }) => {
71
45
  This is a local URL. You need to build and deploy your diagrams to a public URL
72
46
  to make it available for embedding.
73
47
  </Callout.Text>
74
- </Callout.Root>
75
- )}
48
+ </Callout.Root>)}
76
49
  <label>
77
50
  <Flex direction='row' justify='between'>
78
51
  <Text as='div' size='2' weight='medium'>
@@ -82,18 +55,12 @@ export const ShareDialog = ({ diagram }: { diagram: DiagramView }) => {
82
55
  <Link size='2' href={href} target='_blank'>
83
56
  <Text as='span'>Open in new tab</Text>
84
57
  <Text as='span'>
85
- <OpenInNewWindowIcon width={12} height={12} />
58
+ <OpenInNewWindowIcon width={12} height={12}/>
86
59
  </Text>
87
60
  </Link>
88
61
  </Flex>
89
62
  </Flex>
90
- <Box
91
- asChild
92
- display={'block'}
93
- my='2'
94
- p='2'
95
- className='whitespace-pre-wrap overflow-scroll select-all'
96
- >
63
+ <Box asChild display={'block'} my='2' p='2' className='whitespace-pre-wrap overflow-scroll select-all'>
97
64
  <Code variant='soft' autoFocus>
98
65
  {code}
99
66
  </Code>
@@ -107,7 +74,7 @@ export const ShareDialog = ({ diagram }: { diagram: DiagramView }) => {
107
74
  Theme
108
75
  </Text>
109
76
  <Select.Root size='2' defaultValue={theme} onValueChange={v => setTheme(v)}>
110
- <Select.Trigger variant='soft' />
77
+ <Select.Trigger variant='soft'/>
111
78
  <Select.Content>
112
79
  <Select.Item value='system'>Same as system</Select.Item>
113
80
  <Select.Item value='light'>Light</Select.Item>
@@ -143,6 +110,5 @@ export const ShareDialog = ({ diagram }: { diagram: DiagramView }) => {
143
110
  </Button>
144
111
  </Dialog.Close>
145
112
  </Flex>
146
- </Dialog.Content>
147
- )
148
- }
113
+ </Dialog.Content>);
114
+ };
@@ -0,0 +1,60 @@
1
+ import { CaretDownIcon, Share1Icon as ShareIcon } from '@radix-ui/react-icons';
2
+ import { Button, Dialog, DropdownMenu, Flex, Text } from '@radix-ui/themes';
3
+ import { ThemePanelToggle } from '../ThemePanelToggle';
4
+ import { ShareDialog } from './ShareDialog';
5
+ const ExportMenu = ({ diagramApi, children }) => (<DropdownMenu.Root>
6
+ <DropdownMenu.Trigger>{children}</DropdownMenu.Trigger>
7
+ <DropdownMenu.Content>
8
+ <DropdownMenu.Label>
9
+ <Text weight='medium'>Current view</Text>
10
+ </DropdownMenu.Label>
11
+ <DropdownMenu.Group>
12
+ <DropdownMenu.Item onClick={_ => {
13
+ // const { boundingBox } = diagramApi.diagramView()
14
+ console.log('Serialized: ', diagramApi.stage.toObject());
15
+ // diagramApi.stage().toBlob({
16
+ // ...boundingBox,
17
+ // callback(blob) {
18
+ // const url = URL.createObjectURL(blob)
19
+ // window.open(url)
20
+ // // const a = document.createElement('a')
21
+ // // a.href = url
22
+ // // a.download = 'diagram.png'
23
+ // // a.click()
24
+ // URL.revokeObjectURL(url)
25
+ // },
26
+ // })
27
+ }}>
28
+ Export as PNG
29
+ </DropdownMenu.Item>
30
+ <DropdownMenu.Item>Export as SVG</DropdownMenu.Item>
31
+ </DropdownMenu.Group>
32
+ <DropdownMenu.Separator />
33
+ <DropdownMenu.Label>
34
+ <Text weight='medium'>All views</Text>
35
+ </DropdownMenu.Label>
36
+ <DropdownMenu.Group>
37
+ <DropdownMenu.Item>Download as ZIP</DropdownMenu.Item>
38
+ </DropdownMenu.Group>
39
+ </DropdownMenu.Content>
40
+ </DropdownMenu.Root>);
41
+ export const ViewActionsToolbar = ({ diagramApi, diagram }) => {
42
+ return (<Flex position='fixed' top='0' right='0' p='2' gap={'3'} justify='end'>
43
+ <Dialog.Root>
44
+ <Dialog.Trigger>
45
+ <Button variant='solid'>
46
+ <ShareIcon />
47
+ <Text>Share</Text>
48
+ </Button>
49
+ </Dialog.Trigger>
50
+ <ShareDialog diagram={diagram}/>
51
+ </Dialog.Root>
52
+ <ExportMenu diagramApi={diagramApi}>
53
+ <Button variant='soft' color='gray'>
54
+ <Text>Export</Text>
55
+ <CaretDownIcon />
56
+ </Button>
57
+ </ExportMenu>
58
+ <ThemePanelToggle />
59
+ </Flex>);
60
+ };