Forráskód Böngészése

improve parser & add image preview setting

AykutSarac 2 éve
szülő
commit
fd58197035

+ 100 - 0
src/components/CustomNode/ImageNode.tsx

@@ -0,0 +1,100 @@
+import React from "react";
+import { MdLink, MdLinkOff } from "react-icons/md";
+// import { useInViewport } from "react-in-viewport";
+import { CustomNodeProps } from "src/components/CustomNode";
+import useGraph from "src/store/useGraph";
+import useStored from "src/store/useStored";
+import styled from "styled-components";
+import * as Styled from "./styles";
+
+const inViewport = true;
+
+const StyledExpand = styled.button`
+  pointer-events: all;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  color: ${({ theme }) => theme.TEXT_NORMAL};
+  background: rgba(0, 0, 0, 0.1);
+  height: 100%;
+  width: 40px;
+  border-left: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
+
+  &:hover {
+    background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);
+  }
+`;
+
+const StyledTextNodeWrapper = styled.div<{ hasCollapse: boolean }>`
+  display: flex;
+  justify-content: ${({ hasCollapse }) => (hasCollapse ? "space-between" : "center")};
+  align-items: center;
+  height: 100%;
+  width: 100%;
+`;
+
+const StyledImage = styled.img`
+  object-fit: contain;
+`;
+
+const TextNode: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }) => {
+  const { id, text, width, height, data } = node;
+  const ref = React.useRef(null);
+  const hideCollapse = useStored(state => state.hideCollapse);
+  const childrenCount = useStored(state => state.childrenCount);
+  const expandNodes = useGraph(state => state.expandNodes);
+  const collapseNodes = useGraph(state => state.collapseNodes);
+  const isExpanded = useGraph(state => state.collapsedParents.includes(id));
+  const performanceMode = useGraph(state => state.performanceMode);
+  // const { inViewport } = useInViewport(ref);
+
+  const handleExpand = (e: React.MouseEvent<HTMLButtonElement>) => {
+    e.stopPropagation();
+
+    if (!isExpanded) collapseNodes(id);
+    else expandNodes(id);
+  };
+
+  return (
+    <Styled.StyledForeignObject
+      width={width}
+      height={height}
+      x={0}
+      y={0}
+      hasCollapse={data.parent && hasCollapse}
+      ref={ref}
+    >
+      <StyledTextNodeWrapper hasCollapse={data.parent && hideCollapse}>
+        {(!performanceMode || inViewport) && (
+          <Styled.StyledKey
+            data-x={x}
+            data-y={y}
+            data-key={JSON.stringify(text)}
+            parent={data.parent}
+          >
+            <Styled.StyledLinkItUrl>
+              {JSON.stringify(text).replaceAll('"', "")}
+            </Styled.StyledLinkItUrl>
+          </Styled.StyledKey>
+        )}
+        <StyledImage src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg" alt="" />
+
+        {data.parent && data.childrenCount > 0 && childrenCount && (
+          <Styled.StyledChildrenCount>({data.childrenCount})</Styled.StyledChildrenCount>
+        )}
+
+        {inViewport && data.parent && hasCollapse && hideCollapse && (
+          <StyledExpand onClick={handleExpand}>
+            {isExpanded ? <MdLinkOff size={18} /> : <MdLink size={18} />}
+          </StyledExpand>
+        )}
+      </StyledTextNodeWrapper>
+    </Styled.StyledForeignObject>
+  );
+};
+
+function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {
+  return prev.node.text === next.node.text && prev.node.width === next.node.width;
+}
+
+export default React.memo(TextNode, propsAreEqual);

+ 42 - 25
src/components/CustomNode/TextNode.tsx

@@ -33,15 +33,28 @@ const StyledTextNodeWrapper = styled.div<{ hasCollapse: boolean }>`
   width: 100%;
 `;
 
