mirror of
https://github.com/AykutSarac/jsoncrack.com.git
synced 2025-01-27 15:22:56 +08:00
implement zustand
This commit is contained in:
parent
fc5f75c6cc
commit
1c7056e8a9
@ -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",
|
||||
|
@ -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) =>
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 (
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 };
|
54
src/hooks/store/useConfig.tsx
Normal file
54
src/hooks/store/useConfig.tsx
Normal 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;
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
12
yarn.lock
12
yarn.lock
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user