diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index a3d6e4e..1de571f 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -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 = ({ diff --git a/src/components/Graph/index.tsx b/src/components/Graph/index.tsx index 63ff97c..bed059f 100644 --- a/src/components/Graph/index.tsx +++ b/src/components/Graph/index.tsx @@ -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 = ({ + 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([]); const [edges, setEdges] = React.useState([]); const [size, setSize] = React.useState({ @@ -37,18 +80,38 @@ export const Graph: React.FC = () => { }; return ( - + + + + + + + ); }; diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index c9c75fd..b3027c3 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -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; diff --git a/src/containers/Editor/LiveEditor/index.tsx b/src/containers/Editor/LiveEditor/index.tsx index 2167b4b..d072345 100644 --- a/src/containers/Editor/LiveEditor/index.tsx +++ b/src/containers/Editor/LiveEditor/index.tsx @@ -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 ( - - - - - - - + ); }; diff --git a/src/containers/Modals/ShareModal/index.tsx b/src/containers/Modals/ShareModal/index.tsx index dd72299..a3d35d1 100644 --- a/src/containers/Modals/ShareModal/index.tsx +++ b/src/containers/Modals/ShareModal/index.tsx @@ -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 = ({ visible, setVisible }) => { const json = useConfig((state) => state.json); - const [url, setURL] = React.useState(""); + const [encodedJson, setEncodedJson] = React.useState(""); const [_, copy] = useCopyToClipboard(); + const embedText = ``; + 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 = ({ visible, setVisible }) => { Create a Share Link - {url.length > 5000 ? ( + {encodedJson.length > 5000 ? ( @@ -53,16 +77,35 @@ export const ShareModal: React.FC = ({ visible, setVisible }) => { ) : ( - + <> + + Share Link + + + + + + + Embed into your website + + + + + + )} - - {url.length < 5000 && ( - - )} - + ); }; diff --git a/src/pages/widget/[json].tsx b/src/pages/widget/[json].tsx new file mode 100644 index 0000000..7ddf2f2 --- /dev/null +++ b/src/pages/widget/[json].tsx @@ -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( + () => 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 ( +
+ + + jsonvisio.com + +
+ ); +}; + +export default Widget;