Browse Source

feat: optimize loading & performance

AykutSarac 2 years ago
parent
commit
d48105d1b9

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

@@ -2,7 +2,7 @@ import React from "react";
 import { CustomNodeProps } from "src/components/CustomNode";
 import * as Styled from "./styles";
 
-export const ObjectNode: React.FC<CustomNodeProps> = ({ node, x, y }) => {
+const Node: React.FC<CustomNodeProps> = ({ node, x, y }) => {
   const { text, width, height, data } = node;
   const ref = React.useRef(null);
 
@@ -28,4 +28,10 @@ export const ObjectNode: React.FC<CustomNodeProps> = ({ node, x, y }) => {
       })}
     </Styled.StyledForeignObject>
   );
-};
+};
+
+function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {
+  return String(prev.node.text) === String(next.node.text) && prev.node.width === next.node.width;
+}
+
+export const ObjectNode = React.memo(Node, propsAreEqual);

+ 7 - 1
src/components/CustomNode/TextNode.tsx

@@ -40,7 +40,7 @@ const StyledImage = styled.img`
   background: ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
 `;
 
-export const TextNode: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }) => {
+const Node: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }) => {
   const {
     id,
     text,
@@ -105,3 +105,9 @@ export const TextNode: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse =
     </Styled.StyledForeignObject>
   );
 };
+
+function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {
+  return prev.node.text === next.node.text && prev.node.width === next.node.width;
+}
+
+export const TextNode = React.memo(Node, propsAreEqual);

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

@@ -17,7 +17,7 @@ const rootProps = {
   ry: 50,
 };
 
