implement zustand

This commit is contained in:
AykutSarac 2022-07-21 22:27:44 +03:00
parent fc5f75c6cc
commit 1c7056e8a9
18 changed files with 166 additions and 318 deletions

View File

@ -29,7 +29,8 @@
"reaflow": "^5.0.4",
"save-html-as-image": "^1.7.1",
"styled-components": "^5.3.5",
"usehooks-ts": "^2.5.2"
"usehooks-ts": "^2.5.2",
"zustand": "^4.0.0-rc.4"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.4",

View File

@ -1,6 +1,6 @@
import React from "react";
import { useConfig } from "src/hocs/config";
import { ConditionalWrapper, CustomNodeProps } from "src/components/CustomNode";
import useConfig from "src/hooks/store/useConfig";
import * as Styled from "./styles";
const ObjectNode: React.FC<CustomNodeProps<[string, string][]>> = ({
@ -10,11 +10,11 @@ const ObjectNode: React.FC<CustomNodeProps<[string, string][]>> = ({
x,
y,
}) => {
const { settings } = useConfig();
const performance = useConfig((state) => state.settings.performance);
return (
<Styled.StyledForeignObject width={width} height={height} x={0} y={0}>
<ConditionalWrapper condition={settings.performance}>
<ConditionalWrapper condition={performance}>
<Styled.StyledText width={width} height={height}>
{value.map(
(val, idx) =>

View File

@ -1,6 +1,6 @@
import React from "react";
import { useConfig } from "src/hocs/config";
import { ConditionalWrapper, CustomNodeProps } from "src/components/CustomNode";
import useConfig from "src/hooks/store/useConfig";
import * as Styled from "./styles";
const TextNode: React.FC<CustomNodeProps<string>> = ({
@ -11,11 +11,11 @@ const TextNode: React.FC<CustomNodeProps<string>> = ({
x,
y,
}) => {
const { settings } = useConfig();
const performance = useConfig((state) => state.settings.performance);
return (
<Styled.StyledForeignObject width={width} height={height} x={0} y={0}>
<ConditionalWrapper condition={settings.performance}>
<ConditionalWrapper condition={performance}>
<Styled.StyledText width={width} height={height}>
<Styled.StyledKey
data-x={x}

View File

@ -1,11 +1,17 @@
import React from "react";
import { Canvas, EdgeData, ElkRoot, NodeData } from "reaflow";
import { CustomNode } from "src/components/CustomNode";
import { useConfig } from "src/hocs/config";
import { getEdgeNodes } from "src/containers/LiveEditor/helpers";
import useConfig from "src/hooks/store/useConfig";
import shallow from "zustand/shallow";
export const Graph: React.FC = () => {
const { json, settings } = useConfig();
const json = useConfig((state) => state.json);
const [expand, layout] = useConfig(
(state) => [state.settings.expand, state.settings.layout],
shallow
);
const [nodes, setNodes] = React.useState<NodeData[]>([]);
const [edges, setEdges] = React.useState<EdgeData[]>([]);
const [size, setSize] = React.useState({
@ -14,11 +20,11 @@ export const Graph: React.FC = () => {
});
React.useEffect(() => {
const { nodes, edges } = getEdgeNodes(json, settings.expand);
const { nodes, edges } = getEdgeNodes(json, expand);
setNodes(nodes);
setEdges(edges);
}, [json, settings.expand]);
}, [json, expand]);
const onCanvasClick = () => {
const input = document.querySelector("input:focus") as HTMLInputElement;
@ -36,8 +42,8 @@ export const Graph: React.FC = () => {
edges={edges}
maxWidth={size.width}
maxHeight={size.height}
direction={settings.layout}
key={settings.layout}
direction={layout}
key={layout}
onCanvasClick={onCanvasClick}
onLayoutChange={onLayoutChange}
node={CustomNode}

View File

@ -20,13 +20,13 @@ import {
} from "react-icons/ai";
import { Tooltip } from "src/components/Tooltip";
import { ConfigActionType } from "src/reducer/reducer";
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";
import useConfig from "src/hooks/store/useConfig";
import { getNextLayout } from "src/containers/LiveEditor/helpers";
const StyledSidebar = styled.div`
display: flex;
@ -136,7 +136,10 @@ const StyledAlertIcon = styled(IoAlertCircleSharp)`
`;
export const Sidebar: React.FC = () => {
const { json, settings, dispatch } = useConfig();
const json = useConfig((state) => state.json);
const updateSetting = useConfig((state) => state.updateSetting);
const { expand, performance, layout } = useConfig((state) => state.settings);
const router = useRouter();
const [uploadVisible, setUploadVisible] = React.useState(false);
const [clearVisible, setClearVisible] = React.useState(false);
@ -152,12 +155,12 @@ export const Sidebar: React.FC = () => {
};
const toggleExpandCollapse = () => {
dispatch({ type: ConfigActionType.TOGGLE_EXPAND });
toast(`${settings.expand ? "Collapsed" : "Expanded"} nodes.`);
updateSetting("expand", !expand);
toast(`${expand ? "Collapsed" : "Expanded"} nodes.`);
};
const togglePerformance = () => {
const toastMsg = settings.performance
const toastMsg = performance
? "Disabled Performance Mode\nSearch Node & Save Image enabled."
: "Enabled Performance Mode\nSearch Node & Save Image disabled.";
@ -166,7 +169,12 @@ export const Sidebar: React.FC = () => {
duration: 3000,
});
dispatch({ type: ConfigActionType.TOGGLE_PERFORMANCE });
updateSetting("performance", !performance);
};
const toggleLayout = () => {
const nextLayout = getNextLayout(layout);
updateSetting("layout", nextLayout);
};
return (
@ -186,29 +194,25 @@ export const Sidebar: React.FC = () => {
</StyledElement>
</Tooltip>
<Tooltip title="Rotate Layout">
<StyledElement
onClick={() => dispatch({ type: ConfigActionType.TOGGLE_LAYOUT })}
>
<StyledFlowIcon rotate={rotateLayout(settings.layout)} />
<StyledElement onClick={toggleLayout}>
<StyledFlowIcon rotate={rotateLayout(layout)} />
</StyledElement>
</Tooltip>
<Tooltip title={settings.expand ? "Shrink Nodes" : "Expand Nodes"}>
<Tooltip title={expand ? "Shrink Nodes" : "Expand Nodes"}>
<StyledElement
title="Toggle Expand/Collapse"
onClick={toggleExpandCollapse}
>
{settings.expand ? <CgArrowsMergeAltH /> : <CgArrowsShrinkH />}
{expand ? <CgArrowsMergeAltH /> : <CgArrowsShrinkH />}
</StyledElement>
</Tooltip>
<Tooltip
title={`${
settings.performance ? "Disable" : "Enable"
performance ? "Disable" : "Enable"
} Performance Mode (Beta)`}
>
<StyledElement onClick={togglePerformance} beta>
<CgPerformance
color={settings.performance ? "#0073FF" : undefined}
/>
<CgPerformance color={performance ? "#0073FF" : undefined} />
</StyledElement>
</Tooltip>
<Tooltip title="Save JSON">

View File

@ -2,14 +2,13 @@ import React from "react";
import toast from "react-hot-toast";
import { Button } from "src/components/Button";
import { Modal, ModalProps } from "src/components/Modal";
import { useConfig } from "src/hocs/config";
import { ConfigActionType } from "src/reducer/reducer";
import useConfig from "src/hooks/store/useConfig";
export const ClearModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const { dispatch } = useConfig();
const updateJson = useConfig((state) => state.updateJson);
const handleClear = () => {
dispatch({ type: ConfigActionType.SET_JSON, payload: "{}" });
updateJson("{}");
toast.success(`Cleared JSON and removed from memory.`);
setVisible(false);
};

View File

@ -1,16 +1,16 @@
import { Allotment } from "allotment";
import React from "react";
import { useConfig } from "src/hocs/config";
import { JsonEditor } from "src/containers/JsonEditor";
import { StyledEditor } from "./styles";
import dynamic from "next/dynamic";
import useConfig from "src/hooks/store/useConfig";
const LiveEditor = dynamic(() => import("src/containers/LiveEditor"), {
ssr: false,
});
const Panes: React.FC = () => {
const { settings } = useConfig();
const hideEditor = useConfig((state) => state.settings.hideEditor);
return (
<StyledEditor>
@ -18,7 +18,7 @@ const Panes: React.FC = () => {
preferredSize={400}
minSize={300}
maxSize={600}
visible={!settings.hideEditor}
visible={!hideEditor}
>
<JsonEditor />
</Allotment.Pane>

View File

@ -9,9 +9,9 @@ import { FiDownload } from "react-icons/fi";
import { HiOutlineSun, HiOutlineMoon } from "react-icons/hi";
import { MdCenterFocusWeak } from "react-icons/md";
import { SearchInput } from "src/containers/SearchInput";
import { useConfig } from "src/hocs/config";
import { ConfigActionType } from "src/reducer/reducer";
import styled from "styled-components";
import useConfig from "src/hooks/store/useConfig";
import shallow from "zustand/shallow";
export const StyledTools = styled.div`
display: flex;
@ -41,17 +41,22 @@ const StyledToolElement = styled.button`
`;
export const Tools: React.FC = () => {
const { settings, dispatch } = useConfig();
const [lightmode, performance, hideEditor] = useConfig(
(state) => [
state.settings.lightmode,
state.settings.performance,
state.settings.hideEditor,
],
shallow
);
const zoomIn = () => dispatch({ type: ConfigActionType.ZOOM_IN });
const updateSetting = useConfig((state) => state.updateSetting);
const zoomOut = () => dispatch({ type: ConfigActionType.ZOOM_OUT });
const centerView = () => dispatch({ type: ConfigActionType.CENTER_VIEW });
const toggleEditor = () => dispatch({ type: ConfigActionType.TOGGLE_DOCK });
const toggleTheme = () => dispatch({ type: ConfigActionType.TOGGLE_THEME });
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 exportAsImage = () => {
saveAsPng(document.querySelector("svg[id*='ref']"), {
@ -66,10 +71,10 @@ export const Tools: React.FC = () => {
<AiOutlineFullscreen />
</StyledToolElement>
<StyledToolElement aria-label="switch theme" onClick={toggleTheme}>
{settings.lightmode ? <HiOutlineMoon /> : <HiOutlineSun />}
{lightmode ? <HiOutlineMoon /> : <HiOutlineSun />}
</StyledToolElement>
{!settings.performance && <SearchInput />}
{!settings.performance && (
{!performance && <SearchInput />}
{!performance && (
<StyledToolElement aria-label="save" onClick={exportAsImage}>
<FiDownload />
</StyledToolElement>

View File

@ -2,11 +2,10 @@ import React from "react";
import styled from "styled-components";
import toast from "react-hot-toast";
import { useConfig } from "src/hocs/config";
import { ConfigActionType } from "src/reducer/reducer";
import { Modal, ModalProps } from "src/components/Modal";
import { Button } from "src/components/Button";
import { AiOutlineUpload } from "react-icons/ai";
import useConfig from "src/hooks/store/useConfig";
const StyledInput = styled.input`
background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
@ -55,7 +54,7 @@ const StyledUploadMessage = styled.h3`
`;
export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const { dispatch } = useConfig();
const updateJson = useConfig((state) => state.updateJson);
const [url, setURL] = React.useState("");
const [jsonFile, setJsonFile] = React.useState<File | null>(null);
@ -71,11 +70,7 @@ export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
return fetch(url)
.then((res) => res.json())
.then((json) => {
dispatch({
type: ConfigActionType.SET_JSON,
payload: JSON.stringify(json),
});
updateJson(JSON.stringify(json));
setVisible(false);
})
.catch(() => toast.error("Failed to fetch JSON!"))
@ -87,11 +82,7 @@ export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
reader.readAsText(jsonFile, "UTF-8");
reader.onload = function (data) {
dispatch({
type: ConfigActionType.SET_JSON,
payload: data.target?.result as string,
});
updateJson(data.target?.result as string);
setVisible(false);
};
}

View File

@ -3,10 +3,9 @@ import Editor from "@monaco-editor/react";
import parseJson from "parse-json";
import styled from "styled-components";
import { ErrorContainer } from "src/components/ErrorContainer/ErrorContainer";
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 useConfig from "src/hooks/store/useConfig";
loader.config({ paths: { vs: "/monaco-editor/min/vs" } });
@ -33,7 +32,10 @@ const StyledWrapper = styled.div`
`;
export const JsonEditor: React.FC = () => {
const { json, settings, dispatch } = useConfig();
const json = useConfig((state) => state.json);
const lightmode = useConfig((state) => state.settings.lightmode);
const updateJson = useConfig((state) => state.updateJson);
const [value, setValue] = React.useState("");
const [error, setError] = React.useState({
message: "",
@ -41,8 +43,8 @@ export const JsonEditor: React.FC = () => {
});
const editorTheme = React.useMemo(
() => (settings.lightmode ? "light" : "vs-dark"),
[settings.lightmode]
() => (lightmode ? "light" : "vs-dark"),
[lightmode]
);
React.useEffect(() => {
@ -54,11 +56,11 @@ export const JsonEditor: React.FC = () => {
try {
if (!value) {
setError((err) => ({ ...err, message: "" }));
return dispatch({ type: ConfigActionType.SET_JSON, payload: "[]" });
return updateJson("[]");
}
parseJson(value);
dispatch({ type: ConfigActionType.SET_JSON, payload: value });
updateJson(value);
setError((err) => ({ ...err, message: "" }));
} catch (jsonError: any) {
setError((err) => ({ ...err, message: jsonError.message }));
@ -66,7 +68,7 @@ export const JsonEditor: React.FC = () => {
}, 1500);
return () => clearTimeout(formatTimer);
}, [value, dispatch]);
}, [value, updateJson]);
return (
<StyledEditorWrapper>

View File

@ -6,10 +6,9 @@ import {
ReactZoomPanPinchRef,
} from "react-zoom-pan-pinch";
import { useConfig } from "src/hocs/config";
import { Tools } from "src/containers/Editor/Tools";
import { ConfigActionType } from "src/reducer/reducer";
import { Graph } from "src/components/Graph";
import useConfig from "src/hooks/store/useConfig";
const StyledLiveEditor = styled.div`
position: relative;
@ -34,13 +33,10 @@ const wheelOptions = {
};
const LiveEditor: React.FC = () => {
const { dispatch } = useConfig();
const updateSetting = useConfig((state) => state.updateSetting);
const onInit = (ref: ReactZoomPanPinchRef) => {
dispatch({
type: ConfigActionType.SET_ZOOM_PAN_PICNH_REF,
payload: ref,
});
updateSetting("zoomPanPinch", ref);
};
return (

View File

@ -6,7 +6,7 @@ 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";
import useConfig from "src/hooks/store/useConfig";
const StyledInput = styled.input`
background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
@ -33,7 +33,7 @@ const StyledErrorWrapper = styled.div`
`;
export const ShareModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const { json } = useConfig();
const json = useConfig((state) => state.json);
const [url, setURL] = React.useState("");
const [_, copy] = useCopyToClipboard();

View File

@ -1,104 +0,0 @@
import React from "react";
import { defaultConfig, defaultJson } from "src/constants/data";
import {
ConfigActionType,
ReducerAction,
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;
settings: StorageConfig;
}
export const initialStates: AppConfig = {
json: JSON.stringify(defaultJson),
settings: defaultConfig,
};
interface Config {
json: string;
settings: StorageConfig;
dispatch: React.Dispatch<ReducerAction>;
}
const defaultContext: Config = {
...initialStates,
dispatch: () => {},
};
const ConfigContext: React.Context<Config> =
React.createContext(defaultContext);
const useConfig = () => React.useContext(ConfigContext);
const WithConfig: ReactComponent = ({ children }) => {
const [render, setRender] = React.useState(false);
const [states, dispatch] = React.useReducer(useConfigReducer, initialStates);
const value = {
dispatch,
json: states.json,
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) {
dispatch({
type: ConfigActionType.SET_CONFIG,
payload: JSON.parse(configStored),
});
}
setRender(true);
}, [dispatch, json]);
React.useEffect(() => {
if (render)
localStorage.setItem(
"config",
JSON.stringify({
...states.settings,
zoomPanPinch: undefined,
performance: undefined,
})
);
}, [states.settings, render]);
return (
<ConfigContext.Provider value={value}>{children}</ConfigContext.Provider>
);
};
const withConfig = <P extends object>(
Component: React.ComponentType<P>
): React.FC => {
return (props) => (
<WithConfig>
<Component {...(props as P)} />
</WithConfig>
);
};
export { WithConfig, useConfig, ConfigContext, withConfig };

View File

@ -0,0 +1,54 @@
import { defaultConfig, defaultJson } from "src/constants/data";
import { StorageConfig } from "src/typings/global";
import create from "zustand";
export interface Config {
json: string;
settings: StorageConfig;
updateJson: (json: string) => void;
loadSettings: (settings: StorageConfig) => void;
updateSetting: (setting: keyof StorageConfig, value: any) => void;
zoomIn: () => void;
zoomOut: () => void;
centerView: () => void;
}
const useConfig = create<Config>((set) => ({
json: JSON.stringify(defaultJson),
settings: defaultConfig,
updateJson: (json: string) => set((state) => ({ ...state, json })),
loadSettings: (settings: StorageConfig) =>
set((state) => ({ ...state, settings })),
updateSetting: (setting: keyof StorageConfig, value: any) =>
set((state) => ({
...state,
settings: { ...state.settings, [setting]: value },
})),
zoomIn: () =>
set((state) => {
state.settings.zoomPanPinch?.setTransform(
state.settings.zoomPanPinch?.state.positionX,
state.settings.zoomPanPinch?.state.positionY,
state.settings.zoomPanPinch?.state.scale + 0.4
);
return state;
}),
zoomOut: () =>
set((state) => {
state.settings.zoomPanPinch?.setTransform(
state.settings.zoomPanPinch?.state.positionX,
state.settings.zoomPanPinch?.state.positionY,
state.settings.zoomPanPinch?.state.scale - 0.4
);
return state;
}),
centerView: () =>
set((state) => {
state.settings.zoomPanPinch?.resetTransform();
return state;
}),
}));
export default useConfig;

View File

@ -1,11 +1,11 @@
import React from "react";
import { useConfig } from "src/hocs/config";
import {
searchQuery,
cleanupHighlight,
highlightMatchedNodes,
} from "src/utils/search";
import useConfig from "./store/useConfig";
export const useFocusNode = () => {
const [selectedNode, setSelectedNode] = React.useState(0);
@ -14,7 +14,7 @@ export const useFocusNode = () => {
debounced: "",
});
const { settings } = useConfig();
const zoomPanPinch = useConfig((state) => state.settings.zoomPanPinch);
const skip = () => setSelectedNode((current) => current + 1);
@ -27,8 +27,8 @@ export const useFocusNode = () => {
}, [content.value]);
React.useEffect(() => {
if (!settings.zoomPanPinch) return;
const zoomPanPinch = settings.zoomPanPinch.instance.wrapperComponent;
if (!zoomPanPinch) return;
const ref = zoomPanPinch.instance.wrapperComponent;
const matchedNodes: NodeListOf<Element> = searchQuery(
`span[data-key*='${content.debounced}' i]`
@ -37,23 +37,23 @@ export const useFocusNode = () => {
cleanupHighlight();
if (zoomPanPinch && matchedNode && matchedNode.parentElement) {
if (ref && matchedNode && matchedNode.parentElement) {
const newScale = 1;
const x = Number(matchedNode.getAttribute("data-x"));
const y = Number(matchedNode.getAttribute("data-y"));
const newPositionX =
(zoomPanPinch.offsetLeft - x) * newScale +
zoomPanPinch.clientWidth / 2 -
(ref.offsetLeft - x) * newScale +
ref.clientWidth / 2 -
matchedNode.getBoundingClientRect().width / 2;
const newPositionY =
(zoomPanPinch.offsetLeft - y) * newScale +
zoomPanPinch.clientHeight / 2 -
(ref.offsetLeft - y) * newScale +
ref.clientHeight / 2 -
matchedNode.getBoundingClientRect().height / 2;
highlightMatchedNodes(matchedNodes, selectedNode);
settings.zoomPanPinch?.setTransform(newPositionX, newPositionY, newScale);
zoomPanPinch?.setTransform(newPositionX, newPositionY, newScale);
} else {
setSelectedNode(0);
}
@ -61,7 +61,7 @@ export const useFocusNode = () => {
return () => {
if (!content.value) setSelectedNode(0);
};
}, [content.debounced, settings.zoomPanPinch, selectedNode, setSelectedNode]);
}, [content.debounced, zoomPanPinch, selectedNode, setSelectedNode]);
return [content, setContent, skip] as const;
};

View File

@ -6,8 +6,8 @@ import { init } from "@sentry/nextjs";
import GlobalStyle from "src/constants/globalStyle";
import { darkTheme, lightTheme } from "src/constants/theme";
import { useConfig, withConfig } from "src/hocs/config";
import { GoogleAnalytics } from "src/components/GoogleAnalytics";
import useConfig from "src/hooks/store/useConfig";
if (process.env.NODE_ENV !== "development") {
init({
@ -17,7 +17,7 @@ if (process.env.NODE_ENV !== "development") {
}
function JsonVisio({ Component, pageProps }: AppProps) {
const { settings } = useConfig();
const lightmode = useConfig((state) => state.settings.lightmode);
React.useEffect(() => {
if (!window.matchMedia("(display-mode: standalone)").matches) {
@ -37,7 +37,7 @@ function JsonVisio({ Component, pageProps }: AppProps) {
return (
<>
<GoogleAnalytics />
<ThemeProvider theme={settings.lightmode ? lightTheme : darkTheme}>
<ThemeProvider theme={lightmode ? lightTheme : darkTheme}>
<GlobalStyle />
<Component {...pageProps} />
<Toaster
@ -54,4 +54,4 @@ function JsonVisio({ Component, pageProps }: AppProps) {
);
}
export default withConfig(JsonVisio);
export default JsonVisio;

View File

@ -1,118 +0,0 @@
import React from "react";
import { getNextLayout } from "src/containers/LiveEditor/helpers";
import { AppConfig, initialStates } from "../hocs/config";
export enum ConfigActionType {
SET_CONFIG,
TOGGLE_LAYOUT,
TOGGLE_EXPAND,
TOGGLE_PERFORMANCE,
TOGGLE_DOCK,
TOGGLE_THEME,
ZOOM_IN,
ZOOM_OUT,
CENTER_VIEW,
SET_JSON,
SET_ZOOM_PAN_PICNH_REF,
}
export type ReducerAction = {
type: ConfigActionType;
payload?: any;
};
export const useConfigReducer: React.Reducer<AppConfig, ReducerAction> = (
state = initialStates,
action
) => {
switch (action.type) {
case ConfigActionType.SET_CONFIG:
return {
...state,
settings: action.payload,
};
case ConfigActionType.TOGGLE_THEME:
return {
...state,
settings: {
...state.settings,
lightmode: !state.settings.lightmode,
},
};
case ConfigActionType.SET_ZOOM_PAN_PICNH_REF:
return {
...state,
settings: {
...state.settings,
zoomPanPinch: action.payload,
},
};
case ConfigActionType.CENTER_VIEW:
state.settings.zoomPanPinch?.resetTransform();
return state;
case ConfigActionType.ZOOM_IN:
state.settings.zoomPanPinch?.setTransform(
state.settings.zoomPanPinch?.state.positionX,
state.settings.zoomPanPinch?.state.positionY,
state.settings.zoomPanPinch?.state.scale + 0.4
);
return state;
case ConfigActionType.ZOOM_OUT:
state.settings.zoomPanPinch?.setTransform(
state.settings.zoomPanPinch?.state.positionX,
state.settings.zoomPanPinch?.state.positionY,
state.settings.zoomPanPinch?.state.scale - 0.4
);
return state;
case ConfigActionType.TOGGLE_DOCK:
return {
...state,
settings: {
...state.settings,
hideEditor: !state.settings.hideEditor,
},
};
case ConfigActionType.TOGGLE_EXPAND:
return {
...state,
settings: {
...state.settings,
expand: !state.settings.expand,
},
};
case ConfigActionType.TOGGLE_PERFORMANCE:
return {
...state,
settings: {
...state.settings,
performance: !state.settings.performance,
},
};
case ConfigActionType.TOGGLE_LAYOUT:
return {
...state,
settings: {
...state.settings,
layout: getNextLayout(state.settings.layout),
},
};
case ConfigActionType.SET_JSON:
return {
...state,
json: action.payload,
};
default:
return state;
}
};

View File

@ -6617,6 +6617,11 @@ use-resize-observer@^9.0.0:
dependencies:
"@juggle/resize-observer" "^3.3.1"
use-sync-external-store@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
usehooks-ts@^2.5.2:
version "2.6.0"
resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-2.6.0.tgz#aebab367da2350a0bee1c3749bc6dd4bcce3eaae"
@ -7003,3 +7008,10 @@ yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
zustand@^4.0.0-rc.4:
version "4.0.0-rc.4"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.0.0-rc.4.tgz#ed0e0f1fa3e1c7d0d9021739d862d88048d845da"
integrity sha512-BP35Rq40GBTKKtYyjLuZogzXGh289xqO8U8ivGIK43nRqURD2dEEImkUci1/jWRUz7J1OPJkUZuNySCho801gQ==
dependencies:
use-sync-external-store "1.2.0"