Browse Source

Merge branch 'main' into add-shortcut-in-popup

AykutSarac 2 years ago
parent
commit
11cb853a2b

+ 4 - 2
package.json

@@ -1,7 +1,7 @@
 {
   "name": "json-visio",
   "private": true,
-  "version": "v1.9.0",
+  "version": "v1.9.2",
   "homepage": "https://jsonvisio.com",
   "scripts": {
     "dev": "next dev",
@@ -15,6 +15,7 @@
     "@monaco-editor/react": "^4.4.5",
     "@sentry/nextjs": "^7.1.1",
     "allotment": "^1.14.2",
+    "compress-json": "^2.0.1",
     "next": "^12.1.5",
     "next-transpile-modules": "^9.0.0",
     "parse-json": "^6.0.2",
@@ -27,7 +28,8 @@
     "react-zoom-pan-pinch": "^2.1.3",
     "reaflow": "^5.0.4",
     "save-html-as-image": "^1.7.1",
-    "styled-components": "^5.3.5"
+    "styled-components": "^5.3.5",
+    "usehooks-ts": "^2.5.2"
   },
   "devDependencies": {
     "@testing-library/jest-dom": "^5.16.4",

+ 3 - 1
src/components/Modal/index.tsx

@@ -57,8 +57,10 @@ const Modal: React.FC<React.PropsWithChildren<ModalProps>> & ModalTypes = ({
     }
   };
 
+  if (!visible) return null;
+
   return (
-    <Styled.ModalWrapper visible={visible} onClick={onClick}>
+    <Styled.ModalWrapper onClick={onClick}>
       <Styled.ModalInnerWrapper>{children}</Styled.ModalInnerWrapper>
     </Styled.ModalWrapper>
   );

+ 4 - 3
src/components/Modal/styles.tsx

@@ -5,17 +5,17 @@ const appearAnimation = keyframes`
   to { transform: scale(1); opacity: 1; };
 `;
 
-export const ModalWrapper = styled.div<{ visible: boolean }>`
+export const ModalWrapper = styled.div`
   position: fixed;
   top: 0;
   left: 0;
   height: 100vh;
   width: 100%;
-  display: ${({ visible }) => (visible ? "flex" : "none")};
+  display: flex;
   justify-content: center;
   align-items: center;
   background: rgba(0, 0, 0, 0.85);
-  z-index: 6;
+  z-index: 36;
 
   * {
     box-sizing: border-box;
@@ -24,6 +24,7 @@ export const ModalWrapper = styled.div<{ visible: boolean }>`
 
 export const ModalInnerWrapper = styled.div`
   min-width: 440px;
+  max-width: 70%;
   width: fit-content;
   animation: ${appearAnimation} 220ms ease-in-out;
 `;

+ 19 - 10
src/components/Sidebar/index.tsx

@@ -16,6 +16,7 @@ import {
   AiOutlineTwitter,
   AiOutlineSave,
   AiOutlineFileAdd,
+  AiOutlineLink,
 } from "react-icons/ai";
 
 import { Tooltip } from "src/components/Tooltip";
@@ -24,6 +25,7 @@ import { useConfig } from "src/hocs/config";
 import { useRouter } from "next/router";
 import { ImportModal } from "src/containers/ImportModal";
 import { ClearModal } from "src/containers/ClearModal";
+import { ShareModal } from "src/containers/ShareModal";
 import { IoAlertCircleSharp } from "react-icons/io5";
 
 const StyledSidebar = styled.div`
@@ -138,6 +140,7 @@ export const Sidebar: React.FC = () => {
   const router = useRouter();
   const [uploadVisible, setUploadVisible] = React.useState(false);
   const [clearVisible, setClearVisible] = React.useState(false);
+  const [shareVisible, setShareVisible] = React.useState(false);
 
   const handleSave = () => {
     const a = document.createElement("a");
@@ -197,16 +200,6 @@ export const Sidebar: React.FC = () => {
             {settings.expand ? <CgArrowsMergeAltH /> : <CgArrowsShrinkH />}
           </StyledElement>
         </Tooltip>
-        <Tooltip title="Clear JSON">
-          <StyledElement onClick={() => setClearVisible(true)}>
-            <AiOutlineDelete />
-          </StyledElement>
-        </Tooltip>
-        <Tooltip title="Save JSON">
-          <StyledElement onClick={handleSave}>
-            <AiOutlineSave />
-          </StyledElement>
-        </Tooltip>
         <Tooltip
           title={`${
             settings.performance ? "Disable" : "Enable"
@@ -218,6 +211,21 @@ export const Sidebar: React.FC = () => {
             />
           </StyledElement>
         </Tooltip>
+        <Tooltip title="Save JSON">
+          <StyledElement onClick={handleSave}>
+            <AiOutlineSave />
+          </StyledElement>
+        </Tooltip>
+        <Tooltip title="Clear JSON">
+          <StyledElement onClick={() => setClearVisible(true)}>
+            <AiOutlineDelete />
+          </StyledElement>
+        </Tooltip>
+        <Tooltip title="Share">
+          <StyledElement onClick={() => setShareVisible(true)}>
+            <AiOutlineLink />
+          </StyledElement>
+        </Tooltip>
       </StyledTopWrapper>
       <StyledBottomWrapper>
         <StyledElement>
@@ -244,6 +252,7 @@ export const Sidebar: React.FC = () => {
       </StyledBottomWrapper>
       <ImportModal visible={uploadVisible} setVisible={setUploadVisible} />
       <ClearModal visible={clearVisible} setVisible={setClearVisible} />
+      <ShareModal visible={shareVisible} setVisible={setShareVisible} />
     </StyledSidebar>
   );
 };

+ 1 - 1
src/containers/ImportModal/index.tsx

@@ -15,7 +15,7 @@ const StyledInput = styled.input`
   border: none;
   border-radius: 4px;
   line-height: 32px;
-  padding: 16px;
+  padding: 12px 8px;
   width: 100%;
   margin-bottom: 10px;
   height: 30px;

+ 0 - 1
src/containers/JsonEditor/index.tsx

@@ -7,7 +7,6 @@ import { ConfigActionType } from "src/reducer/reducer";
 import { useConfig } from "src/hocs/config";
 import { Loading } from "src/components/Loading";
 import { loader } from "@monaco-editor/react";
-import { CarbonAds } from "src/components/CarbonAds";
 
 loader.config({ paths: { vs: "/monaco-editor/min/vs" } });
 

+ 80 - 0
src/containers/ShareModal/index.tsx

@@ -0,0 +1,80 @@
+import React from "react";
+import toast from "react-hot-toast";
+import styled from "styled-components";
+import { useCopyToClipboard } from "usehooks-ts";
+import { Modal, ModalProps } from "src/components/Modal";
+import { Button } from "src/components/Button";
+import { BiErrorAlt } from "react-icons/bi";
+import { compress } from "compress-json";
+import { useConfig } from "src/hocs/config";
+
+const StyledInput = styled.input`
+  background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
+  color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
+  outline: none;
+  border: none;
+  border-radius: 4px;
+  line-height: 32px;
+  padding: 12px 8px;
+  width: 100%;
+  margin-bottom: 10px;
+  height: 30px;
+`;
+
+const StyledWarning = styled.p``;
+
+const StyledErrorWrapper = styled.div`
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: ${({ theme }) => theme.TEXT_DANGER};
+  font-weight: 600;
+`;
+
+export const ShareModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
+  const { json } = useConfig();
+  const [url, setURL] = React.useState("");
+  const [_, copy] = useCopyToClipboard();
+
+  React.useEffect(() => {
+    const jsonEncode = compress(JSON.parse(json));
+    const jsonString = JSON.stringify(jsonEncode);
+
+    setURL(
+      `https://jsonvisio.com/editor?json=${encodeURIComponent(jsonString)}`
+    );
+  }, [json]);
+
+  const handleShare = () => {
+    copy(url);
+    toast.success(`Link copied to clipboard.`);
+    setVisible(false);
+  };
+
+  return (
+    <Modal visible={visible} setVisible={setVisible}>
+      <Modal.Header>Create a Share Link</Modal.Header>
+      <Modal.Content>
+        {url.length > 5000 ? (
+          <StyledErrorWrapper>
+            <BiErrorAlt size={60} />
+            <StyledWarning>
+              Link size exceeds 5000 characters, unable to generate link for
+              file of this size!
+            </StyledWarning>
+          </StyledErrorWrapper>
+        ) : (
+          <StyledInput value={url} type="url" readOnly />
+        )}
+      </Modal.Content>
+      <Modal.Controls setVisible={setVisible}>
+        {url.length < 5000 && (
+          <Button status="SECONDARY" onClick={handleShare}>
+            Copy
+          </Button>
+        )}
+      </Modal.Controls>
+    </Modal>
+  );
+};

+ 20 - 1
src/hocs/config.tsx

@@ -6,6 +6,9 @@ import {
   useConfigReducer,
 } from "src/reducer/reducer";
 import { ReactComponent, StorageConfig } from "src/typings/global";
+import { isValidJson } from "src/utils/isValidJson";
+import { useRouter } from "next/router";
+import { Compressed, decompress } from "compress-json";
 
 export interface AppConfig {
   json: string;
@@ -42,7 +45,23 @@ const WithConfig: ReactComponent = ({ children }) => {
     settings: states.settings,
   };
 
+  const router = useRouter();
+  const { json } = router.query;
+
   React.useEffect(() => {
+    const jsonStored = localStorage.getItem("json");
+    const isJsonValid =
+      typeof json === "string" && isValidJson(decodeURIComponent(json));
+
+    if (isJsonValid) {
+      const jsonDecoded = decompress(JSON.parse(isJsonValid));
+      const jsonString = JSON.stringify(jsonDecoded);
+
+      dispatch({ type: ConfigActionType.SET_JSON, payload: jsonString });
+    } else if (jsonStored) {
+      dispatch({ type: ConfigActionType.SET_JSON, payload: jsonStored });
+    }
+
     const configStored = localStorage.getItem("config");
 
     if (configStored) {
@@ -53,7 +72,7 @@ const WithConfig: ReactComponent = ({ children }) => {
     }
 
     setRender(true);
-  }, [dispatch]);
+  }, [dispatch, json]);
 
   React.useEffect(() => {
     if (render)

+ 8 - 0
src/utils/isValidJson.ts

@@ -0,0 +1,8 @@
+export const isValidJson = (str: string) => {
+  try {
+    JSON.parse(str);
+  } catch (e) {
+    return false;
+  }
+  return str;
+};

+ 10 - 0
yarn.lock

@@ -2594,6 +2594,11 @@ commondir@^1.0.1:
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
   integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
 
+compress-json@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/compress-json/-/compress-json-2.0.1.tgz#8460d79e1a5612fab747c20408f997d55f11bc48"
+  integrity sha512-iwGwSGvFd1eN0LQAbbjvBy/liXXKhJpF87u4Rmoqth66o+HqOCwJT3Ok7xY0qCAJhpo4XNH8A699nOUl2G90TQ==
+
 [email protected]:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -6612,6 +6617,11 @@ use-resize-observer@^9.0.0:
   dependencies:
     "@juggle/resize-observer" "^3.3.1"
 
+usehooks-ts@^2.5.2:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-2.6.0.tgz#aebab367da2350a0bee1c3749bc6dd4bcce3eaae"
+  integrity sha512-Kj/4oc2nOxRDGTDb2v1ZulF7+tpeXFuqI6cUesM0Vic7TPPDlFORxKh4ivsYg+NTvX/YbM+lhqqkfFTiIt23eg==
+
 util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"