+const StyledImageWrapper = styled.div`
+  padding: 5px;
+`;
+
+const StyledImage = styled.img`
+  border-radius: 2px;
+  object-fit: contain;
+  background: ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
+`;
+
 const TextNode: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }) => {
   const { id, text, width, height, data } = node;
   const ref = React.useRef(null);
   const hideCollapse = useStored(state => state.hideCollapse);
-  const hideChildrenCount = useStored(state => state.hideChildrenCount);
+  const childrenCount = useStored(state => state.childrenCount);
+  const imagePreview = useStored(state => state.imagePreview);
   const expandNodes = useGraph(state => state.expandNodes);
   const collapseNodes = useGraph(state => state.collapseNodes);
   const isExpanded = useGraph(state => state.collapsedParents.includes(id));
   const performanceMode = useGraph(state => state.performanceMode);
+  const isImage =
+    !Array.isArray(text) && /(https?:\/\/.*\.(?:png|jpg|gif))/i.test(text) && imagePreview;
   // const { inViewport } = useInViewport(ref);
 
   const handleExpand = (e: React.MouseEvent<HTMLButtonElement>) => {
@@ -57,34 +70,38 @@ const TextNode: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }
       height={height}
       x={0}
       y={0}
-      hideCollapse={hideCollapse}
       hasCollapse={data.parent && hasCollapse}
       ref={ref}
     >
-      <StyledTextNodeWrapper hasCollapse={data.parent && !hideCollapse}>
-        {(!performanceMode || inViewport) && (
-          <Styled.StyledKey
-            data-x={x}
-            data-y={y}
-            data-key={JSON.stringify(text)}
-            parent={data.parent}
-          >
-            <Styled.StyledLinkItUrl>
-              {JSON.stringify(text).replaceAll('"', "")}
-            </Styled.StyledLinkItUrl>
-          </Styled.StyledKey>
-        )}
-
-        {data.parent && data.childrenCount > 0 && !hideChildrenCount && (
-          <Styled.StyledChildrenCount>({data.childrenCount})</Styled.StyledChildrenCount>
-        )}
+      {isImage ? (
+        <StyledImageWrapper>
+          <StyledImage src={text} width="70" height="70" />
+        </StyledImageWrapper>
+      ) : (
+        <StyledTextNodeWrapper hasCollapse={data.parent && hideCollapse}>
+          {(!performanceMode || inViewport) && (
+            <Styled.StyledKey
+              data-x={x}
+              data-y={y}
+              data-key={JSON.stringify(text)}
+              parent={data.parent}
+            >
+              <Styled.StyledLinkItUrl>
+                {JSON.stringify(text).replaceAll('"', "")}
+              </Styled.StyledLinkItUrl>
+            </Styled.StyledKey>
+          )}
+          {data.parent && data.childrenCount > 0 && childrenCount && (
+            <Styled.StyledChildrenCount>({data.childrenCount})</Styled.StyledChildrenCount>
+          )}
 
-        {inViewport && data.parent && hasCollapse && !hideCollapse && (
-          <StyledExpand onClick={handleExpand}>
-            {isExpanded ? <MdLinkOff size={18} /> : <MdLink size={18} />}
-          </StyledExpand>
-        )}
-      </StyledTextNodeWrapper>
+          {inViewport && data.parent && hasCollapse && hideCollapse && (
+            <StyledExpand onClick={handleExpand}>
+              {isExpanded ? <MdLinkOff size={18} /> : <MdLink size={18} />}
+            </StyledExpand>
+          )}
+        </StyledTextNodeWrapper>
+      )}
     </Styled.StyledForeignObject>
   );
 };

+ 5 - 1
src/components/CustomNode/index.tsx

