Quellcode durchsuchen

advanced json editor error handling

Aykut Saraç vor 3 Jahren
Ursprung
Commit
7414724de2
4 geänderte Dateien mit 156 neuen und 27 gelöschten Zeilen
  1. 2 0
      package.json
  2. 14 3
      src/components/Sidebar/index.tsx
  3. 102 23
      src/containers/JsonEditor/index.tsx
  4. 38 1
      yarn.lock

+ 2 - 0
package.json

@@ -15,6 +15,7 @@
     "dagre": "^0.8.5",
     "next": "12.0.9",
     "next-transpile-modules": "^9.0.0",
+    "parse-json": "^6.0.2",
     "react": "17.0.2",
     "react-ace": "^9.5.0",
     "react-dom": "17.0.2",
@@ -35,6 +36,7 @@
     "@types/enzyme": "^3.10.11",
     "@types/jest": "^27.4.0",
     "@types/node": "17.0.13",
+    "@types/parse-json": "^4.0.0",
     "@types/react": "17.0.38",
     "@types/react-json-editor-ajrm": "^2.5.2",
     "@types/react-splitter-layout": "^3.0.2",

+ 14 - 3
src/components/Sidebar/index.tsx

@@ -28,6 +28,7 @@ import { getNextLayout } from "src/containers/LiveEditor/helpers";
 import { StorageConfig } from "src/typings/global";
 import { CanvasDirection } from "reaflow";
 import { defaultConfig } from "src/constants/data";
+import toast from "react-hot-toast";
 
 const StyledSidebar = styled.div`
   display: flex;
@@ -160,7 +161,10 @@ const Sidebar: React.FC<{
         <StyledElement
           as="a"
           title="Auto Format"
-          onClick={() => toggle("autoformat")}
+          onClick={() => {
+            toggle("autoformat");
+            toast(`Auto format has been ${config.autoformat ? "disabled." : "enabled."}`);
+          }}
         >
           {config.autoformat ? <MdAutoFixHigh /> : <MdOutlineAutoFixOff />}
         </StyledElement>
@@ -169,6 +173,7 @@ const Sidebar: React.FC<{
           onClick={() => {
             setJson("[]");
             localStorage.removeItem("json");
+            toast.success(`Cleared JSON and removed from memory.`);
           }}
           title="Clear JSON"
         >
@@ -189,7 +194,10 @@ const Sidebar: React.FC<{
         <StyledElement
           title="Toggle Controls"
           as="a"
-          onClick={() => toggle("controls")}
+          onClick={() => {
+            toggle("controls");
+            toast(`Controls ${config.controls ? "disabled." : "enabled."}`);
+          }}
         >
           {config.controls ? <AiFillControl /> : <AiOutlineControl />}
         </StyledElement>
@@ -197,7 +205,10 @@ const Sidebar: React.FC<{
         <StyledElement
           as="a"
           title="Toggle Expand/Collapse"
-          onClick={() => toggle("expand")}
+          onClick={() => {
+            toggle("expand");
+            toast(`${config.expand ? "Collapsed" : "Expanded"} nodes.`);
+          }}
         >
           {config.expand ? <MdUnfoldMore /> : <MdUnfoldLess />}
         </StyledElement>

+ 102 - 23
src/containers/JsonEditor/index.tsx

@@ -1,27 +1,60 @@
 import React, { ComponentType } from "react";
 import dynamic from "next/dynamic";
 import { IAceEditorProps } from "react-ace";
-import toast from "react-hot-toast";
 import { StorageConfig } from "src/typings/global";
 import { useLocalStorage } from "usehooks-ts";
 import { defaultConfig } from "src/constants/data";
+import parseJson from "parse-json";
+import styled from "styled-components";
+import { MdExpandMore, MdExpandLess } from "react-icons/md";
 
-function isJson(value: string) {
-  value = typeof value !== "string" ? JSON.stringify(value) : value;
+const StyledEditorWrapper = styled.div`
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  overflow: auto;
+  user-select: none;
+`;
 
-  try {
-    value = JSON.parse(value);
-  } catch (e: any) {
-    toast.error(e.message);
-    return false;
-  }
+const StyledErrorWrapper = styled.div``;
+
+const StyledErrorHeader = styled.div`
+  display: flex;
+  justify-content: space-between;
+  background: ${({ theme }) => theme.BLACK_DARK};
+  padding: 6px 12px;
+  color: ${({ theme }) => theme.DANGER};
+  font-size: 22px;
+`;
+
+const StyledTitle = styled.span`
+  font-weight: 600;
+`;
 
-  if (typeof value === "object" && value !== null) {
-    return true;
+const StyledCollapse = styled.button`
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: none;
+  color: ${({ theme }) => theme.DANGER};
+  cursor: pointer;
+
+  &:hover {
+    box-shadow: none;
   }
