create graph embed

This commit is contained in:
AykutSarac 2022-07-23 13:20:41 +03:00
parent d1529c50d8
commit fa74a3a997
6 changed files with 208 additions and 81 deletions

View File

@ -29,6 +29,7 @@ const StyledButton = styled.button<{
padding: 8px 16px;
min-width: 60px;
width: ${({ block }) => (block ? "100%" : "fit-content")};
height: 40px;
:disabled {
cursor: not-allowed;
@ -45,6 +46,8 @@ const StyledButtonContent = styled.div`
justify-content: center;
align-items: center;
gap: 8px;
white-space: nowrap;
text-overflow: ellipsis;
`;
export const Button: React.FC<ButtonProps> = ({

View File

@ -1,17 +1,60 @@
import React from "react";
import { Canvas, EdgeData, ElkRoot, NodeData } from "reaflow";
import {
ReactZoomPanPinchRef,
TransformComponent,
TransformWrapper,
} from "react-zoom-pan-pinch";
import {
Canvas,
CanvasContainerProps,
EdgeData,
ElkRoot,
NodeData,
} from "reaflow";
import { CustomNode } from "src/components/CustomNode";
import { getEdgeNodes } from "src/containers/Editor/LiveEditor/helpers";
import useConfig from "src/hooks/store/useConfig";
import styled from "styled-components";
import shallow from "zustand/shallow";
export const Graph: React.FC = () => {
const json = useConfig((state) => state.json);
interface GraphProps {
json: string;
isWidget?: boolean;
}
const wheelOptions = {
step: 0.05,
};
const StyledEditorWrapper = styled.div<{ isWidget: boolean }>`
position: absolute;
width: 100%;
height: ${({ isWidget }) => (isWidget ? "100vh" : "calc(100vh - 36px)")};
:active {
cursor: move;
}
rect {
fill: ${({ theme }) => theme.BACKGROUND_NODE};
}
`;
export const Graph: React.FC<GraphProps & CanvasContainerProps> = ({
json,
isWidget = false,
...props
}) => {
const updateSetting = useConfig((state) => state.updateSetting);
const [expand, layout] = useConfig(
(state) => [state.settings.expand, state.settings.layout],
shallow
);
const onInit = (ref: ReactZoomPanPinchRef) => {
updateSetting("zoomPanPinch", ref);
};
const [nodes, setNodes] = React.useState<NodeData[]>([]);
const [edges, setEdges] = React.useState<EdgeData[]>([]);
const [size, setSize] = React.useState({
@ -37,18 +80,38 @@ export const Graph: React.FC = () => {
};
return (
<Canvas
nodes={nodes}
edges={edges}
maxWidth={size.width}
maxHeight={size.height}
direction={layout}
key={layout}
onCanvasClick={onCanvasClick}
onLayoutChange={onLayoutChange}
node={CustomNode}
zoomable={false}
readonly
/>
<StyledEditorWrapper isWidget={isWidget}>
<TransformWrapper
maxScale={1.8}
minScale={0.4}
initialScale={0.7}
wheel={wheelOptions}
onInit={onInit}
centerOnInit
>
<TransformComponent
wrapperStyle={{
width: "100%",
height: "100%",
overflow: "hidden",
}}
>
<Canvas
nodes={nodes}
edges={edges}
maxWidth={size.width}
maxHeight={size.height}
direction={layout}
key={layout}
onCanvasClick={onCanvasClick}
onLayoutChange={onLayoutChange}
node={CustomNode}
zoomable={false}
readonly
{...props}
/>
</TransformComponent>
</TransformWrapper>
</StyledEditorWrapper>
);
};

View File

@ -8,10 +8,10 @@ const StyledInput = styled.input`
border: none;
border-radius: 4px;
line-height: 32px;
padding: 12px 8px;
padding: 10px;
width: 100%;
margin-bottom: 10px;
height: 30px;
height: 40px;
`;
type InputProps = React.InputHTMLAttributes<HTMLInputElement>;

View File

@ -1,11 +1,5 @@
import React from "react";
import styled from "styled-components";
import {
TransformWrapper,
TransformComponent,
ReactZoomPanPinchRef,
} from "react-zoom-pan-pinch";
import { Tools } from "src/containers/Editor/Tools";
import { Graph } from "src/components/Graph";
import useConfig from "src/hooks/store/useConfig";
@ -14,53 +8,13 @@ const StyledLiveEditor = styled.div`
position: relative;
`;
const StyledEditorWrapper = styled.div`
position: absolute;
width: 100%;
height: calc(100vh - 36px);
:active {
cursor: move;
}
rect {
fill: ${({ theme }) => theme.BACKGROUND_NODE};
}
`;
const wheelOptions = {
step: 0.05,
};
const LiveEditor: React.FC = () => {
const updateSetting = useConfig((state) => state.updateSetting);
const onInit = (ref: ReactZoomPanPinchRef) => {
updateSetting("zoomPanPinch", ref);
};
const json = useConfig((state) => state.json);
return (
<StyledLiveEditor>
<Tools />
<StyledEditorWrapper>
<TransformWrapper
maxScale={1.8}
minScale={0.4}
initialScale={0.9}
wheel={wheelOptions}
onInit={onInit}
>
<TransformComponent
wrapperStyle={{
width: "100%",
height: "100%",
overflow: "hidden",
}}
>
<Graph />
</TransformComponent>
</TransformWrapper>
</StyledEditorWrapper>
<Graph json={json} />
</StyledLiveEditor>
);
};

View File

@ -20,22 +20,46 @@ const StyledErrorWrapper = styled.div`
font-weight: 600;
`;
const StyledFlex = styled.div`
display: flex;
gap: 12px;
`;
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
padding: 12px 0;
border-top: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
font-size: 12px;
line-height: 16px;
font-weight: 600;
text-transform: uppercase;
color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
&:first-of-type {
padding-top: 0;
border: none;
}
`;
export const ShareModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const json = useConfig((state) => state.json);
const [url, setURL] = React.useState("");
const [encodedJson, setEncodedJson] = React.useState("");
const [_, copy] = useCopyToClipboard();
const embedText = `<iframe src="https://jsonvisio.com/widget/${encodedJson}" width="512" height="384"></iframe>`;
const shareURL = `https://jsonvisio.com/editor?json=${encodedJson}`;
React.useEffect(() => {
const jsonEncode = compress(JSON.parse(json));
const jsonString = JSON.stringify(jsonEncode);
setURL(
`https://jsonvisio.com/editor?json=${encodeURIComponent(jsonString)}`
);
setEncodedJson(encodeURIComponent(jsonString));
}, [json]);
const handleShare = () => {
copy(url);
const handleShare = (value: string) => {
copy(value);
toast.success(`Link copied to clipboard.`);
setVisible(false);
};
@ -44,7 +68,7 @@ export const ShareModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
<Modal visible={visible} setVisible={setVisible}>
<Modal.Header>Create a Share Link</Modal.Header>
<Modal.Content>
{url.length > 5000 ? (
{encodedJson.length > 5000 ? (
<StyledErrorWrapper>
<BiErrorAlt size={60} />
<StyledWarning>
@ -53,16 +77,35 @@ export const ShareModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
</StyledWarning>
</StyledErrorWrapper>
) : (
<Input value={url} type="url" readOnly />
<>
<StyledContainer>
Share Link
<StyledFlex>
<Input value={shareURL} type="url" readOnly />
<Button
status="SECONDARY"
onClick={() => handleShare(shareURL)}
>
Copy
</Button>
</StyledFlex>
</StyledContainer>
<StyledContainer>
Embed into your website
<StyledFlex>
<Input value={embedText} type="url" readOnly />
<Button
status="SECONDARY"
onClick={() => handleShare(embedText)}
>
Copy
</Button>
</StyledFlex>
</StyledContainer>
</>
)}
</Modal.Content>
<Modal.Controls setVisible={setVisible}>
{url.length < 5000 && (
<Button status="SECONDARY" onClick={handleShare}>
Copy
</Button>
)}
</Modal.Controls>
<Modal.Controls setVisible={setVisible}></Modal.Controls>
</Modal>
);
};

View File

@ -0,0 +1,64 @@
import { decompress } from "compress-json";
import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import React from "react";
import { isValidJson } from "src/utils/isValidJson";
import styled from "styled-components";
const Graph = dynamic<any>(
() => import("src/components/Graph").then((c) => c.Graph),
{ ssr: false }
);
const StyledAttribute = styled.a`
position: fixed;
bottom: 0;
right: 0;
background: rgba(255, 255, 255, 0.3);
padding: 4px 8px;
font-size: 14px;
font-weight: 500;
`;
function inIframe() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
const Widget = () => {
const { query, push } = useRouter();
const [json, setJson] = React.useState("");
React.useEffect(() => {
const isJsonValid =
typeof query.json === "string" &&
isValidJson(decodeURIComponent(query.json));
if (isJsonValid) {
const jsonDecoded = decompress(JSON.parse(isJsonValid));
const jsonString = JSON.stringify(jsonDecoded);
setJson(jsonString);
}
if (!inIframe()) push("/");
}, [query.json]);
return (
<div>
<Graph json={json} isWidget />
<StyledAttribute
href={`https://jsonvisio.com/editor?json=${query.json}`}
target="_blank"
rel="noreferrer"
>
jsonvisio.com
</StyledAttribute>
</div>
);
};
export default Widget;