@@ -21,7 +21,11 @@ export const CustomNode = (nodeProps: NodeProps) => {
   const { text, data } = nodeProps.properties;
 
   return (
-    <Node {...nodeProps} {...(data.isEmpty && rootProps)} label={<React.Fragment />}>
+    <Node
+      {...nodeProps}
+      {...(data.isEmpty && rootProps)}
+      label={<React.Fragment />}
+    >
       {({ node, x, y }) => {
         if (Array.isArray(text)) {
           return <ObjectNode node={node as NodeData} x={x} y={y} />;

+ 0 - 1
src/components/CustomNode/styles.tsx

@@ -16,7 +16,6 @@ export const StyledLinkItUrl = styled(LinkItUrl)`
 
 export const StyledForeignObject = styled.foreignObject<{
   hasCollapse?: boolean;
-  hideCollapse?: boolean;
   isObject?: boolean;
 }>`
   text-align: ${({ isObject }) => !isObject && "center"};

+ 0 - 2
src/components/Sidebar/index.tsx

@@ -148,8 +148,6 @@ export const Sidebar: React.FC = () => {
   const fullscreen = useGraph(state => state.fullscreen);
   const graphCollapsed = useGraph(state => state.graphCollapsed);
 
-  console.log(graphCollapsed, foldNodes);
-
   const handleSave = () => {
     const a = document.createElement("a");
     const file = new Blob([getJson()], { type: "text/plain" });

+ 24 - 10
src/containers/Modals/SettingsModal/index.tsx

@@ -19,12 +19,23 @@ const StyledModalWrapper = styled.div`
 export const SettingsModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
   const lightmode = useStored(state => state.lightmode);
   const setLightTheme = useStored(state => state.setLightTheme);
-  const [toggleHideCollapse, hideCollapse] = useStored(
-    state => [state.toggleHideCollapse, state.hideCollapse],
-    shallow
-  );
-  const [toggleHideChildrenCount, hideChildrenCount] = useStored(
-    state => [state.toggleHideChildrenCount, state.hideChildrenCount],
+
+  const [
+    toggleHideCollapse,
+    toggleChildrenCount,
+    toggleImagePreview,
+    hideCollapse,
+    childrenCount,
+    imagePreview,
+  ] = useStored(
+    state => [
+      state.toggleHideCollapse,
+      state.toggleChildrenCount,
+      state.toggleImagePreview,
+      state.hideCollapse,
+      state.childrenCount,
+      state.imagePreview,
+    ],
     shallow
   );
 
@@ -33,14 +44,17 @@ export const SettingsModal: React.FC<ModalProps> = ({ visible, setVisible }) =>
       <Modal.Header>Settings</Modal.Header>
       <Modal.Content>
         <StyledModalWrapper>
+          <StyledToggle onChange={toggleImagePreview} checked={imagePreview}>
+            Live Image Preview
+          </StyledToggle>
           <StyledToggle onChange={toggleHideCollapse} checked={hideCollapse}>
-            Hide Collapse/Expand Button
+            Display Collapse/Expand Button
           </StyledToggle>
-          <StyledToggle onChange={toggleHideChildrenCount} checked={hideChildrenCount}>
-            Hide Children Count
+          <StyledToggle onChange={toggleChildrenCount} checked={childrenCount}>
+            Display Children Count
           </StyledToggle>
           <StyledToggle onChange={() => setLightTheme(!lightmode)} checked={lightmode}>
-            Enable Light Theme
+            Light Theme
           </StyledToggle>
         </StyledModalWrapper>
       </Modal.Content>

+ 5 - 7
src/store/useGraph.tsx

@@ -25,7 +25,7 @@ const initialStates = {
 export type Graph = typeof initialStates;
 
 interface GraphActions {
-  setGraph: (json: string) => void;
+  setGraph: (json?: string) => void;
   setNodeEdges: (nodes: NodeData[], edges: EdgeData[]) => void;
   setLoading: (loading: boolean) => void;
   setDirection: (direction: CanvasDirection) => void;
@@ -44,8 +44,8 @@ interface GraphActions {
 
 const useGraph = create<Graph & GraphActions>((set, get) => ({
   ...initialStates,
-  setGraph: (data: string) => {
-    const { nodes, edges } = parser(data, get().foldNodes);
+  setGraph: (data?: string) => {
+    const { nodes, edges } = parser(data ?? useJson.getState().json);
 
     set({
       nodes,
@@ -177,10 +177,8 @@ const useGraph = create<Graph & GraphActions>((set, get) => ({
     if (zoomPanPinch && canvas) zoomPanPinch.zoomToElement(canvas);
   },
   toggleFold: foldNodes => {
-    const { json } = useJson.getState();
-    const { nodes, edges } = parser(json, foldNodes);
-
-    set({ nodes, edges, foldNodes });
+    set({ foldNodes });
+    get().setGraph();
   },
   togglePerfMode: performanceMode => set({ performanceMode }),
   toggleFullscreen: fullscreen => set({ fullscreen }),

+ 21 - 17
src/store/useStored.tsx

@@ -1,5 +1,6 @@
 import create from "zustand";
 import { persist } from "zustand/middleware";
+import useGraph from "./useGraph";
 
 type Sponsor = {
   handle: string;
@@ -15,30 +16,29 @@ function getTomorrow() {
   return new Date(tomorrow).getTime();
 }
 
-export interface Config {
-  lightmode: boolean;
-  hideCollapse: boolean;
-  hideChildrenCount: boolean;
+const initialStates = {
+  lightmode: false,
+  hideCollapse: true,
+  childrenCount: true,
+  imagePreview: true,
   sponsors: {
-    users: Sponsor[];
-    nextDate: number;
-  };
+    users: [] as Sponsor[],
+    nextDate: Date.now(),
+  },
+};
+
+export interface ConfigActions {
   setSponsors: (sponsors: Sponsor[]) => void;
   setLightTheme: (theme: boolean) => void;
   toggleHideCollapse: (value: boolean) => void;
-  toggleHideChildrenCount: (value: boolean) => void;
+  toggleChildrenCount: (value: boolean) => void;
+  toggleImagePreview: (value: boolean) => void;
 }
 
 const useStored = create(
-  persist<Config>(
+  persist<typeof initialStates & ConfigActions>(
     set => ({
-      lightmode: false,
-      hideCollapse: false,
-      hideChildrenCount: true,
-      sponsors: {
-        users: [],
-        nextDate: Date.now(),
-      },
+      ...initialStates,
       setLightTheme: (value: boolean) =>
         set({
           lightmode: value,
@@ -51,7 +51,11 @@ const useStored = create(
           },
         }),
       toggleHideCollapse: (value: boolean) => set({ hideCollapse: value }),
-      toggleHideChildrenCount: (value: boolean) => set({ hideChildrenCount: value }),
+      toggleChildrenCount: (value: boolean) => set({ childrenCount: value }),
+      toggleImagePreview: (value: boolean) => {
+        set({ imagePreview: value });
+        useGraph.getState().setGraph();
+      },
     }),
     {
       name: "config",

+ 21 - 21
src/utils/jsonParser.ts

@@ -1,6 +1,11 @@
 import { Node, parseTree } from "jsonc-parser";
+import useGraph from "src/store/useGraph";
+import useStored from "src/store/useStored";
+
+const calculateSize = (text: string | [string, string][], isParent = false) => {
+  const isFolded = useGraph.getState().foldNodes;
+  const isImagePreview = useStored.getState().imagePreview;
 
-const calculateSize = (text: string | [string, string][], isParent = false, isFolded: boolean) => {
   let value = "";
 
   if (typeof text === "string") value = text;
@@ -22,13 +27,16 @@ const calculateSize = (text: string | [string, string][], isParent = false, isFo
     return (lineCount.length + 1) * 18;
   };
 
+  const isImage =
+    !Array.isArray(text) && /(https?:\/\/.*\.(?:png|jpg|gif))/i.test(text) && isImagePreview;
+
   return {
-    width: getWidth(),
-    height: getHeight(),
+    width: isImage ? 80 : getWidth(),
+    height: isImage ? 80 : getHeight(),
   };
 };
 
-export const parser = (jsonStr: string, isFolded = false) => {
+export const parser = (jsonStr: string) => {
   try {
     let json = parseTree(jsonStr);
     let nodes: NodeData[] = [];
@@ -103,7 +111,7 @@ export const parser = (jsonStr: string, isFolded = false) => {
               brotherKey = value;
             }
           } else if (parentType === "array") {
-            const { width, height } = calculateSize(String(value), false, isFolded);
+            const { width, height } = calculateSize(String(value), false);
             const nodeFromArrayId = addNodes(String(value), width, height, false);
             if (myParentId) {
               addEdges(myParentId, nodeFromArrayId);
@@ -134,18 +142,14 @@ export const parser = (jsonStr: string, isFolded = false) => {
 
               if (ModifyNodes[findNode]) {
                 ModifyNodes[findNode].text = ModifyNodes[findNode].text.concat(brothersNode);
-                const { width, height } = calculateSize(
-                  ModifyNodes[findNode].text,
-                  false,
-                  isFolded
-                );
+                const { width, height } = calculateSize(ModifyNodes[findNode].text, false);
                 ModifyNodes[findNode].width = width;
                 ModifyNodes[findNode].height = height;
                 nodes = [...ModifyNodes];
                 brothersNode = [];
               }
             } else {
-              const { width, height } = calculateSize(brothersNode, false, isFolded);
+              const { width, height } = calculateSize(brothersNode, false);
               const brothersNodeId = addNodes(brothersNode, width, height, false);
               brothersNode = [];
 
@@ -167,7 +171,7 @@ export const parser = (jsonStr: string, isFolded = false) => {
           }
 
           // add parent node
-          const { width, height } = calculateSize(parentName, true, isFolded);
+          const { width, height } = calculateSize(parentName, true);
           parentId = addNodes(parentName, width, height, type);
           bracketOpen = [...bracketOpen, { id: parentId, type: type }];
           parentName = "";
@@ -230,18 +234,14 @@ export const parser = (jsonStr: string, isFolded = false) => {
 
               if (ModifyNodes[findNode]) {
                 ModifyNodes[findNode].text = ModifyNodes[findNode].text.concat(brothersNode);
-                const { width, height } = calculateSize(
-                  ModifyNodes[findNode].text,
-                  false,
-                  isFolded
-                );
+                const { width, height } = calculateSize(ModifyNodes[findNode].text, false);
                 ModifyNodes[findNode].width = width;
                 ModifyNodes[findNode].height = height;
                 nodes = [...ModifyNodes];
                 brothersNode = [];
               }
             } else {
-              const { width, height } = calculateSize(brothersNode, false, isFolded);
+              const { width, height } = calculateSize(brothersNode, false);
               const brothersNodeId = addNodes(brothersNode, width, height, false);
               brothersNode = [];
 
@@ -297,7 +297,7 @@ export const parser = (jsonStr: string, isFolded = false) => {
       if (notHaveParent.length > 1) {
         if (json.type !== "array") {
           const text = "";
-          const { width, height } = calculateSize(text, false, isFolded);
+          const { width, height } = calculateSize(text, false);
           const emptyId = addNodes(text, width, height, false, true);
           notHaveParent.forEach(children => {
             addEdges(emptyId, children);
@@ -308,11 +308,11 @@ export const parser = (jsonStr: string, isFolded = false) => {
       if (nodes.length === 0) {
         if (json.type === "array") {
           const text = "[]";
-          const { width, height } = calculateSize(text, false, isFolded);
+          const { width, height } = calculateSize(text, false);
           addNodes(text, width, height, false);
         } else {
           const text = "{}";
-          const { width, height } = calculateSize(text, false, isFolded);
+          const { width, height } = calculateSize(text, false);
           addNodes(text, width, height, false);
         }
       }