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.
- package/dist/@likec4/diagrams/diagram/Diagram.js +21 -12
- package/dist/@likec4/diagrams/diagram/Nodes.js +3 -3
- package/dist/@likec4/diagrams/diagram/icons/ExternalLink.js +0 -1
- package/dist/@likec4/diagrams/diagram/shapes/utils.js +4 -4
- package/dist/@likec4/diagrams/diagram/state/hooks.js +12 -12
- package/dist/@likec4/diagrams/diagram/state/provider.js +2 -2
- package/dist/@likec4/diagrams/hooks/index.js +1 -1
- package/dist/@likec4/diagrams/hooks/useDiagramApi.js +27 -0
- package/dist/@likec4/diagrams/konva-html.js +4 -4
- package/dist/@likec4/diagrams/konva-portal.js +2 -2
- package/dist/__app__/src/App.jsx +26 -0
- package/dist/__app__/src/components/{DiagramNotFound.tsx → DiagramNotFound.jsx} +7 -10
- package/dist/__app__/src/components/ThemePanelToggle.jsx +12 -0
- package/dist/__app__/src/components/index.js +4 -0
- package/dist/__app__/src/components/sidebar/DiagramsTree.jsx +38 -0
- package/dist/__app__/src/components/sidebar/Sidebar.jsx +35 -0
- package/dist/__app__/src/components/sidebar/styles.module.css +0 -31
- package/dist/__app__/src/components/view-page/{ShareDialog.tsx → ShareDialog.jsx} +34 -68
- package/dist/__app__/src/components/view-page/ViewActionsToolbar.jsx +60 -0
- package/dist/__app__/src/data/atoms.js +83 -0
- package/dist/__app__/src/data/hooks.js +11 -0
- package/dist/__app__/src/data/index.js +1 -0
- package/dist/__app__/src/data/sidebar-diagram-tree.js +38 -0
- package/dist/__app__/src/main.jsx +9 -0
- package/dist/__app__/src/pages/export.page.jsx +19 -0
- package/dist/__app__/src/pages/index.js +3 -0
- package/dist/__app__/src/pages/index.module.css +1 -1
- package/dist/__app__/src/pages/index.page.jsx +57 -0
- package/dist/__app__/src/pages/view.page.jsx +35 -0
- package/dist/__app__/src/router.js +60 -0
- package/dist/__app__/src/utils/index.js +1 -0
- package/dist/__app__/src/utils/utils.js +5 -0
- package/dist/__app__/tsconfig.json +16 -8
- package/dist/cli/index.js +266 -57675
- package/package.json +31 -15
- package/dist/@likec4/diagrams/hooks/useDiagramRef.js +0 -21
- package/dist/__app__/src/App.tsx +0 -37
- package/dist/__app__/src/components/ThemePanelToggle.tsx +0 -15
- package/dist/__app__/src/components/index.ts +0 -4
- package/dist/__app__/src/components/sidebar/DiagramsTree.tsx +0 -77
- package/dist/__app__/src/components/sidebar/Sidebar.tsx +0 -67
- package/dist/__app__/src/components/view-page/ViewActionsToolbar.tsx +0 -76
- package/dist/__app__/src/data/atoms.ts +0 -108
- package/dist/__app__/src/data/hooks.ts +0 -16
- package/dist/__app__/src/data/index.ts +0 -1
- package/dist/__app__/src/data/likec4.d.ts +0 -5
- package/dist/__app__/src/data/sidebar-diagram-tree.ts +0 -52
- package/dist/__app__/src/main.tsx +0 -12
- package/dist/__app__/src/pages/export.page.tsx +0 -37
- package/dist/__app__/src/pages/index.page.tsx +0 -103
- package/dist/__app__/src/pages/index.ts +0 -3
- package/dist/__app__/src/pages/view.page.tsx +0 -67
- package/dist/__app__/src/router.ts +0 -67
- package/dist/__app__/src/utils/index.ts +0 -1
- 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 {
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
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 {
|
|
190
|
-
const tx = ox -
|
|
191
|
-
const ty = oy -
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export
|
|
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
|
|
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
|
|
5
|
+
export function useHoveredNode() {
|
|
6
6
|
return useAtom(hoveredNodeAtom);
|
|
7
|
-
}
|
|
8
|
-
export
|
|
7
|
+
}
|
|
8
|
+
export function useHoveredNodeId() {
|
|
9
9
|
return useAtomValue(hoveredNodeIdAtom);
|
|
10
|
-
}
|
|
11
|
-
export
|
|
10
|
+
}
|
|
11
|
+
export function useSetHoveredNode() {
|
|
12
12
|
return useSetAtom(hoveredNodeAtom);
|
|
13
|
-
}
|
|
14
|
-
export
|
|
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
|
|
22
|
+
}
|
|
23
|
+
export function useHoveredEdgeId() {
|
|
24
24
|
return useAtomValue(hoveredEdgeIdAtom);
|
|
25
|
-
}
|
|
26
|
-
export
|
|
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
|
|
4
|
+
export function DiagramStateProvider({ children }) {
|
|
5
5
|
const [store] = useState(() => createStore());
|
|
6
6
|
return /* @__PURE__ */ jsx(Provider, { store, children });
|
|
7
|
-
}
|
|
7
|
+
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,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
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
};
|