import { Node, NodeType } from "jsonc-parser"; import { calculateNodeSize } from "src/lib/utils/graph/calculateNodeSize"; import { Graph, States } from "src/lib/utils/json/jsonParser"; import { addEdgeToGraph } from "./addEdgeToGraph"; import { addNodeToGraph } from "./addNodeToGraph"; type PrimitiveOrNullType = "boolean" | "string" | "number" | "null"; type Traverse = { states: States; objectToTraverse: Node; parentType?: string; myParentId?: string; nextType?: string; }; const isPrimitiveOrNullType = (type: unknown): type is PrimitiveOrNullType => { return ["boolean", "string", "number", "null"].includes(type as string); }; const alignChildren = (nodeA: Node, nodeB: Node): number => { const aChildType = nodeA?.children?.[1]?.type; const bChildType = nodeB?.children?.[1]?.type; if (isPrimitiveOrNullType(aChildType) && !isPrimitiveOrNullType(bChildType)) { return -1; } return 0; }; function handleNoChildren( value: string | undefined, states: States, graph: Graph, myParentId?: string, parentType?: string, nextType?: string ) { if (value === undefined) return; if (parentType === "property" && nextType !== "object" && nextType !== "array") { states.brothersParentId = myParentId; if (nextType === undefined && Array.isArray(states.brothersNode)) { states.brothersNode.push([states.brotherKey, value]); } else { states.brotherKey = value; } } else if (parentType === "array") { const nodeFromArrayId = addNodeToGraph({ graph, text: String(value) }); if (myParentId) { addEdgeToGraph(graph, myParentId, nodeFromArrayId); } } if (nextType && parentType !== "array" && (nextType === "object" || nextType === "array")) { states.parentName = value; } } function handleHasChildren( type: NodeType, states: States, graph: Graph, children: Node[], myParentId?: string, parentType?: string ) { let parentId: string | undefined; if (type !== "property" && states.parentName !== "") { // add last brothers node and add parent node if (states.brothersNode.length > 0) { const findBrothersNode = states.brothersNodeProps.find( e => e.parentId === states.brothersParentId && e.objectsFromArrayId === states.objectsFromArray[states.objectsFromArray.length - 1] ); if (findBrothersNode) { const findNodeIndex = graph.nodes.findIndex(e => e.id === findBrothersNode?.id); if (findNodeIndex !== -1) { const modifyNodes = [...graph.nodes]; const foundNode = modifyNodes[findNodeIndex]; foundNode.text = foundNode.text.concat(states.brothersNode as any); const { width, height } = calculateNodeSize(foundNode.text, false); foundNode.width = width; foundNode.height = height; graph.nodes = modifyNodes; states.brothersNode = []; } } else { const brothersNodeId = addNodeToGraph({ graph, text: states.brothersNode }); states.brothersNode = []; if (states.brothersParentId) { addEdgeToGraph(graph, states.brothersParentId, brothersNodeId); } else { states.notHaveParent.push(brothersNodeId); } states.brothersNodeProps.push({ id: brothersNodeId, parentId: states.brothersParentId, objectsFromArrayId: states.objectsFromArray[states.objectsFromArray.length - 1], }); } } // Add parent node parentId = addNodeToGraph({ graph, type, text: states.parentName }); states.bracketOpen.push({ id: parentId, type }); states.parentName = ""; // Add edges from parent node const brothersProps = states.brothersNodeProps.filter( e => e.parentId === myParentId && e.objectsFromArrayId === states.objectsFromArray[states.objectsFromArray.length - 1] ); if ( (brothersProps.length > 0 && states.bracketOpen[states.bracketOpen.length - 2]?.type !== "object") || (brothersProps.length > 0 && states.bracketOpen.length === 1) ) { addEdgeToGraph(graph, brothersProps[brothersProps.length - 1].id, parentId); } else if (myParentId) { addEdgeToGraph(graph, myParentId, parentId); } else { states.notHaveParent.push(parentId); } } else if (parentType === "array") { states.objectsFromArray = [...states.objectsFromArray, states.objectsFromArrayId++]; } const traverseObject = (objectToTraverse: Node, nextType: string) => { traverse({ states, objectToTraverse, parentType: type, myParentId: states.bracketOpen[states.bracketOpen.length - 1]?.id, nextType, }); }; const traverseArray = () => { children.forEach((objectToTraverse, index, array) => { const nextType = array[index + 1]?.type; traverseObject(objectToTraverse, nextType); }); }; if (type === "object") { children.sort(alignChildren); traverseArray(); } else { traverseArray(); } if (type !== "property") { // Add or concatenate brothers node when it is the last parent node if (states.brothersNode.length > 0) { const findBrothersNode = states.brothersNodeProps.find( e => e.parentId === states.brothersParentId && e.objectsFromArrayId === states.objectsFromArray[states.objectsFromArray.length - 1] ); if (findBrothersNode) { const modifyNodes = [...graph.nodes]; const findNodeIndex = modifyNodes.findIndex(e => e.id === findBrothersNode?.id); if (modifyNodes[findNodeIndex] && typeof states.brothersNode === "string") { modifyNodes[findNodeIndex].text += states.brothersNode; const { width, height } = calculateNodeSize(modifyNodes[findNodeIndex].text, false); modifyNodes[findNodeIndex].width = width; modifyNodes[findNodeIndex].height = height; graph.nodes = modifyNodes; states.brothersNode = []; } } else { const brothersNodeId = addNodeToGraph({ graph, text: states.brothersNode }); states.brothersNode = []; if (states.brothersParentId) { addEdgeToGraph(graph, states.brothersParentId, brothersNodeId); } else { states.notHaveParent = [...states.notHaveParent, brothersNodeId]; } const brothersNodeProps = { id: brothersNodeId, parentId: states.brothersParentId, objectsFromArrayId: states.objectsFromArray[states.objectsFromArray.length - 1], }; states.brothersNodeProps = [...states.brothersNodeProps, brothersNodeProps]; } } // Close brackets if (parentType === "array") { if (states.objectsFromArray.length > 0) { states.objectsFromArray.pop(); } } else { if (states.bracketOpen.length > 0) { states.bracketOpen.pop(); } } if (parentId) { const myChildren = graph.edges.filter(edge => edge.from === parentId); const parentIndex = graph.nodes.findIndex(node => node.id === parentId); graph.nodes = graph.nodes.map((node, index) => { if (index === parentIndex) { const childrenCount = myChildren.length; return { ...node, data: { ...node.data, childrenCount } }; } return node; }); } } } export const traverse = ({ objectToTraverse, states, myParentId, nextType, parentType, }: Traverse) => { const graph = states.graph; const { type, children, value } = objectToTraverse; if (!children) { handleNoChildren(value, states, graph, myParentId, parentType, nextType); } else if (children) { handleHasChildren(type, states, graph, children, myParentId, parentType); } };