Pārlūkot izejas kodu

Merge branch 'main' into mona-sans

AykutSarac 2 gadi atpakaļ
vecāks
revīzija
5da20fa5be

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

@@ -65,16 +65,16 @@ const TextNode: React.FC<CustomNodeProps> = ({
       x={0}
       y={0}
       hideCollapse={hideCollapse}
-      hasCollapse={data.isParent && hasCollapse}
+      hasCollapse={data.parent && hasCollapse}
       ref={ref}
     >
-      <StyledTextNodeWrapper hasCollapse={data.isParent && !hideCollapse}>
+      <StyledTextNodeWrapper hasCollapse={data.parent && !hideCollapse}>
         {(!performanceMode || inViewport) && (
           <Styled.StyledKey
             data-x={x}
             data-y={y}
             data-key={JSON.stringify(text)}
-            parent={data.isParent}
+            parent={data.parent}
           >
             <Styled.StyledLinkItUrl>
               {JSON.stringify(text).replaceAll('"', "")}
@@ -82,13 +82,13 @@ const TextNode: React.FC<CustomNodeProps> = ({
           </Styled.StyledKey>
         )}
 
-        {data.isParent && data.childrenCount > 0 && !hideChildrenCount && (
+        {data.parent && data.childrenCount > 0 && !hideChildrenCount && (
           <Styled.StyledChildrenCount>
             ({data.childrenCount})
           </Styled.StyledChildrenCount>
         )}
 
-        {inViewport && data.isParent && hasCollapse && !hideCollapse && (
+        {inViewport && data.parent && hasCollapse && !hideCollapse && (
           <StyledExpand onClick={handleExpand}>
             {isExpanded ? <MdLinkOff size={18} /> : <MdLink size={18} />}
           </StyledExpand>

+ 19 - 10
src/components/CustomNode/styles.tsx

@@ -2,9 +2,11 @@ import { LinkItUrl } from "react-linkify-it";
 import styled, { DefaultTheme } from "styled-components";
 
 function getTypeColor(value: string, theme: DefaultTheme) {
-  if (!Number.isNaN(+value)) return "#FD0079";
-  if (value === "true") return theme.TEXT_POSITIVE;
-  if (value === "false") return theme.TEXT_DANGER;
+  if (!Number.isNaN(+value)) return theme.NODE_COLORS.INTEGER;
+  if (value === "true") return theme.NODE_COLORS.BOOL.TRUE;
+  if (value === "false") return theme.NODE_COLORS.BOOL.FALSE;
+  if (value === "null") return theme.NODE_COLORS.NULL;
+  return theme.NODE_COLORS.NODE_VALUE;
 }
 
 export const StyledLinkItUrl = styled(LinkItUrl)`
@@ -20,7 +22,7 @@ export const StyledForeignObject = styled.foreignObject<{
   text-align: ${({ isObject }) => !isObject && "center"};
   font-size: 12px;
   overflow: hidden;
-  color: ${({ theme }) => theme.TEXT_NORMAL};
+  color: ${({ theme }) => theme.NODE_COLORS.TEXT};
   pointer-events: none;
   padding: ${({ isObject }) => isObject && "10px"};
 
@@ -51,15 +53,22 @@ export const StyledForeignObject = styled.foreignObject<{
   }
 `;
 
-function getKeyColor(theme: DefaultTheme, parent: boolean, objectKey: boolean) {
-  if (parent) return theme.NODE_KEY;
-  if (objectKey) return theme.OBJECT_KEY;
-  return theme.TEXT_POSITIVE;
+function getKeyColor(
+  theme: DefaultTheme,
+  parent: "array" | "object" | false,
+  objectKey: boolean
+) {
+  if (parent) {
+    if (parent === "array") return theme.NODE_COLORS.PARENT_ARR;
+    return theme.NODE_COLORS.PARENT_OBJ;
+  }
+  if (objectKey) return theme.NODE_COLORS.NODE_KEY;
+  return theme.NODE_COLORS.TEXT;
 }
 
 export const StyledKey = styled.span<{
   objectKey?: boolean;
-  parent?: boolean;
+  parent?: "array" | "object" | false;
   value?: string;
 }>`
   display: inline;
@@ -90,7 +99,7 @@ export const StyledRow = styled.span.attrs<{
 `;
 
 export const StyledChildrenCount = styled.span`
-  color: ${({ theme }) => theme.TEXT_POSITIVE};
+  color: ${({ theme }) => theme.NODE_COLORS.CHILD_COUNT};
   padding: 10px;
   margin-left: -15px;
 `;

+ 48 - 6
src/constants/theme.ts

@@ -1,5 +1,3 @@
-import { DefaultTheme } from "styled-components";
-
 const fixedColors = {
   CRIMSON: "#DC143C",
   BLURPLE: "#5865F2",
@@ -18,8 +16,44 @@ const fixedColors = {
   TEXT_DANGER: "#db662e",
 };
 
-export const darkTheme: DefaultTheme = {
+const nodeColors = {
+  dark: {
+    NODE_COLORS: {
+      TEXT: "#35D073",
+      NODE_KEY: "#59b8ff",
+      NODE_VALUE: "#DCE5E7",
+      INTEGER: "#e8c479",
+      NULL: "#939598",
+      BOOL: {
+        FALSE: "#F85C50",
+        TRUE: "#00DC7D",
+      },
+      PARENT_ARR: "#FC9A40",
+      PARENT_OBJ: "#59b8ff",
+      CHILD_COUNT: "white",
+    },
+  },
+  light: {
+    NODE_COLORS: {
+      TEXT: "#748700",
+      NODE_KEY: "#761CEA",
+      NODE_VALUE: "#535353",
+      INTEGER: "#A771FE",
+      NULL: "#afafaf",
+      BOOL: {
+        FALSE: "#FF0000",
+        TRUE: "#748700",
+      },
+      PARENT_ARR: "#FF6B00",
+      PARENT_OBJ: "#761CEA",
+      CHILD_COUNT: "#535353",
+    },
+  },
+};
+
+export const darkTheme = {
   ...fixedColors,
+  ...nodeColors.dark,
   BLACK_SECONDARY: "#23272A",
   SILVER_DARK: "#4D4D4D",
   NODE_KEY: "#FAA81A",
@@ -37,10 +71,11 @@ export const darkTheme: DefaultTheme = {
   MODAL_BACKGROUND: "#36393E",
   TEXT_NORMAL: "#dcddde",
   TEXT_POSITIVE: "hsl(139,calc(var(--saturation-factor, 1)*51.6%),52.2%)",
-} as const;
+};
 
-export const lightTheme: DefaultTheme = {
+export const lightTheme = {
   ...fixedColors,
+  ...nodeColors.light,
   BLACK_SECONDARY: "#F2F2F2",
   SILVER_DARK: "#CCCCCC",
   NODE_KEY: "#DC3790",
@@ -58,4 +93,11 @@ export const lightTheme: DefaultTheme = {
   MODAL_BACKGROUND: "#FFFFFF",
   TEXT_NORMAL: "#2e3338",
   TEXT_POSITIVE: "#008736",
-} as const;
+};
+
+const themeDs = {
+  ...lightTheme,
+  ...darkTheme,
+};
+
+export default themeDs;

+ 23 - 10
src/containers/Home/index.tsx

@@ -62,15 +62,14 @@ const HeroSection = () => {
           Help JSON Crack&apos;s Goals
           <FaHeart />
         </Styles.StyledSponsorButton>
-        <Link
+        <Styles.StyledSponsorButton
           href="https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode"
-          passHref
+          link
+          isBlue
         >
-          <Styles.StyledSponsorButton isBlue>
-            GET IT ON VS CODE
-            <SiVisualstudiocode />
-          </Styles.StyledSponsorButton>
-        </Link>
+          GET IT ON VS CODE
+          <SiVisualstudiocode />
+        </Styles.StyledSponsorButton>
         <GoalsModal visible={isModalVisible} setVisible={setModalVisible} />
       </Styles.StyledButtonWrapper>
     </Styles.StyledHeroSection>
@@ -220,6 +219,13 @@ const EmbedSection = () => (
         navigate and understand even complex JSON data, making it a valuable tool for
         anyone working with JSON.
       </Styles.StyledMinorTitle>
+      <Styles.StyledButton
+        href="https://jsoncrack.com/embed"
+        status="SECONDARY"
+        link
+      >
+        LEARN TO EMBED
+      </Styles.StyledButton>
     </Styles.StyledSectionArea>
     <div>
       <Styles.StyledIframge
@@ -227,9 +233,16 @@ const EmbedSection = () => (
         onLoad={e => {
           const frame = e.currentTarget.contentWindow;
           setTimeout(() => {
-            frame?.postMessage({
-              json: defaultJson,
-            });
+            frame?.postMessage(
+              {
+                json: defaultJson,
+                options: {
+                  theme: "dark",
+                  direction: "DOWN"
+                }
+              },
+              "*"
+            );
           }, 500);
         }}
       ></Styles.StyledIframge>

+ 19 - 5
src/pages/widget.tsx

@@ -3,10 +3,11 @@ import dynamic from "next/dynamic";
 import { useRouter } from "next/router";
 import toast from "react-hot-toast";
 import { baseURL } from "src/constants/data";
+import { darkTheme, lightTheme } from "src/constants/theme";
 import { NodeModal } from "src/containers/Modals/NodeModal";
 import useGraph from "src/store/useGraph";
 import { parser } from "src/utils/jsonParser";
-import styled from "styled-components";
+import styled, { ThemeProvider } from "styled-components";
 
 const Graph = dynamic<any>(() => import("src/components/Graph").then(c => c.Graph), {
   ssr: false,
@@ -62,11 +63,13 @@ const WidgetPage = () => {
 
   const [isModalVisible, setModalVisible] = React.useState(false);
   const [selectedNode, setSelectedNode] = React.useState<[string, string][]>([]);
+  const [theme, setTheme] = React.useState("dark");
 
   const collapsedNodes = useGraph(state => state.collapsedNodes);
   const collapsedEdges = useGraph(state => state.collapsedEdges);
   const loading = useGraph(state => state.loading);
   const setNodeEdges = useGraph(state => state.setNodeEdges);
+  const setDirection = useGraph(state => state.setDirection);
 
   const openModal = React.useCallback(() => setModalVisible(true), []);
 
@@ -92,9 +95,20 @@ const WidgetPage = () => {
     const handler = (event: EmbedMessage) => {
       try {
         if (!event.data?.json) return;
+
         const { nodes, edges } = parser(event.data.json);
 
-        setNodeEdges(nodes, edges);
+        const options = {
+          direction: "RIGHT",
+          theme,
+          ...event.data.options,
+        };
+
+        setDirection(options.direction);
+        if (options.theme === "light" || options.theme === "dark")
+          setTheme(options.theme);
+
+          setNodeEdges(nodes, edges);
       } catch (error) {
         console.error(error);
         toast.error("Invalid JSON!");
@@ -103,7 +117,7 @@ const WidgetPage = () => {
 
     window.addEventListener("message", handler);
     return () => window.removeEventListener("message", handler);
-  }, [setNodeEdges]);
+  }, [setDirection, setNodeEdges, theme]);
 
   if (query.json)
     return (
@@ -117,7 +131,7 @@ const WidgetPage = () => {
     );
 
   return (
-    <>
+    <ThemeProvider theme={theme === "dark" ? darkTheme : lightTheme}>
       <Graph openModal={openModal} setSelectedNode={setSelectedNode} isWidget />
       <NodeModal
         selectedNode={selectedNode}
@@ -127,7 +141,7 @@ const WidgetPage = () => {
       <StyledAttribute href={`${baseURL}/editor`} target="_blank" rel="noreferrer">
         jsoncrack.com
       </StyledAttribute>
-    </>
+    </ThemeProvider>
   );
 };
 

+ 4 - 34
src/typings/styled.d.ts

@@ -1,38 +1,8 @@
+import theme from "src/constants/theme";
 import "styled-components";
 
-declare module "styled-components" {
-  export interface DefaultTheme {
-    BLURPLE: string;
-    FULL_WHITE: string;
-    BLACK: string;
-    BLACK_LIGHT: string;
-    BLACK_DARK: string;
-    BLACK_PRIMARY: string;
-    BLACK_SECONDARY: string;
-    CRIMSON: string;
-    DARK_SALMON: string;
-    DANGER: string;
-    LIGHTGREEN: string;
-    SEAGREEN: string;
-    ORANGE: string;
-    SILVER: string;
-    SILVER_DARK: string;
-    PRIMARY: string;
-    NODE_KEY: string;
-    OBJECT_KEY: string;
-    SIDEBAR_ICONS: string;
+type CustomTheme = typeof theme;
 
-    INTERACTIVE_NORMAL: string;
-    INTERACTIVE_HOVER: string;
-    INTERACTIVE_ACTIVE: string;
-    BACKGROUND_NODE: string;
-    BACKGROUND_TERTIARY: string;
-    BACKGROUND_SECONDARY: string;
-    BACKGROUND_PRIMARY: string;
-    BACKGROUND_MODIFIER_ACCENT: string;
-    MODAL_BACKGROUND: string;
-    TEXT_NORMAL: string;
-    TEXT_POSITIVE: string;
-    TEXT_DANGER: string;
-  }
+declare module "styled-components" {
+  export interface DefaultTheme extends CustomTheme {}
 }

+ 6 - 1
src/utils/getOutgoers.ts

@@ -7,6 +7,11 @@ export const getOutgoers = (
   const outgoerNodes: NodeData[] = [];
   const matchingNodes: string[] = [];
 
+  if (parent.includes(nodeId)) {
+    const initialParentNode = nodes.find(n => n.id === nodeId);
+    if (initialParentNode) outgoerNodes.push(initialParentNode);
+  }
+
   const runner = (nodeId: string) => {
     const outgoerIds = edges.filter(e => e.from === nodeId).map(e => e.to);
     const nodeList = nodes.filter(n => {
@@ -14,7 +19,7 @@ export const getOutgoers = (
         matchingNodes.push(n.id);
       return outgoerIds.includes(n.id) && !parent.includes(n.id);
     });
-
+    
     outgoerNodes.push(...nodeList);
     nodeList.forEach(node => runner(node.id));
   };

+ 283 - 98
src/utils/jsonParser.ts

@@ -1,4 +1,4 @@
-import { parse } from "jsonc-parser";
+import { Node, parseTree } from "jsonc-parser";
 
 const calculateSize = (
   text: string | [string, string][],
@@ -32,113 +32,303 @@ const calculateSize = (
   };
 };
 
-const filterChild = ([_, v]) => {
-  const isNull = v === null;
-  const isArray = Array.isArray(v) && v.length;
-  const isObject = v instanceof Object;
-
-  return !isNull && (isArray || isObject);
-};
-
-const filterValues = ([k, v]) => {
-  if (Array.isArray(v) || v instanceof Object) return false;
-  return true;
-};
-
-function generateChildren(object: Object, isFolded = false, nextId: () => string) {
-  if (!(object instanceof Object)) object = [object];
-
-  return Object.entries(object)
-    .filter(filterChild)
-    .flatMap(([key, v]) => {
-      const { width, height } = calculateSize(key, true, isFolded);
-      const children = extract(v, isFolded, nextId);
+export const parser = (jsonStr: string, isFolded = false) => {
+  try {
+    let json = parseTree(jsonStr);
+    let nodes: NodeData[] = [];
+    let edges: EdgeData[] = [];
 
-      return [
+    const addNodes = (
+      text: any,
+      width: number,
+      height: number,
+      parent: "string" | "number" | "boolean" | "object" | "array" | "null" | false,
+      isEmpty?: boolean
+    ) => {
+      let actualId = String(nodes.length + 1);
+      nodes = [
+        ...nodes,
         {
-          id: nextId(),
-          text: key,
-          children,
-          width,
-          height,
+          id: actualId,
+          text: text,
+          width: width,
+          height: height,
           data: {
-            isParent: true,
-            childrenCount: children.length,
+            parent: parent === "array" || parent === "object" ? parent : false,
+            childrenCount: parent ? 1 : 0,
+            isEmpty: isEmpty,
           },
         },
       ];
-    });
-}
+      return actualId;
+    };
 
-function generateNodeData(object: Object) {
-  if (object instanceof Object) {
-    const entries = Object.entries(object).filter(filterValues);
-    return entries;
-  }
+    const addEdges = (from: string, to: string) => {
+      edges = [
+        ...edges,
+        {
+          id: `e${from}-${to}`,
+          from: from,
+          to: to,
+        },
+      ];
+    };
 
-  return String(object);
-}
+    let parentName: string = "";
+    let bracketOpen: { id: string; type: string }[] = [];
+    let objectsFromArray: number[] = [];
+    let objectsFromArrayId = 0;
+    let notHaveParent: string[] = [];
+    let brothersNode: [string, string][] = [];
+    let brothersParentId: string | undefined = "";
+    let brotherKey: string = "";
+    let brothersNodeProps: {
+      id: string;
+      parentId: string | undefined;
+      objectsFromArrayId: number | undefined;
+    }[] = [];
 
-const extract = (
-  os: string[] | object[] | null,
-  isFolded = false,
-  nextId = (
-    id => () =>
-      String(++id)
-  )(0)
-) => {
-  if (!os) return [];
+    const traverse = (
+      objectToTraverse: Node,
+      parentType?: string,
+      myParentId?: string,
+      nextType?: string
+    ) => {
+      let { type, children, value } = objectToTraverse;
 
-  return [os].flat().map(o => {
-    const text = generateNodeData(o);
-    const { width, height } = calculateSize(text, false, isFolded);
+      if (!children) {
+        if (value !== undefined) {
+          if (
+            parentType === "property" &&
+            nextType !== "object" &&
+            nextType !== "array"
+          ) {
+            brothersParentId = myParentId;
+            if (nextType === undefined) {
+              // add key and value to brothers node
+              brothersNode = [...brothersNode, [brotherKey, value]];
+            } else {
+              brotherKey = value;
+            }
+          } else if (parentType === "array") {
+            const { width, height } = calculateSize(String(value), false, isFolded);
+            const nodeFromArrayId = addNodes(String(value), width, height, false);
+            if (myParentId) {
+              addEdges(myParentId, nodeFromArrayId);
+            }
+          }
+          if (nextType && parentType !== "array") {
+            if (nextType === "object" || nextType === "array") {
+              parentName = value;
+            }
+          }
+        }
+      } else if (children) {
+        let parentId: string | undefined;
 
-    return {
-      id: nextId(),
-      text,
-      width,
-      height,
-      children: generateChildren(o, isFolded, nextId),
-      data: {
-        isParent: false,
-        childrenCount: 0,
-        isEmpty: !text.length,
-      },
+        if (type !== "property" && parentName !== "") {
+          // add last brothers node and add parent node
+
+          if (brothersNode.length > 0) {
+            // add or concat brothers node of same parent
+            let findBrothersNode = brothersNodeProps.find(
+              e =>
+                e.parentId === brothersParentId &&
+                e.objectsFromArrayId ===
+                  objectsFromArray[objectsFromArray.length - 1]
+            );
+            if (findBrothersNode) {
+              let ModifyNodes = [...nodes];
+              let findNode = nodes.findIndex(e => e.id === findBrothersNode?.id);
+
+              if (ModifyNodes[findNode]) {
+                ModifyNodes[findNode].text =
+                  ModifyNodes[findNode].text.concat(brothersNode);
+                const { width, height } = calculateSize(
+                  ModifyNodes[findNode].text,
+                  false,
+                  isFolded
+                );
+                ModifyNodes[findNode].width = width;
+                ModifyNodes[findNode].height = height;
+                nodes = [...ModifyNodes];
+                brothersNode = [];
+              }
+            } else {
+              const { width, height } = calculateSize(brothersNode, false, isFolded);
+              const brothersNodeId = addNodes(brothersNode, width, height, false);
+              brothersNode = [];
+
+              if (brothersParentId) {
+                addEdges(brothersParentId, brothersNodeId);
+              } else {
+                notHaveParent = [...notHaveParent, brothersNodeId];
+              }
+
+              brothersNodeProps = [
+                ...brothersNodeProps,
+                {
+                  id: brothersNodeId,
+                  parentId: brothersParentId,
+                  objectsFromArrayId: objectsFromArray[objectsFromArray.length - 1],
+                },
+              ];
+            }
+          }
+
+          // add parent node
+          const { width, height } = calculateSize(parentName, true, isFolded);
+          parentId = addNodes(parentName, width, height, type);
+          bracketOpen = [...bracketOpen, { id: parentId, type: type }];
+          parentName = "";
+
+          // add edges from parent node
+          let brothersProps = brothersNodeProps.filter(
+            e =>
+              e.parentId === myParentId &&
+              e.objectsFromArrayId === objectsFromArray[objectsFromArray.length - 1]
+          );
+          if (
+            (brothersProps.length > 0 &&
+              bracketOpen[bracketOpen.length - 2] &&
+              bracketOpen[bracketOpen.length - 2].type !== "object") ||
+            (brothersProps.length > 0 && bracketOpen.length === 1)
+          ) {
+            addEdges(brothersProps[brothersProps.length - 1].id, parentId);
+          } else if (myParentId) {
+            addEdges(myParentId, parentId);
+          } else {
+            notHaveParent = [...notHaveParent, parentId];
+          }
+        } else if (parentType === "array") {
+          objectsFromArray = [...objectsFromArray, objectsFromArrayId++];
+        }
+        children.forEach((branch, index, array) => {
+          if (array[index + 1]) {
+            traverse(
+              branch,
+              type,
+              bracketOpen[bracketOpen.length - 1]
+                ? bracketOpen[bracketOpen.length - 1].id
+                : undefined,
+              array[index + 1].type
+            );
+          } else {
+            traverse(
+              branch,
+              type,
+              bracketOpen[bracketOpen.length - 1]
+                ? bracketOpen[bracketOpen.length - 1].id
+                : undefined
+            );
+          }
+        });
+
+        if (type !== "property") {
+          // when children end
+
+          // add or concat brothers node when it is the last parent node
+          if (brothersNode.length > 0) {
+            let findBrothersNode = brothersNodeProps.find(
+              e =>
+                e.parentId === brothersParentId &&
+                e.objectsFromArrayId ===
+                  objectsFromArray[objectsFromArray.length - 1]
+            );
+            if (findBrothersNode) {
+              let ModifyNodes = [...nodes];
+              let findNode = nodes.findIndex(e => e.id === findBrothersNode?.id);
+
+              if (ModifyNodes[findNode]) {
+                ModifyNodes[findNode].text =
+                  ModifyNodes[findNode].text.concat(brothersNode);
+                const { width, height } = calculateSize(
+                  ModifyNodes[findNode].text,
+                  false,
+                  isFolded
+                );
+                ModifyNodes[findNode].width = width;
+                ModifyNodes[findNode].height = height;
+                nodes = [...ModifyNodes];
+                brothersNode = [];
+              }
+            } else {
+              const { width, height } = calculateSize(brothersNode, false, isFolded);
+              const brothersNodeId = addNodes(brothersNode, width, height, false);
+              brothersNode = [];
+
+              if (brothersParentId) {
+                addEdges(brothersParentId, brothersNodeId);
+              } else {
+                notHaveParent = [...notHaveParent, brothersNodeId];
+              }
+
+              brothersNodeProps = [
+                ...brothersNodeProps,
+                {
+                  id: brothersNodeId,
+                  parentId: brothersParentId,
+                  objectsFromArrayId: objectsFromArray[objectsFromArray.length - 1],
+                },
+              ];
+            }
+          }
+
+          // close brackets
+          if (parentType !== "array") {
+            if (bracketOpen.length > 0) {
+              let newBracketOpen = [...bracketOpen];
+              newBracketOpen.splice(newBracketOpen.length - 1);
+              bracketOpen = [...newBracketOpen];
+            }
+          } else if (parentType === "array") {
+            if (objectsFromArray.length > 0) {
+              let newobjectsFromArray = [...objectsFromArray];
+              newobjectsFromArray.splice(newobjectsFromArray.length - 1);
+              objectsFromArray = [...newobjectsFromArray];
+            }
+          }
+
+          if (parentId) {
+            let myChildrens = edges.filter(e => e.from === parentId);
+            let myIndex = nodes.findIndex(e => e.id === parentId);
+
+            let ModifyNodes = [...nodes];
+            if (ModifyNodes[myIndex]) {
+              ModifyNodes[myIndex].data.childrenCount = myChildrens.length;
+              nodes = [...ModifyNodes];
+            }
+          }
+        }
+      }
     };
-  });
-};
 
-const flatten = (xs: { id: string; children: never[] }[]) =>
-  xs.flatMap(({ children, ...rest }) => [rest, ...flatten(children)]);
-
-const relationships = (xs: { id: string; children: never[] }[]) => {
-  return xs.flatMap(({ id: from, children = [] }) => [
-    ...children.map(({ id: to }) => ({
-      id: `e${from}-${to}`,
-      from,
-      to,
-    })),
-    ...relationships(children),
-  ]);
-};
+    if (json) {
+      traverse(json);
 
-export const parser = (jsonStr: string, isFolded = false) => {
-  try {
-    let json = parse(jsonStr);
-    if (!Array.isArray(json)) json = [json];
-    const nodes: NodeData[] = [];
-    const edges: EdgeData[] = [];
-
-    const mappedElements = extract(json, isFolded);
-    const res = [...flatten(mappedElements), ...relationships(mappedElements)];
-
-    res.forEach(data => {
-      if (isNode(data)) {
-        nodes.push(data);
-      } else {
-        edges.push(data);
+      if (notHaveParent.length > 1) {
+        if (json.type !== "array") {
+          const text = "";
+          const { width, height } = calculateSize(text, false, isFolded);
+          const emptyId = addNodes(text, width, height, false, true);
+          notHaveParent.forEach(children => {
+            addEdges(emptyId, children);
+          });
+        }
+      }
+
+      if (nodes.length === 0) {
+        if (json.type === "array") {
+          const text = "[]";
+          const { width, height } = calculateSize(text, false, isFolded);
+          addNodes(text, width, height, false);
+        } else {
+          const text = "{}";
+          const { width, height } = calculateSize(text, false, isFolded);
+          addNodes(text, width, height, false);
+        }
       }
-    });
+    }
 
     return { nodes, edges };
   } catch (error) {
@@ -149,8 +339,3 @@ export const parser = (jsonStr: string, isFolded = false) => {
     };
   }
 };
-
-function isNode(element: NodeData | EdgeData) {
-  if ("text" in element) return true;
-  return false;
-}