-const NodeComponent = (nodeProps: NodeProps) => {
+export const CustomNode = (nodeProps: NodeProps) => {
   const { text, data } = nodeProps.properties;
 
   return (
@@ -34,13 +34,3 @@ const NodeComponent = (nodeProps: NodeProps) => {
     </Node>
   );
 };
-
-export const CustomNode = React.memo(NodeComponent, (prev, next) => {
-  return (
-    String(prev.properties.text) === String(next.properties.text) &&
-    prev.properties.width === next.properties.width &&
-    prev.properties.height === next.properties.height &&
-    prev.x === next.x &&
-    prev.y === next.y
-  );
-});

+ 84 - 51
src/components/Graph/index.tsx

@@ -1,6 +1,6 @@
 import React from "react";
 import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
-import { Canvas, Edge, ElkRoot } from "reaflow";
+import { Canvas, Edge, EdgeProps, ElkRoot, NodeProps } from "reaflow";
 import { CustomNode } from "src/components/CustomNode";
 import useGraph from "src/store/useGraph";
 import useUser from "src/store/useUser";
@@ -48,7 +48,7 @@ const StyledEditorWrapper = styled.div<{ widget: boolean }>`
   }
 `;
 
-const GraphComponent = ({ isWidget = false, openNodeModal }: GraphProps) => {
+export const Graph = ({ isWidget = false, openNodeModal }: GraphProps) => {
   const isPremium = useUser(state => state.isPremium);
   const setLoading = useGraph(state => state.setLoading);
   const setZoomPanPinch = useGraph(state => state.setZoomPanPinch);
@@ -60,6 +60,27 @@ const GraphComponent = ({ isWidget = false, openNodeModal }: GraphProps) => {
   const nodes = useGraph(state => state.nodes);
   const edges = useGraph(state => state.edges);
 
+  const collapsedNodes = useGraph(state => state.collapsedNodes);
+  const collapsedEdges = useGraph(state => state.collapsedEdges);
+
+  React.useEffect(() => {
+    const nodeList = collapsedNodes.map(id => `[id$="node-${id}"]`);
+    const edgeList = collapsedEdges.map(id => `[class$="edge-${id}"]`);
+
+    const hiddenItems = document.querySelectorAll(".hide");
+    hiddenItems.forEach(item => item.classList.remove("hide"));
+
+    if (nodeList.length) {
+      const selectedNodes = document.querySelectorAll(nodeList.join(","));
+      selectedNodes.forEach(node => node.classList.add("hide"));
+    }
+
+    if (edgeList.length) {
+      const selectedEdges = document.querySelectorAll(edgeList.join(","));
+      selectedEdges.forEach(edge => edge.classList.add("hide"));
+    }
+  }, [collapsedNodes, collapsedEdges]);
+
   const [size, setSize] = React.useState({
     width: 1,
     height: 1,
@@ -92,12 +113,10 @@ const GraphComponent = ({ isWidget = false, openNodeModal }: GraphProps) => {
           height: (layout.height as number) + 400,
         });
 
-        requestAnimationFrame(() => {
-          setTimeout(() => {
-            setLoading(false);
-            setTimeout(() => {
-              if (changeRatio > 70 || isWidget) centerView();
-            });
+        setTimeout(() => {
+          setLoading(false);
+          window.requestAnimationFrame(() => {
+            if (changeRatio > 70 || isWidget) centerView();
           });
         });
       }
@@ -110,6 +129,20 @@ const GraphComponent = ({ isWidget = false, openNodeModal }: GraphProps) => {
     if (input) input.blur();
   }, []);
 
+  const memoizedNode = React.useCallback(
+    (props: JSX.IntrinsicAttributes & NodeProps<any>) => (
+      <CustomNode {...props} onClick={handleNodeClick} animated={false} />
+    ),
+    [handleNodeClick]
+  );
+
+  const memoizedEdge = React.useCallback(
+    (props: JSX.IntrinsicAttributes & Partial<EdgeProps>) => (
+      <Edge {...props} containerClassName={`edge-${props.id}`} />
+    ),
+    []
+  );
+
   if (nodes.length > 8_000) return <ErrorView />;
 
   if (nodes.length > 1_000 && !isWidget) {
@@ -117,50 +150,50 @@ const GraphComponent = ({ isWidget = false, openNodeModal }: GraphProps) => {
   }
 
   return (
-    <StyledEditorWrapper onContextMenu={e => e.preventDefault()} widget={isWidget}>
+    <>
       <Loading message="Painting graph..." loading={loading} />
-      <TransformWrapper
-        maxScale={2}
-        minScale={0.05}
-        initialScale={0.4}
-        wheel={{ step: 0.08 }}
-        zoomAnimation={{ animationType: "linear" }}
-        doubleClick={{ disabled: true }}
-        onInit={onInit}
-        onPanning={ref => ref.instance.wrapperComponent?.classList.add("dragging")}
-        onPanningStop={ref => ref.instance.wrapperComponent?.classList.remove("dragging")}
-      >
-        <TransformComponent
-          wrapperStyle={{
-            width: "100%",
-            height: "100%",
-            overflow: "hidden",
-            display: loading ? "none" : "block",
-          }}
+      <StyledEditorWrapper onContextMenu={e => e.preventDefault()} widget={isWidget}>
+        <TransformWrapper
+          maxScale={2}
+          minScale={0.05}
+          initialScale={0.4}
+          wheel={{ step: 0.08 }}
+          zoomAnimation={{ animationType: "linear" }}
+          doubleClick={{ disabled: true }}
+          onInit={onInit}
+          onPanning={ref => ref.instance.wrapperComponent?.classList.add("dragging")}
+          onPanningStop={ref => ref.instance.wrapperComponent?.classList.remove("dragging")}
         >
-          <Canvas
-            className="jsoncrack-canvas"
-            nodes={nodes}
-            edges={edges}
-            maxWidth={size.width}
-            maxHeight={size.height}
-            direction={direction}
-            onLayoutChange={onLayoutChange}
-            onCanvasClick={onCanvasClick}
-            zoomable={false}
-            animated={false}
-            readonly={true}
-            dragEdge={null}
-            dragNode={null}
-            fit={true}
-            key={direction}
-            node={props => <CustomNode {...props} onClick={handleNodeClick} />}
-            edge={props => <Edge {...props} containerClassName={`edge-${props.id}`} />}
-          />
-        </TransformComponent>
-      </TransformWrapper>
-    </StyledEditorWrapper>
+          <TransformComponent
+            wrapperStyle={{
+              width: "100%",
+              height: "100%",
+              overflow: "hidden",
+              display: loading ? "none" : "block",
+            }}
+          >
+            <Canvas
+              className="jsoncrack-canvas"
+              nodes={nodes}
+              edges={edges}
+              maxWidth={size.width}
+              maxHeight={size.height}
+              direction={direction}
+              onLayoutChange={onLayoutChange}
+              onCanvasClick={onCanvasClick}
+              node={memoizedNode}
+              edge={memoizedEdge}
+              key={direction}
+              zoomable={false}
+              animated={false}
+              readonly={true}
+              dragEdge={null}
+              dragNode={null}
+              fit={true}
+            />
+          </TransformComponent>
+        </TransformWrapper>
+      </StyledEditorWrapper>
+    </>
   );
 };
-
-export const Graph = React.memo(GraphComponent);

+ 0 - 23
src/containers/Editor/LiveEditor/GraphCanvas.tsx

@@ -1,34 +1,11 @@
 import React from "react";
 import { Graph } from "src/components/Graph";
 import { NodeModal } from "src/containers/Modals/NodeModal";
-import useGraph from "src/store/useGraph";
 
 export const GraphCanvas = ({ isWidget = false }: { isWidget?: boolean }) => {
   const [isNodeModalVisible, setNodeModalVisible] = React.useState(false);
   const openNodeModal = React.useCallback(() => setNodeModalVisible(true), []);
 
-  const collapsedNodes = useGraph(state => state.collapsedNodes);
-  const collapsedEdges = useGraph(state => state.collapsedEdges);
-
-
-  React.useEffect(() => {
-    const nodeList = collapsedNodes.map(id => `[id$="node-${id}"]`);
-    const edgeList = collapsedEdges.map(id => `[class$="edge-${id}"]`);
-
-    const hiddenItems = document.querySelectorAll(".hide");
-    hiddenItems.forEach(item => item.classList.remove("hide"));
-
-    if (nodeList.length) {
-      const selectedNodes = document.querySelectorAll(nodeList.join(","));
-      selectedNodes.forEach(node => node.classList.add("hide"));
-    }
-
-    if (edgeList.length) {
-      const selectedEdges = document.querySelectorAll(edgeList.join(","));
-      selectedEdges.forEach(edge => edge.classList.add("hide"));
-    }
-  }, [collapsedNodes, collapsedEdges]);
-
   return (
     <>
       <Graph openNodeModal={openNodeModal} isWidget={isWidget} />