+`;
 
-  return false;
-}
+const StyledError = styled.pre`
+  color: ${({ theme }) => theme.DANGER};
+  border: 1px solid ${({ theme }) => theme.SILVER_DARK};
+  border-left: none;
+  border-right: none;
+  font-size: 12px;
+  padding: 12px;
+  margin: 0;
+  word-wrap: break-word;
+  white-space: pre-line;
+`;
 
 const AceEditor: ComponentType<IAceEditorProps> = dynamic(
   async () => {
@@ -39,11 +72,40 @@ const JsonEditor: React.FC<{
   json: string;
   setJson: React.Dispatch<React.SetStateAction<string>>;
 }> = ({ json, setJson }) => {
+  const [error, setError] = React.useState({
+    message: "",
+    isExpanded: true,
+  });
   const [config] = useLocalStorage<StorageConfig>("config", defaultConfig);
   const [value, setValue] = React.useState(
     JSON.stringify(JSON.parse(json), null, 2)
   );
 
+  function isJson(value: string) {
+    value = typeof value !== "string" ? JSON.stringify(value) : value;
+
+    try {
+      value = parseJson(value);
+      setError((err) => ({
+        ...err,
+        message: "",
+      }));
+    } catch (e: any) {
+      setError((err) => ({
+        ...err,
+        message: e.message,
+      }));
+
+      return false;
+    }
+
+    if (typeof value === "object" && value !== null) {
+      return true;
+    }
+
+    return false;
+  }
+
   React.useEffect(() => {
     if (config.autoformat) {
       return setValue(JSON.stringify(JSON.parse(json), null, 2));
@@ -63,21 +125,38 @@ const JsonEditor: React.FC<{
       }
 
       setJson(value);
-    }, 2000);
+    }, 1000);
 
     return () => clearTimeout(formatTimer);
   }, [value, config.autoformat]);
 
   return (
-    <AceEditor
-      value={value}
-      onChange={setValue}
-      mode="json"
-      theme="tomorrow_night"
-      width="auto"
-      height="100%"
-      fontSize={14}
-    />
+    <StyledEditorWrapper>
+      {error.message && (
+        <StyledErrorWrapper>
+          <StyledErrorHeader>
+            <StyledTitle>Error</StyledTitle>
+            <StyledCollapse
+              onClick={() =>
+                setError((err) => ({ ...err, isExpanded: !err.isExpanded }))
+              }
+            >
+              {true ? <MdExpandMore size={22} /> : <MdExpandLess size={22} />}
+            </StyledCollapse>
+          </StyledErrorHeader>
+          {error.isExpanded && <StyledError>{error.message}</StyledError>}
+        </StyledErrorWrapper>
+      )}
+      <AceEditor
+        value={value}
+        onChange={setValue}
+        mode="json"
+        theme="tomorrow_night"
+        width="auto"
+        height="100%"
+        fontSize={14}
+      />
+    </StyledEditorWrapper>
   );
 };
 

+ 38 - 1
yarn.lock

@@ -10,7 +10,7 @@
     "@jridgewell/trace-mapping" "^0.2.2"
     sourcemap-codec "1.4.8"
 
-"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7":
+"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
   integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
@@ -918,6 +918,11 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.13.tgz#5ed7ed7c662948335fcad6c412bb42d99ea754e3"
   integrity sha512-Y86MAxASe25hNzlDbsviXl8jQHb0RDvKt4c40ZJQ1Don0AAL0STLZSs4N+6gLEO55pedy7r2cLwS+ZDxPm/2Bw==
 
+"@types/parse-json@^4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
+  integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
+
 "@types/prettier@^2.1.5":
   version "2.4.3"
   resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.3.tgz#a3c65525b91fca7da00ab1a3ac2b5a2a4afbffbf"
@@ -1997,6 +2002,13 @@ enzyme@^3.11.0:
     rst-selector-parser "^2.2.3"
     string.prototype.trim "^1.2.1"
 
+error-ex@^1.3.2:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
+  integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
+  dependencies:
+    is-arrayish "^0.2.1"
+
 es-abstract@^1.19.0, es-abstract@^1.19.1:
   version "1.19.1"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3"
@@ -2833,6 +2845,11 @@ is-arguments@^1.0.4:
     call-bind "^1.0.2"
     has-tostringtag "^1.0.0"
 
+is-arrayish@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+  integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
+
 is-bigint@^1.0.1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@@ -3469,6 +3486,11 @@ jsesc@^2.5.1:
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
   integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
 
+json-parse-even-better-errors@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+  integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
 json-schema-traverse@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -3570,6 +3592,11 @@ levn@~0.3.0:
     prelude-ls "~1.1.2"
     type-check "~0.3.2"
 
+lines-and-columns@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz#b2f0badedb556b747020ab8ea7f0373e22efac1b"
+  integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==
+
 locate-path@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@@ -3971,6 +3998,16 @@ parent-module@^1.0.0:
   dependencies:
     callsites "^3.0.0"
 
+parse-json@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-6.0.2.tgz#6bf79c201351cc12d5d66eba48d5a097c13dc200"
+  integrity sha512-SA5aMiaIjXkAiBrW/yPgLgQAQg42f7K3ACO+2l/zOvtQBwX58DMUsFJXelW2fx3yMBmWOVkR6j1MGsdSbCA4UA==
+  dependencies:
+    "@babel/code-frame" "^7.16.0"
+    error-ex "^1.3.2"
+    json-parse-even-better-errors "^2.3.1"
+    lines-and-columns "^2.0.2"
+
 parse5-htmlparser2-tree-adapter@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"