Selaa lähdekoodia

refactor zustand store

AykutSarac 2 vuotta sitten
vanhempi
commit
6471bebbc8

+ 2 - 2
src/components/CustomNode/ObjectNode.tsx

@@ -10,11 +10,11 @@ const ObjectNode: React.FC<CustomNodeProps<[string, string][]>> = ({
   x,
   y,
 }) => {
-  const performance = useConfig((state) => state.settings.performance);
+  const performanceMode = useConfig((state) => state.performanceMode);
 
   return (
     <Styled.StyledForeignObject width={width} height={height} x={0} y={0}>
-      <ConditionalWrapper condition={performance}>
+      <ConditionalWrapper condition={performanceMode}>
         <Styled.StyledText width={width} height={height}>
           {value.map((val, idx) => (
             <Styled.StyledRow

+ 2 - 2
src/components/CustomNode/TextNode.tsx

@@ -11,11 +11,11 @@ const TextNode: React.FC<CustomNodeProps<string>> = ({
   x,
   y,
 }) => {
-  const performance = useConfig((state) => state.settings.performance);
+  const performanceMode = useConfig((state) => state.performanceMode);
 
   return (
     <Styled.StyledForeignObject width={width} height={height} x={0} y={0}>
-      <ConditionalWrapper condition={performance}>
+      <ConditionalWrapper condition={performanceMode}>
         <Styled.StyledText width={width} height={height}>
           <Styled.StyledKey
             data-x={x}

+ 3 - 3
src/components/Graph/index.tsx

@@ -46,9 +46,9 @@ const MemoizedGraph = React.memo(function Layout({
     height: 2000,
   });
 
-  const updateSetting = useConfig((state) => state.updateSetting);
+  const setConfig = useConfig((state) => state.setConfig);
   const [expand, layout] = useConfig(
-    (state) => [state.settings.expand, state.settings.layout],
+    (state) => [state.expand, state.layout],
     shallow
   );
 
@@ -60,7 +60,7 @@ const MemoizedGraph = React.memo(function Layout({
   }, [json, expand]);
 
   const onInit = (ref: ReactZoomPanPinchRef) => {
-    updateSetting("zoomPanPinch", ref);
+    setConfig("zoomPanPinch", ref);
   };
 
   const onCanvasClick = () => {

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

@@ -27,6 +27,7 @@ import { IoAlertCircleSharp } from "react-icons/io5";
 import useConfig from "src/hooks/store/useConfig";
 import { getNextLayout } from "src/containers/Editor/LiveEditor/helpers";
 import { HiHeart } from "react-icons/hi";
+import shallow from "zustand/shallow";
 
 const StyledSidebar = styled.div`
   display: flex;
@@ -137,13 +138,16 @@ const StyledAlertIcon = styled(IoAlertCircleSharp)`
 
 export const Sidebar: React.FC = () => {
   const getJson = useConfig((state) => state.getJson);
-  const updateSetting = useConfig((state) => state.updateSetting);
-
-  const { expand, performance, layout } = useConfig((state) => state.settings);
-  const { push } = useRouter();
+  const setConfig = useConfig((state) => state.setConfig);
   const [uploadVisible, setUploadVisible] = React.useState(false);
   const [clearVisible, setClearVisible] = React.useState(false);
   const [shareVisible, setShareVisible] = React.useState(false);
+  const { push } = useRouter();
+
+  const [expand, performanceMode, layout] = useConfig(
+    (state) => [state.expand, state.performanceMode, state.layout],
+    shallow
+  );
 
   const handleSave = () => {
     const a = document.createElement("a");
@@ -155,12 +159,12 @@ export const Sidebar: React.FC = () => {
   };
 
   const toggleExpandCollapse = () => {
-    updateSetting("expand", !expand);
+    setConfig("expand", !expand);
     toast(`${expand ? "Collapsed" : "Expanded"} nodes.`);
   };
 
   const togglePerformance = () => {
-    const toastMsg = performance
+    const toastMsg = performanceMode
       ? "Disabled Performance Mode\nSearch Node & Save Image enabled."
       : "Enabled Performance Mode\nSearch Node & Save Image disabled.";
 
@@ -169,12 +173,12 @@ export const Sidebar: React.FC = () => {
       duration: 3000,
     });
 
-    updateSetting("performance", !performance);
+    setConfig("performanceMode", !performanceMode);
   };
 
   const toggleLayout = () => {
     const nextLayout = getNextLayout(layout);
-    updateSetting("layout", nextLayout);
+    setConfig("layout", nextLayout);
   };
 
   return (
@@ -208,11 +212,11 @@ export const Sidebar: React.FC = () => {
         </Tooltip>
         <Tooltip
           title={`${
-            performance ? "Disable" : "Enable"
+            performanceMode ? "Disable" : "Enable"
           } Performance Mode (Beta)`}
         >
           <StyledElement onClick={togglePerformance} beta>
-            <CgPerformance color={performance ? "#0073FF" : undefined} />
+            <CgPerformance color={performanceMode ? "#0073FF" : undefined} />
           </StyledElement>
         </Tooltip>
         <Tooltip title="Save JSON">

+ 2 - 2
src/components/Sponsors/index.tsx

@@ -1,5 +1,5 @@
 import React from "react";
-import useConfig from "src/hooks/store/useConfig";
+import useStored from "src/hooks/store/useStored";
 import styled from "styled-components";
 
 async function getSponsors() {
@@ -71,7 +71,7 @@ const StyledSponsor = styled.li<{ handle: string }>`
 `;
 
 export const Sponsors = () => {
-  const { sponsors, setSponsors } = useConfig();
+  const { sponsors, setSponsors } = useStored();
 
   React.useEffect(() => {
     if (!sponsors?.nextDate || sponsors?.nextDate < Date.now()) {

+ 3 - 2
src/components/Tooltip/index.tsx

@@ -2,7 +2,7 @@ import React from "react";
 import styled from "styled-components";
 
 interface TooltipProps {
-  title: string;
+  title?: string;
 }
 
 const StyledTooltipWrapper = styled.div`
@@ -53,7 +53,8 @@ export const Tooltip: React.FC<React.PropsWithChildren<TooltipProps>> = ({
 
   return (
     <StyledTooltipWrapper>
-      <StyledTooltip visible={visible}>{title}</StyledTooltip>
+      { title &&  <StyledTooltip visible={visible}>{title}</StyledTooltip>}
+      
       <StyledChildren
         onMouseEnter={() => setVisible(true)}
         onMouseLeave={() => setVisible(false)}

+ 0 - 11
src/constants/data.ts

@@ -1,5 +1,3 @@
-import { StorageConfig } from "src/typings/global";
-
 // Example taken from https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json
 export const defaultJson = {
   squadName: "Super hero squad",
@@ -38,12 +36,3 @@ export const defaultJson = {
     },
   ],
 };
-
-export const defaultConfig: StorageConfig = {
-  layout: "RIGHT",
-  expand: true,
-  hideEditor: false,
-  zoomPanPinch: undefined,
-  lightmode: false,
-  performance: false,
-};

+ 6 - 5
src/containers/Editor/JsonEditor/index.tsx

@@ -6,6 +6,7 @@ import { loader } from "@monaco-editor/react";
 import { ErrorContainer } from "src/components/ErrorContainer/ErrorContainer";
 import useConfig from "src/hooks/store/useConfig";
 import { Loading } from "src/components/Loading";
+import useStored from "src/hooks/store/useStored";
 
 loader.config({
   paths: {
@@ -40,8 +41,8 @@ export const JsonEditor: React.FC = () => {
   const [error, setError] = React.useState("");
 
   const json = useConfig((state) => state.json);
-  const lightmode = useConfig((state) => state.settings.lightmode);
-  const updateJson = useConfig((state) => state.updateJson);
+  const lightmode = useStored((state) => state.lightmode);
+  const setJson = useConfig((state) => state.setJson);
 
   const editorTheme = React.useMemo(
     () => (lightmode ? "light" : "vs-dark"),
@@ -57,11 +58,11 @@ export const JsonEditor: React.FC = () => {
       try {
         if (!value) {
           setError("");
-          return updateJson("{}");
+          return setJson("{}");
         }
 
         parseJson(value);
-        updateJson(value);
+        setJson(value);
         setError("");
       } catch (jsonError: any) {
         setError(jsonError.message);
@@ -69,7 +70,7 @@ export const JsonEditor: React.FC = () => {
     }, 1500);
 
     return () => clearTimeout(formatTimer);
-  }, [value, updateJson]);
+  }, [value, setJson]);
 
   return (
     <StyledEditorWrapper>

+ 1 - 1
src/containers/Editor/Panes.tsx

@@ -17,7 +17,7 @@ const LiveEditor = dynamic(() => import("src/containers/Editor/LiveEditor"), {
 });
 
 const Panes: React.FC = () => {
-  const hideEditor = useConfig((state) => state.settings.hideEditor);
+  const hideEditor = useConfig((state) => state.hideEditor);
 
   return (
     <StyledEditor>

+ 11 - 11
src/containers/Editor/Tools.tsx

@@ -12,6 +12,7 @@ import styled from "styled-components";
 import useConfig from "src/hooks/store/useConfig";
 import shallow from "zustand/shallow";
 import { DownloadModal } from "../Modals/DownloadModal";
+import useStored from "src/hooks/store/useStored";
 
 export const StyledTools = styled.div`
   display: flex;
@@ -42,22 +43,21 @@ const StyledToolElement = styled.button`
 
 export const Tools: React.FC = () => {
   const [isDownloadVisible, setDownloadVisible] = React.useState(false);
-  const [lightmode, performance, hideEditor] = useConfig(
-    (state) => [
-      state.settings.lightmode,
-      state.settings.performance,
-      state.settings.hideEditor,
-    ],
+  const lightmode = useStored((state) => state.lightmode);
+  const setLightTheme = useStored((state) => state.setLightTheme);
+
+  const [performanceMode, hideEditor] = useConfig(
+    (state) => [state.performanceMode, state.hideEditor],
     shallow
   );
 
-  const updateSetting = useConfig((state) => state.updateSetting);
+  const setConfig = useConfig((state) => state.setConfig);
 
   const zoomIn = useConfig((state) => state.zoomIn);
   const zoomOut = useConfig((state) => state.zoomOut);
   const centerView = useConfig((state) => state.centerView);
-  const toggleEditor = () => updateSetting("hideEditor", !hideEditor);
-  const toggleTheme = () => updateSetting("lightmode", !lightmode);
+  const toggleEditor = () => setConfig("hideEditor", !hideEditor);
+  const toggleTheme = () => setLightTheme(!lightmode);
 
   return (
     <StyledTools>
@@ -67,8 +67,8 @@ export const Tools: React.FC = () => {
       <StyledToolElement aria-label="switch theme" onClick={toggleTheme}>
         {lightmode ? <HiOutlineMoon /> : <HiOutlineSun />}
       </StyledToolElement>
-      {!performance && <SearchInput />}
-      {!performance && (
+      {!performanceMode && <SearchInput />}
+      {!performanceMode && (
         <StyledToolElement
           aria-label="save"
           onClick={() => setDownloadVisible(true)}

+ 2 - 2
src/containers/Modals/ClearModal/index.tsx

@@ -5,10 +5,10 @@ import { Modal, ModalProps } from "src/components/Modal";
 import useConfig from "src/hooks/store/useConfig";
 
 export const ClearModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
-  const updateJson = useConfig((state) => state.updateJson);
+  const setJson = useConfig((state) => state.setJson);
 
   const handleClear = () => {
-    updateJson("{}");
+    setJson("{}");
     toast.success(`Cleared JSON and removed from memory.`);
     setVisible(false);
   };

+ 3 - 3
src/containers/Modals/ImportModal/index.tsx

@@ -43,7 +43,7 @@ const StyledUploadMessage = styled.h3`
 `;
 
 export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
-  const updateJson = useConfig((state) => state.updateJson);
+  const setJson = useConfig((state) => state.setJson);
   const [url, setURL] = React.useState("");
   const [jsonFile, setJsonFile] = React.useState<File | null>(null);
 
@@ -59,7 +59,7 @@ export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
       return fetch(url)
         .then((res) => res.json())
         .then((json) => {
-          updateJson(JSON.stringify(json));
+          setJson(JSON.stringify(json));
           setVisible(false);
         })
         .catch(() => toast.error("Failed to fetch JSON!"))
@@ -71,7 +71,7 @@ export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
 
       reader.readAsText(jsonFile, "UTF-8");
       reader.onload = function (data) {
-        updateJson(data.target?.result as string);
+        setJson(data.target?.result as string);
         setVisible(false);
       };
     }

+ 58 - 89
src/hooks/store/useConfig.tsx

@@ -1,100 +1,69 @@
 import create from "zustand";
-import { persist } from "zustand/middleware";
-import { defaultConfig, defaultJson } from "src/constants/data";
-import { StorageConfig } from "src/typings/global";
+import { defaultJson } from "src/constants/data";
+import { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
+import { CanvasDirection } from "reaflow";
 
-type Sponsor = {
-  handle: string;
-  avatar: string;
-  profile: string;
-};
-
-function getTomorrow() {
-  const today = new Date();
-  const tomorrow = new Date(today);
-  tomorrow.setDate(tomorrow.getDate() + 1);
-  return new Date(tomorrow).getTime();
-}
-
-export interface Config {
-  json: string;
+interface ConfigActions {
+  setJson: (json: string) => void;
+  setConfig: (key: keyof Config, value: unknown) => void;
   getJson: () => string;
-  settings: StorageConfig;
-  sponsors: {
-    users: Sponsor[];
-    nextDate: number;
-  } | null;
-  setSponsors: (sponsors: Sponsor[]) => void;
-  updateJson: (json: string) => void;
-  loadSettings: (settings: StorageConfig) => void;
-  updateSetting: (setting: keyof StorageConfig, value: unknown) => void;
   zoomIn: () => void;
   zoomOut: () => void;
   centerView: () => void;
 }
 
-const useConfig = create(
-  persist<Config>(
-    (set, get) => ({
-      json: JSON.stringify(defaultJson),
-      getJson: () => get().json,
-      settings: defaultConfig,
-      sponsors: null,
-      setSponsors: (users) =>
-        set((state) => ({
-          ...state,
-          sponsors: {
-            users,
-            nextDate: getTomorrow(),
-          },
-        })),
-      updateJson: (json: string) => set((state) => ({ ...state, json })),
-      zoomIn: () => {
-        const zoomPanPinch = get().settings.zoomPanPinch;
-        if (zoomPanPinch) {
-          zoomPanPinch.setTransform(
-            zoomPanPinch?.state.positionX,
-            zoomPanPinch?.state.positionY,
-            zoomPanPinch?.state.scale + 0.4
-          );
-        }
-      },
-      zoomOut: () => {
-        const zoomPanPinch = get().settings.zoomPanPinch;
-        if (zoomPanPinch) {
-          zoomPanPinch.setTransform(
-            zoomPanPinch?.state.positionX,
-            zoomPanPinch?.state.positionY,
-            zoomPanPinch?.state.scale - 0.4
-          );
-        }
-      },
-      centerView: () => {
-        const zoomPanPinch = get().settings.zoomPanPinch;
-        if (zoomPanPinch) zoomPanPinch.resetTransform();
-      },
-      loadSettings: (settings: StorageConfig) =>
-        set((state) => ({ ...state, settings })),
-      updateSetting: (setting: keyof StorageConfig, value: unknown) =>
-        set((state) => ({
-          ...state,
-          settings: { ...state.settings, [setting]: value },
-        })),
-    }),
-    {
-      name: "config",
-      partialize: (state) =>
-        ({
-          sponsors: {
-            ...state.sponsors,
-          },
-          settings: {
-            ...state.settings,
-            zoomPanPinch: undefined,
-          },
-        } as any),
+export interface Config {
+  json: string;
+  cursorMode: "move" | "navigation";
+  layout: CanvasDirection;
+  expand: boolean;
+  hideEditor: boolean;
+  zoomPanPinch?: ReactZoomPanPinchRef;
+  performanceMode: boolean;
+}
+
+const initialStates: Config = {
+  json: JSON.stringify(defaultJson),
+  cursorMode: "move",
+  layout: "RIGHT",
+  expand: true,
+  hideEditor: false,
+  performanceMode: false,
+};
+
+const useConfig = create<Config & ConfigActions>()((set, get) => ({
+  ...initialStates,
+  getJson: () => get().json,
+  setJson: (json: string) => set((state) => ({ ...state, json })),
+  zoomIn: () => {
+    const zoomPanPinch = get().zoomPanPinch;
+    if (zoomPanPinch) {
+      zoomPanPinch.setTransform(
+        zoomPanPinch?.state.positionX,
+        zoomPanPinch?.state.positionY,
+        zoomPanPinch?.state.scale + 0.4
+      );
+    }
+  },
+  zoomOut: () => {
+    const zoomPanPinch = get().zoomPanPinch;
+    if (zoomPanPinch) {
+      zoomPanPinch.setTransform(
+        zoomPanPinch?.state.positionX,
+        zoomPanPinch?.state.positionY,
+        zoomPanPinch?.state.scale - 0.4
+      );
     }
-  )
-);
+  },
+  centerView: () => {
+    const zoomPanPinch = get().zoomPanPinch;
+    if (zoomPanPinch) zoomPanPinch.resetTransform();
+  },
+  setConfig: (setting: keyof Config, value: unknown) =>
+    set((state) => ({
+      ...state,
+      [setting]: value,
+    })),
+}));
 
 export default useConfig;

+ 58 - 0
src/hooks/store/useStored.tsx

@@ -0,0 +1,58 @@
+import create from "zustand";
+import { persist } from "zustand/middleware";
+
+type Sponsor = {
+  handle: string;
+  avatar: string;
+  profile: string;
+};
+
+function getTomorrow() {
+  const today = new Date();
+  const tomorrow = new Date(today);
+  tomorrow.setDate(tomorrow.getDate() + 1);
+
+  return new Date(tomorrow).getTime();
+}
+
+export interface Config {
+  lightmode: boolean;
+  sponsors: {
+    users: Sponsor[];
+    nextDate: number;
+  };
+  setSponsors: (sponsors: Sponsor[]) => void;
+  setLightTheme: (theme: boolean) => void;
+}
+
+const useStored = create(
+  persist<Config>(
+    (set) => ({
+      lightmode: true,
+      sponsors: {
+        users: [],
+        nextDate: Date.now(),
+      },
+      setLightTheme: (enabled: boolean) =>
+        set((state) => {
+          return {
+            ...state,
+            lightmode: enabled,
+          };
+        }),
+      setSponsors: (users) =>
+        set((state) => ({
+          ...state,
+          sponsors: {
+            users,
+            nextDate: getTomorrow(),
+          },
+        })),
+    }),
+    {
+      name: "config",
+    }
+  )
+);
+
+export default useStored;

+ 1 - 2
src/hooks/useFocusNode.tsx

@@ -8,14 +8,13 @@ import {
 import useConfig from "./store/useConfig";
 
 export const useFocusNode = () => {
+  const zoomPanPinch = useConfig((state) => state.zoomPanPinch);
   const [selectedNode, setSelectedNode] = React.useState(0);
   const [content, setContent] = React.useState({
     value: "",
     debounced: "",
   });
 
-  const zoomPanPinch = useConfig((state) => state.settings.zoomPanPinch);
-
   const skip = () => setSelectedNode((current) => current + 1);
 
   React.useEffect(() => {

+ 5 - 4
src/pages/_app.tsx

@@ -11,6 +11,7 @@ import useConfig from "src/hooks/store/useConfig";
 import { decompress } from "compress-json";
 import { useRouter } from "next/router";
 import { isValidJson } from "src/utils/isValidJson";
+import useStored from "src/hooks/store/useStored";
 
 if (process.env.NODE_ENV !== "development") {
   init({
@@ -21,8 +22,8 @@ if (process.env.NODE_ENV !== "development") {
 
 function JsonVisio({ Component, pageProps }: AppProps) {
   const { query } = useRouter();
-  const lightmode = useConfig((state) => state.settings.lightmode);
-  const updateJson = useConfig((state) => state.updateJson);
+  const lightmode = useStored((state) => state.lightmode);
+  const setJson = useConfig((state) => state.setJson);
   const [isRendered, setRendered] = React.useState(false);
 
   React.useEffect(() => {
@@ -34,9 +35,9 @@ function JsonVisio({ Component, pageProps }: AppProps) {
       const jsonDecoded = decompress(JSON.parse(isJsonValid));
       const jsonString = JSON.stringify(jsonDecoded);
 
-      updateJson(jsonString);
+      setJson(jsonString);
     }
-  }, [query.json, updateJson]);
+  }, [query.json, setJson]);
 
   React.useEffect(() => {
     if (!window.matchMedia("(display-mode: standalone)").matches) {

+ 0 - 11
src/typings/global.ts

@@ -1,14 +1,3 @@
 import React, { PropsWithChildren } from "react";
-import { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
-import { CanvasDirection } from "reaflow";
-
-export interface StorageConfig {
-  layout: CanvasDirection;
-  expand: boolean;
-  hideEditor: boolean;
-  zoomPanPinch?: ReactZoomPanPinchRef;
-  lightmode: boolean;
-  performance: boolean;
-}
 
 export type ReactComponent = React.FC<PropsWithChildren<{}>>;