소스 검색

apply context api

AykutSarac 3 년 전
부모
커밋
9ae3ded389
4개의 변경된 파일104개의 추가작업 그리고 187개의 파일을 삭제
  1. 31 55
      src/components/Sidebar/index.tsx
  2. 26 7
      src/containers/Editor/index.tsx
  3. 25 48
      src/containers/JsonEditor/index.tsx
  4. 22 77
      src/containers/LiveEditor/index.tsx

+ 31 - 55
src/components/Sidebar/index.tsx

@@ -1,7 +1,6 @@
 import React from "react";
 import Link from "next/link";
 import styled from "styled-components";
-import { useLocalStorage } from "usehooks-ts";
 import { FaFileImport } from "react-icons/fa";
 import {
   MdUnfoldMore,
@@ -11,7 +10,7 @@ import {
 } from "react-icons/md";
 import {
   AiFillHome,
-  AiOutlineClear,
+  AiFillDelete,
   AiFillGithub,
   AiOutlineTwitter,
   AiFillControl,
@@ -24,12 +23,11 @@ import {
   CgArrowLongUpE,
 } from "react-icons/cg";
 
-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";
 import { Tooltip } from "../Tooltip";
+import { ConfigActionType } from "src/reducer/reducer";
+import { useConfig } from "src/hocs/config";
 
 const StyledSidebar = styled.div`
   display: flex;
@@ -50,12 +48,13 @@ const StyledElement = styled.div<{ disabled?: boolean }>`
   font-weight: 700;
   width: 100%;
   color: ${({ theme, disabled }) =>
-    disabled ? theme.SILVER_DARK : theme.SILVER};
+    disabled ? theme.SILVER_DARK : theme.FULL_WHITE};
+  opacity: 0.6;
   cursor: pointer;
   pointer-events: ${({ disabled }) => disabled && "none"};
 
   &:hover {
-    filter: brightness(1.3);
+    opacity: 1;
   }
 
   a {
@@ -122,36 +121,30 @@ function getLayoutIcon(layout: CanvasDirection) {
   return <CgArrowLongUpE />;
 }
 
-const Sidebar: React.FC<{
-  setJson: (json: string) => void;
-}> = ({ setJson }) => {
+const Sidebar: React.FC = () => {
+  const {
+    states: { settings },
+    dispatch,
+  } = useConfig();
   const [jsonFile, setJsonFile] = React.useState<File | null>(null);
-  const [config, setConfig] = useLocalStorage<StorageConfig>(
-    "config",
-    defaultConfig
-  );
 
   const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
     if (e.target.files) setJsonFile(e.target.files?.item(0));
   };
 
-  const toggle = (setting: "expand" | "controls" | "autoformat") => {
-    setConfig((c) => ({
-      ...c,
-      [setting]: !c[setting],
-    }));
-  };
-
   React.useEffect(() => {
     if (jsonFile) {
       const reader = new FileReader();
 
       reader.readAsText(jsonFile, "UTF-8");
       reader.onload = function (data) {
-        setJson(data.target?.result as string);
+        dispatch({
+          type: ConfigActionType.SET_JSON,
+          payload: data.target?.result as string,
+        });
       };
     }
-  }, [jsonFile, setJson]);
+  }, [jsonFile, dispatch]);
 
   return (
     <StyledSidebar>
@@ -175,64 +168,47 @@ const Sidebar: React.FC<{
           <StyledElement
             as="a"
             onClick={() => {
-              toggle("autoformat");
+              dispatch({ type: ConfigActionType.TOGGLE_AUTOFORMAT });
               toast(
                 `Auto format has been ${
-                  config.autoformat ? "disabled." : "enabled."
+                  settings.autoformat ? "disabled." : "enabled."
                 }`
               );
             }}
           >
-            {config.autoformat ? <MdAutoFixHigh /> : <MdOutlineAutoFixOff />}
-          </StyledElement>
-        </Tooltip>
-        <Tooltip title="Clear JSON">
-          <StyledElement
-            as="a"
-            onClick={() => {
-              setJson("[]");
-              localStorage.removeItem("json");
-              toast.success(`Cleared JSON and removed from memory.`);
-            }}
-          >
-            <AiOutlineClear />
+            {settings.autoformat ? <MdAutoFixHigh /> : <MdOutlineAutoFixOff />}
           </StyledElement>
         </Tooltip>
         <Tooltip title="Change Layout">
           <StyledElement
             as="a"
-            onClick={() =>
-              setConfig((c) => ({
-                ...c,
-                layout: getNextLayout(c.layout),
-              }))
-            }
+            onClick={() => dispatch({ type: ConfigActionType.TOGGLE_LAYOUT })}
           >
-            {getLayoutIcon(config.layout)}
+            {getLayoutIcon(settings.layout)}
           </StyledElement>
         </Tooltip>
-        <Tooltip title="Toggle Control Buttons">
+        <Tooltip title="Toggle Compact Nodes">
           <StyledElement
-            title="Toggle Controls"
             as="a"
+            title="Toggle Expand/Collapse"
             onClick={() => {
-              toggle("controls");
-              toast(`Controls ${config.controls ? "disabled." : "enabled."}`);
+              dispatch({ type: ConfigActionType.TOGGLE_EXPAND });
+              toast(`${settings.expand ? "Collapsed" : "Expanded"} nodes.`);
             }}
           >
-            {config.controls ? <AiFillControl /> : <AiOutlineControl />}
+            {settings.expand ? <MdUnfoldMore /> : <MdUnfoldLess />}
           </StyledElement>
         </Tooltip>
-        <Tooltip title="Toggle Compact Nodes">
+        <Tooltip title="Clear JSON">
           <StyledElement
             as="a"
-            title="Toggle Expand/Collapse"
             onClick={() => {
-              toggle("expand");
-              toast(`${config.expand ? "Collapsed" : "Expanded"} nodes.`);
+              dispatch({ type: ConfigActionType.SET_JSON, payload: "[]" });
+              localStorage.removeItem("json");
+              toast.success(`Cleared JSON and removed from memory.`);
             }}
           >
-            {config.expand ? <MdUnfoldMore /> : <MdUnfoldLess />}
+            <AiFillDelete />
           </StyledElement>
         </Tooltip>
         <Tooltip title="Import File">

+ 26 - 7
src/containers/Editor/index.tsx

@@ -4,7 +4,9 @@ import { LiveEditor } from "src/containers/LiveEditor";
 import { Loading } from "src/components/Loading";
 import { Incompatible } from "src/containers/Incompatible";
 import * as Styles from "src/containers/Editor/styles";
-import { defaultJson } from "src/constants/data";
+import { Tools } from "./Tools";
+import { ConfigActionType } from "src/reducer/reducer";
+import { useConfig } from "src/hocs/config";
 
 const JsonEditor = dynamic(() => import("src/containers/JsonEditor"), {
   ssr: false,
@@ -17,26 +19,43 @@ const Sidebar = dynamic(() => import("src/components/Sidebar"), {
 });
 
 export const Editor: React.FC = () => {
-  const [json, setJson] = React.useState(JSON.stringify(defaultJson));
+  const {
+    states: { settings },
+    dispatch,
+  } = useConfig();
 
   React.useEffect(() => {
     const jsonStored = localStorage.getItem("json");
-    if (jsonStored) setJson(jsonStored);
+    if (jsonStored)
+      dispatch({ type: ConfigActionType.SET_JSON, payload: jsonStored });
+
+    const configStored = localStorage.getItem("config");
+    if (configStored)
+      dispatch({
+        type: ConfigActionType.SET_CONFIG,
+        payload: JSON.parse(configStored),
+      });
   }, []);
 
+  React.useEffect(() => {
+    localStorage.setItem("config", JSON.stringify(settings));
+  }, [settings]);
+
   return (
     <Styles.StyledPageWrapper>
-      <Sidebar setJson={setJson} />
+      <Sidebar />
       <Styles.StyledEditorWrapper>
-        <Styles.StyledTools></Styles.StyledTools>
+        <Tools />
         <Styles.StyledEditor
           maxSize={800}
           minSize={300}
           defaultSize={450}
           split="vertical"
+          size={settings.hideEditor ? 0 : 450}
+          allowResize={!settings.hideEditor}
         >
-          <JsonEditor json={json} setJson={setJson} />
-          <LiveEditor json={json} setJson={setJson} />
+          <JsonEditor />
+          <LiveEditor />
         </Styles.StyledEditor>
       </Styles.StyledEditorWrapper>
       <Incompatible />

+ 25 - 48
src/containers/JsonEditor/index.tsx

@@ -1,11 +1,10 @@
 import React from "react";
 import AceEditor from "react-ace";
-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 { Error, ErrorContainer } from "./ErrorContainer";
+import { ConfigActionType } from "src/reducer/reducer";
+import { useConfig } from "src/hocs/config";
 require("ace-builds/src-noconflict/mode-json");
 require("ace-builds/src-noconflict/theme-tomorrow_night");
 
@@ -17,42 +16,14 @@ const StyledEditorWrapper = styled.div`
   user-select: none;
 `;
 
-function isJson(
-  value: string,
-  setState: React.Dispatch<React.SetStateAction<Error>>
-) {
-  value = typeof value !== "string" ? JSON.stringify(value) : value;
-
-  try {
-    value = parseJson(value);
-    setState((err) => ({
-      ...err,
-      message: "",
-    }));
-  } catch (e: any) {
-    setState((err) => ({
-      ...err,
-      message: e.message,
-    }));
-
-    return false;
-  }
-
-  if (typeof value === "object" && value !== null) {
-    return true;
-  }
-
-  return false;
-}
-
 const aceOptions = { useWorker: false };
 
-const JsonEditor: React.FC<{
-  json: string;
-  setJson: (json: string) => void;
-}> = ({ json, setJson }) => {
+const JsonEditor: React.FC = () => {
+  const {
+    states: { json, settings },
+    dispatch,
+  } = useConfig();
   const [editorWidth, setEditorWidth] = React.useState("auto");
-  const [config] = useLocalStorage<StorageConfig>("config", defaultConfig);
   const [value, setValue] = React.useState(
     JSON.stringify(JSON.parse(json), null, 2)
   );
@@ -76,28 +47,34 @@ const JsonEditor: React.FC<{
   }, [json]);
 
   React.useEffect(() => {
-    if (config.autoformat) {
+    if (settings.autoformat) {
       return setValue(JSON.stringify(JSON.parse(json), null, 2));
     }
 
     setValue(json);
-  }, [config.autoformat, json]);
+  }, [settings.autoformat, json]);
 
   React.useEffect(() => {
     const formatTimer = setTimeout(() => {
-      if (!isJson(value, setError)) return;
-
-      if (config.autoformat) {
-        setValue(JSON.stringify(JSON.parse(value), null, 2));
-      } else {
-        setValue(value);
+      try {
+        if (value === "") return setError({ ...error, message: "" });
+        const parsedJson = parseJson(value);
+
+        if (settings.autoformat) {
+          setValue(JSON.stringify(JSON.parse(value), null, 2));
+        } else {
+          setValue(value);
+        }
+
+        dispatch({ type: ConfigActionType.SET_JSON, payload: value });
+        setError({ ...error, message: "" });
+      } catch (jsonError: any) {
+        setError({ ...error, message: jsonError.message });
       }
-
-      setJson(value);
     }, 1000);
 
     return () => clearTimeout(formatTimer);
-  }, [value, config.autoformat, setJson]);
+  }, [value, settings.autoformat, dispatch]);
 
   return (
     <StyledEditorWrapper>
@@ -119,4 +96,4 @@ const JsonEditor: React.FC<{
   );
 };
 
-export default JsonEditor;
+export default React.memo(JsonEditor);

+ 22 - 77
src/containers/LiveEditor/index.tsx

@@ -5,22 +5,12 @@ import {
   TransformComponent,
   ReactZoomPanPinchRef,
 } from "react-zoom-pan-pinch";
-import { useLocalStorage } from "usehooks-ts";
-import { Arrow, Canvas, CanvasRef } from "reaflow";
+import { Canvas } from "reaflow";
 
-import { StorageConfig } from "src/typings/global";
 import { getEdgeNodes } from "./helpers";
 import { CustomNode } from "./CustomNode";
-import { Button } from "src/components/Button";
-import {
-  AiOutlineZoomIn,
-  AiOutlineZoomOut,
-  AiOutlineFullscreen,
-  AiFillSave,
-} from "react-icons/ai";
 import { useLoading } from "src/hooks/useLoading";
-import toast from "react-hot-toast";
-import { defaultConfig } from "src/constants/data";
+import { useConfig } from "src/hocs/config";
 
 const StyledLiveEditor = styled.div`
   position: relative;
@@ -46,61 +36,34 @@ const StyledControls = styled.div`
   }
 `;
 
-export const LiveEditor: React.FC<{
-  json: string;
-  setJson: (json: string) => void;
-}> = React.memo(({ json }) => {
+const wheelOptions = {
+  step: 0.4,
+};
+
+export const LiveEditor: React.FC = React.memo(() => {
+  const {
+    states: { json, settings },
+  } = useConfig();
   const pageLoaded = useLoading();
   const wrapperRef = React.useRef<ReactZoomPanPinchRef | null>(null);
-  const [config] = useLocalStorage<StorageConfig>("config", defaultConfig);
   const [data, setData] = React.useState({
     nodes: [],
     edges: [],
   });
 
   React.useEffect(() => {
-    const { nodes, edges } = getEdgeNodes(json, config.expand);
+    const { nodes, edges } = getEdgeNodes(json, settings.expand);
 
-    setData({
-      nodes,
-      edges,
-    });
-  }, [json, config.expand]);
+    setData({ nodes, edges });
+  }, [json, settings.expand]);
 
   React.useEffect(() => {
-    if (wrapperRef.current) wrapperRef.current?.resetTransform();
-  }, [wrapperRef]);
-
-  const zoomIn = (scale: number) => {
-    if (
-      wrapperRef.current?.state.scale &&
-      wrapperRef.current?.state.scale < 2
-    ) {
-      wrapperRef.current?.setTransform(
-        wrapperRef.current.instance.transformState.positionX - 200,
-        wrapperRef.current.instance.transformState.positionY - 200,
-        wrapperRef.current.state.scale + scale
-      );
-    }
-  };
+    wrapperRef.current?.zoomIn();
+  }, [settings.zoomScale]);
 
-  const zoomOut = (scale: number) => {
-    if (
-      wrapperRef.current?.state.scale &&
-      wrapperRef.current?.state.scale > 0.4
-    ) {
-      wrapperRef.current?.setTransform(
-        wrapperRef.current.instance.transformState.positionX + 200,
-        wrapperRef.current.instance.transformState.positionY + 200,
-        wrapperRef.current.state.scale - scale
-      );
-    }
-  };
-
-  const handleSave = () => {
-    localStorage.setItem("json", json);
-    toast.success("Saved JSON successfully!");
-  };
+  React.useEffect(() => {
+    wrapperRef.current?.resetTransform();
+  }, [settings.transform]);
 
   if (pageLoaded)
     return (
@@ -112,9 +75,7 @@ export const LiveEditor: React.FC<{
             initialScale={0.8}
             ref={wrapperRef}
             limitToBounds={false}
-            wheel={{
-              step: 0.4,
-            }}
+            wheel={wheelOptions}
           >
             <TransformComponent>
               <Canvas
@@ -125,30 +86,14 @@ export const LiveEditor: React.FC<{
                 maxHeight={20000}
                 center={false}
                 zoomable={false}
-                direction={config.layout}
-                fit
+                fit={true}
+                direction={settings.layout}
                 readonly
-                animated
+                key={settings.layout}
               />
             </TransformComponent>
           </TransformWrapper>
         </StyledEditorWrapper>
-        {config.controls && (
-          <StyledControls>
-            <Button onClick={() => zoomIn(0.5)}>
-              <AiOutlineZoomIn size={24} />
-            </Button>
-            <Button onClick={() => zoomOut(0.4)}>
-              <AiOutlineZoomOut size={24} />
-            </Button>
-            <Button onClick={() => wrapperRef.current?.resetTransform()}>
-              <AiOutlineFullscreen size={24} />
-            </Button>
-            <Button onClick={handleSave}>
-              <AiFillSave size={24} />
-            </Button>
-          </StyledControls>
-        )}
       </StyledLiveEditor>
     );