mirror of
https://github.com/AykutSarac/jsoncrack.com.git
synced 2025-01-12 19:02:53 +08:00
improve json parser
This commit is contained in:
parent
633f5bb8f1
commit
a94d951c35
@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
import { MdCompareArrows } from "react-icons/md";
|
||||
import { NodeData } from "reaflow/dist/types";
|
||||
import { ConditionalWrapper, CustomNodeProps } from "src/components/CustomNode";
|
||||
import useConfig from "src/hooks/store/useConfig";
|
||||
import useGraph from "src/hooks/store/useGraph";
|
||||
@ -26,12 +25,15 @@ const StyledExpand = styled.button`
|
||||
border-left: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
|
||||
`;
|
||||
|
||||
const TextNode: React.FC<CustomNodeProps<string> & { node: NodeData }> = ({
|
||||
const TextNode: React.FC<
|
||||
CustomNodeProps<string> & { node: NodeData; hasCollapse: boolean }
|
||||
> = ({
|
||||
node,
|
||||
width,
|
||||
height,
|
||||
value,
|
||||
isParent = false,
|
||||
hasCollapse = false,
|
||||
x,
|
||||
y,
|
||||
}) => {
|
||||
@ -60,7 +62,7 @@ const TextNode: React.FC<CustomNodeProps<string> & { node: NodeData }> = ({
|
||||
<ConditionalWrapper condition={performanceMode}>
|
||||
<Styled.StyledText
|
||||
hideCollapse={hideCollapse}
|
||||
parent={isParent}
|
||||
hasCollapse={isParent && hasCollapse}
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
@ -76,7 +78,7 @@ const TextNode: React.FC<CustomNodeProps<string> & { node: NodeData }> = ({
|
||||
</Styled.StyledKey>
|
||||
</Styled.StyledText>
|
||||
</ConditionalWrapper>
|
||||
{isParent && !hideCollapse && (
|
||||
{isParent && hasCollapse && !hideCollapse && (
|
||||
<StyledExpand onClick={handleExpand}>
|
||||
<MdCompareArrows size={18} />
|
||||
</StyledExpand>
|
||||
|
@ -39,11 +39,9 @@ export const CustomNode = (nodeProps: NodeProps) => {
|
||||
<Node {...nodeProps} label={<Label style={baseLabelStyle} />}>
|
||||
{({ width, height, x, y, node }) => {
|
||||
if (properties.text instanceof Object) {
|
||||
const entries = Object.entries<string>(properties.text);
|
||||
|
||||
return (
|
||||
<ObjectNode
|
||||
value={entries}
|
||||
value={properties.text}
|
||||
width={width}
|
||||
height={height}
|
||||
x={x}
|
||||
@ -59,6 +57,7 @@ export const CustomNode = (nodeProps: NodeProps) => {
|
||||
value={properties.text}
|
||||
width={width}
|
||||
height={height}
|
||||
hasCollapse={properties.data.hasChild}
|
||||
x={x}
|
||||
y={y}
|
||||
/>
|
||||
|
@ -26,7 +26,7 @@ export const StyledTextWrapper = styled.div`
|
||||
export const StyledText = styled.div<{
|
||||
width: number;
|
||||
height: number;
|
||||
parent?: boolean;
|
||||
hasCollapse?: boolean;
|
||||
hideCollapse?: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
@ -36,8 +36,8 @@ export const StyledText = styled.div<{
|
||||
height: ${({ height }) => height};
|
||||
min-height: 50;
|
||||
color: ${({ theme }) => theme.TEXT_NORMAL};
|
||||
padding-right: ${({ parent, hideCollapse }) =>
|
||||
parent && !hideCollapse && "20px"};
|
||||
padding-right: ${({ hasCollapse, hideCollapse }) =>
|
||||
hasCollapse && !hideCollapse && "20px"};
|
||||
`;
|
||||
|
||||
export const StyledForeignObject = styled.foreignObject`
|
||||
|
@ -4,19 +4,19 @@ import {
|
||||
TransformComponent,
|
||||
TransformWrapper,
|
||||
} from "react-zoom-pan-pinch";
|
||||
import { Canvas, Edge, ElkRoot, NodeData } from "reaflow";
|
||||
import { Canvas, Edge, ElkRoot } from "reaflow";
|
||||
import { CustomNode } from "src/components/CustomNode";
|
||||
import { NodeModal } from "src/containers/Modals/NodeModal";
|
||||
import { getEdgeNodes } from "src/containers/Editor/LiveEditor/helpers";
|
||||
import useConfig from "src/hooks/store/useConfig";
|
||||
import styled from "styled-components";
|
||||
import shallow from "zustand/shallow";
|
||||
import useGraph from "src/hooks/store/useGraph";
|
||||
import { parser } from "src/utils/jsonParser";
|
||||
|
||||
interface LayoutProps {
|
||||
isWidget: boolean;
|
||||
openModal: () => void;
|
||||
setSelectedNode: (node: object) => void;
|
||||
setSelectedNode: (node: [string, string][]) => void;
|
||||
}
|
||||
|
||||
const StyledEditorWrapper = styled.div<{ isWidget: boolean }>`
|
||||
@ -40,6 +40,7 @@ const MemoizedGraph = React.memo(function Layout({
|
||||
setSelectedNode,
|
||||
}: LayoutProps) {
|
||||
const json = useConfig((state) => state.json);
|
||||
const setConfig = useConfig((state) => state.setConfig);
|
||||
const nodes = useGraph((state) => state.nodes);
|
||||
const edges = useGraph((state) => state.edges);
|
||||
const setGraphValue = useGraph((state) => state.setGraphValue);
|
||||
@ -49,7 +50,6 @@ const MemoizedGraph = React.memo(function Layout({
|
||||
height: 2000,
|
||||
});
|
||||
|
||||
const setConfig = useConfig((state) => state.setConfig);
|
||||
const [expand, layout] = useConfig(
|
||||
(state) => [state.expand, state.layout],
|
||||
shallow
|
||||
@ -63,17 +63,21 @@ const MemoizedGraph = React.memo(function Layout({
|
||||
[openModal, setSelectedNode]
|
||||
);
|
||||
|
||||
const onInit = (ref: ReactZoomPanPinchRef) => {
|
||||
setConfig("zoomPanPinch", ref);
|
||||
};
|
||||
const onInit = React.useCallback(
|
||||
(ref: ReactZoomPanPinchRef) => {
|
||||
setConfig("zoomPanPinch", ref);
|
||||
},
|
||||
[setConfig]
|
||||
);
|
||||
|
||||
const onLayoutChange = (layout: ElkRoot) => {
|
||||
if (layout.width && layout.height)
|
||||
setSize({ width: layout.width, height: layout.height });
|
||||
};
|
||||
const onLayoutChange = React.useCallback((layout: ElkRoot) => {
|
||||
if (layout.width && layout.height) {
|
||||
setSize({ width: layout.width + 400, height: layout.height + 400 });
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const { nodes, edges } = getEdgeNodes(json, expand);
|
||||
const { nodes, edges } = parser(json, expand);
|
||||
|
||||
setGraphValue("nodes", nodes);
|
||||
setGraphValue("edges", edges);
|
||||
@ -82,13 +86,13 @@ const MemoizedGraph = React.memo(function Layout({
|
||||
return (
|
||||
<StyledEditorWrapper isWidget={isWidget}>
|
||||
<TransformWrapper
|
||||
onInit={onInit}
|
||||
maxScale={1.8}
|
||||
minScale={0.4}
|
||||
maxScale={2}
|
||||
minScale={0.5}
|
||||
initialScale={0.7}
|
||||
wheel={{ step: 0.1 }}
|
||||
wheel={{ step: 0.05 }}
|
||||
zoomAnimation={{ animationType: "linear" }}
|
||||
doubleClick={{ disabled: true }}
|
||||
onInit={onInit}
|
||||
>
|
||||
<TransformComponent
|
||||
wrapperStyle={{
|
||||
@ -100,23 +104,23 @@ const MemoizedGraph = React.memo(function Layout({
|
||||
<Canvas
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
maxWidth={size.width + 400}
|
||||
maxHeight={size.height + 400}
|
||||
maxWidth={size.width}
|
||||
maxHeight={size.height}
|
||||
direction={layout}
|
||||
key={layout}
|
||||
onLayoutChange={onLayoutChange}
|
||||
node={(props) => (
|
||||
<CustomNode {...props} onClick={handleNodeClick} />
|
||||
)}
|
||||
edge={(props) => (
|
||||
<Edge {...props} containerClassName={`edge-${props.id}`} />
|
||||
)}
|
||||
key={layout}
|
||||
zoomable={false}
|
||||
animated={false}
|
||||
readonly={true}
|
||||
dragEdge={null}
|
||||
dragNode={null}
|
||||
fit={true}
|
||||
node={(props) => (
|
||||
<CustomNode {...props} onClick={handleNodeClick} />
|
||||
)}
|
||||
edge={(props) => (
|
||||
<Edge {...props} containerClassName={`edge-${props.id}`} />
|
||||
)}
|
||||
/>
|
||||
</TransformComponent>
|
||||
</TransformWrapper>
|
||||
@ -126,7 +130,9 @@ const MemoizedGraph = React.memo(function Layout({
|
||||
|
||||
export const Graph = ({ isWidget = false }: { isWidget?: boolean }) => {
|
||||
const [isModalVisible, setModalVisible] = React.useState(false);
|
||||
const [selectedNode, setSelectedNode] = React.useState<object>({});
|
||||
const [selectedNode, setSelectedNode] = React.useState<[string, string][]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const openModal = React.useCallback(() => setModalVisible(true), []);
|
||||
|
||||
|
@ -2,7 +2,6 @@ import React from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import Link from "next/link";
|
||||
import styled from "styled-components";
|
||||
import { CanvasDirection } from "reaflow";
|
||||
import { TiFlowMerge } from "react-icons/ti";
|
||||
import { CgArrowsMergeAltH, CgArrowsShrinkH } from "react-icons/cg";
|
||||
import {
|
||||
@ -21,10 +20,10 @@ import { ImportModal } from "src/containers/Modals/ImportModal";
|
||||
import { ClearModal } from "src/containers/Modals/ClearModal";
|
||||
import { ShareModal } from "src/containers/Modals/ShareModal";
|
||||
import useConfig from "src/hooks/store/useConfig";
|
||||
import { getNextLayout } from "src/containers/Editor/LiveEditor/helpers";
|
||||
import { HiHeart } from "react-icons/hi";
|
||||
import shallow from "zustand/shallow";
|
||||
import { MdCenterFocusWeak } from "react-icons/md";
|
||||
import { getNextLayout } from "src/utils/getNextLayout";
|
||||
|
||||
const StyledSidebar = styled.div`
|
||||
display: flex;
|
||||
@ -129,7 +128,7 @@ const StyledLogo = styled.div`
|
||||
color: ${({ theme }) => theme.FULL_WHITE};
|
||||
`;
|
||||
|
||||
function rotateLayout(layout: CanvasDirection) {
|
||||
function rotateLayout(layout: "LEFT" | "RIGHT" | "DOWN" | "UP") {
|
||||
if (layout === "LEFT") return 90;
|
||||
if (layout === "UP") return 180;
|
||||
if (layout === "RIGHT") return 270;
|
||||
|
@ -1,87 +0,0 @@
|
||||
import { CanvasDirection, NodeData, EdgeData } from "reaflow";
|
||||
import { parser } from "src/utils/json-editor-parser";
|
||||
|
||||
export function getEdgeNodes(
|
||||
graph: string,
|
||||
isExpanded: boolean = true
|
||||
): {
|
||||
nodes: NodeData[];
|
||||
edges: EdgeData[];
|
||||
} {
|
||||
const elements = parser(JSON.parse(graph));
|
||||
|
||||
let nodes: NodeData[] = [],
|
||||
edges: EdgeData[] = [];
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const el = elements[i];
|
||||
|
||||
if (isNode(el)) {
|
||||
const text = renderText(el.text);
|
||||
const lines = text.split("\n");
|
||||
const lineLengths = lines
|
||||
.map((line) => line.length)
|
||||
.sort((a, b) => a - b);
|
||||
const longestLine = lineLengths.reverse()[0];
|
||||
|
||||
const height = lines.length * 17.8 < 30 ? 40 : lines.length * 17.8;
|
||||
|
||||
nodes.push({
|
||||
id: el.id,
|
||||
text: el.text,
|
||||
data: {
|
||||
isParent: el.parent,
|
||||
},
|
||||
width: isExpanded ? 35 + longestLine * 8 + (el.parent && 60) : 180,
|
||||
height,
|
||||
});
|
||||
} else {
|
||||
edges.push(el);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nodes,
|
||||
edges,
|
||||
};
|
||||
}
|
||||
|
||||
export function getNextLayout(layout: CanvasDirection) {
|
||||
switch (layout) {
|
||||
case "RIGHT":
|
||||
return "DOWN";
|
||||
|
||||
case "DOWN":
|
||||
return "LEFT";
|
||||
|
||||
case "LEFT":
|
||||
return "UP";
|
||||
|
||||
default:
|
||||
return "RIGHT";
|
||||
}
|
||||
}
|
||||
|
||||
function renderText(value: string | object) {
|
||||
if (value instanceof Object) {
|
||||
let temp = "";
|
||||
const entries = Object.entries(value);
|
||||
|
||||
if (Object.keys(value).every((val) => !isNaN(+val))) {
|
||||
return Object.values(value).join("");
|
||||
}
|
||||
|
||||
entries.forEach((entry) => {
|
||||
temp += `${entry[0]}: ${String(entry[1])}\n`;
|
||||
});
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function isNode(element: NodeData | EdgeData) {
|
||||
if ("text" in element) return true;
|
||||
return false;
|
||||
}
|
@ -30,9 +30,13 @@ export const NodeModal = ({
|
||||
visible,
|
||||
closeModal,
|
||||
}: NodeModalProps) => {
|
||||
const nodeData = Array.isArray(selectedNode)
|
||||
? Object.fromEntries(selectedNode)
|
||||
: selectedNode;
|
||||
|
||||
const handleClipboard = () => {
|
||||
toast.success("Content copied to clipboard!");
|
||||
navigator.clipboard.writeText(JSON.stringify(selectedNode));
|
||||
navigator.clipboard.writeText(JSON.stringify(nodeData));
|
||||
closeModal();
|
||||
};
|
||||
|
||||
@ -42,8 +46,8 @@ export const NodeModal = ({
|
||||
<Modal.Content>
|
||||
<StyledTextarea
|
||||
defaultValue={JSON.stringify(
|
||||
selectedNode,
|
||||
(k, v) => {
|
||||
nodeData,
|
||||
(_, v) => {
|
||||
if (typeof v === "string") return v.replaceAll('"', "");
|
||||
return v;
|
||||
},
|
||||
|
@ -1,7 +1,6 @@
|
||||
import create from "zustand";
|
||||
import { defaultJson } from "src/constants/data";
|
||||
import { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
|
||||
import { CanvasDirection } from "reaflow";
|
||||
|
||||
interface ConfigActions {
|
||||
setJson: (json: string) => void;
|
||||
@ -15,7 +14,7 @@ interface ConfigActions {
|
||||
export interface Config {
|
||||
json: string;
|
||||
cursorMode: "move" | "navigation";
|
||||
layout: CanvasDirection;
|
||||
layout: "LEFT" | "RIGHT" | "DOWN" | "UP";
|
||||
expand: boolean;
|
||||
hideEditor: boolean;
|
||||
zoomPanPinch?: ReactZoomPanPinchRef;
|
||||
@ -34,7 +33,7 @@ const initialStates: Config = {
|
||||
const useConfig = create<Config & ConfigActions>()((set, get) => ({
|
||||
...initialStates,
|
||||
getJson: () => get().json,
|
||||
setJson: (json: string) => set((state) => ({ ...state, json })),
|
||||
setJson: (json: string) => set({ json }),
|
||||
zoomIn: () => {
|
||||
const zoomPanPinch = get().zoomPanPinch;
|
||||
if (zoomPanPinch) {
|
||||
@ -57,13 +56,10 @@ const useConfig = create<Config & ConfigActions>()((set, get) => ({
|
||||
},
|
||||
centerView: () => {
|
||||
const zoomPanPinch = get().zoomPanPinch;
|
||||
if (zoomPanPinch) zoomPanPinch.resetTransform();
|
||||
if (zoomPanPinch) zoomPanPinch.centerView(0.6);
|
||||
},
|
||||
setConfig: (setting: keyof Config, value: unknown) =>
|
||||
set((state) => ({
|
||||
...state,
|
||||
[setting]: value,
|
||||
})),
|
||||
set({ [setting]: value }),
|
||||
}));
|
||||
|
||||
export default useConfig;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import create from "zustand";
|
||||
import { EdgeData, NodeData } from "reaflow/dist/types";
|
||||
import { Graph } from "src/components/Graph";
|
||||
import { getChildrenEdges } from "src/utils/getChildrenEdges";
|
||||
import { getOutgoers } from "src/utils/getOutgoers";
|
||||
@ -24,7 +23,7 @@ const initialStates: Graph = {
|
||||
collapsedEdges: [],
|
||||
};
|
||||
|
||||
const useGraph = create<Graph & GraphActions>((set) => ({
|
||||
const useGraph = create<Graph & GraphActions>((set, get) => ({
|
||||
...initialStates,
|
||||
setGraphValue: (key, value) =>
|
||||
set({
|
||||
@ -32,38 +31,34 @@ const useGraph = create<Graph & GraphActions>((set) => ({
|
||||
collapsedEdges: [],
|
||||
[key]: value,
|
||||
}),
|
||||
expandNodes: (nodeId) =>
|
||||
set((state) => {
|
||||
const childrenNodes = getOutgoers(nodeId, state.nodes, state.edges);
|
||||
const childrenEdges = getChildrenEdges(childrenNodes, state.edges);
|
||||
expandNodes: (nodeId) => {
|
||||
const childrenNodes = getOutgoers(nodeId, get().nodes, get().edges);
|
||||
const childrenEdges = getChildrenEdges(childrenNodes, get().edges);
|
||||
|
||||
const nodeIds = childrenNodes.map((node) => node.id);
|
||||
const edgeIds = childrenEdges.map((edge) => edge.id);
|
||||
const nodeIds = childrenNodes.map((node) => node.id);
|
||||
const edgeIds = childrenEdges.map((edge) => edge.id);
|
||||
|
||||
return {
|
||||
...state,
|
||||
collapsedNodes: state.collapsedNodes.filter(
|
||||
(nodeId) => !nodeIds.includes(nodeId)
|
||||
),
|
||||
collapsedEdges: state.collapsedEdges.filter(
|
||||
(edgeId) => !edgeIds.includes(edgeId)
|
||||
),
|
||||
};
|
||||
}),
|
||||
collapseNodes: (nodeId) =>
|
||||
set((state) => {
|
||||
const childrenNodes = getOutgoers(nodeId, state.nodes, state.edges);
|
||||
const childrenEdges = getChildrenEdges(childrenNodes, state.edges);
|
||||
set({
|
||||
collapsedNodes: get().collapsedNodes.filter(
|
||||
(nodeId) => !nodeIds.includes(nodeId)
|
||||
),
|
||||
collapsedEdges: get().collapsedEdges.filter(
|
||||
(edgeId) => !edgeIds.includes(edgeId)
|
||||
),
|
||||
});
|
||||
},
|
||||
collapseNodes: (nodeId) => {
|
||||
const childrenNodes = getOutgoers(nodeId, get().nodes, get().edges);
|
||||
const childrenEdges = getChildrenEdges(childrenNodes, get().edges);
|
||||
|
||||
const nodeIds = childrenNodes.map((node) => node.id);
|
||||
const edgeIds = childrenEdges.map((edge) => edge.id);
|
||||
const nodeIds = childrenNodes.map((node) => node.id);
|
||||
const edgeIds = childrenEdges.map((edge) => edge.id);
|
||||
|
||||
return {
|
||||
...state,
|
||||
collapsedNodes: state.collapsedNodes.concat(nodeIds),
|
||||
collapsedEdges: state.collapsedEdges.concat(edgeIds),
|
||||
};
|
||||
}),
|
||||
set({
|
||||
collapsedNodes: get().collapsedNodes.concat(nodeIds),
|
||||
collapsedEdges: get().collapsedEdges.concat(edgeIds),
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
export default useGraph;
|
||||
|
33
src/types.d.ts
vendored
Normal file
33
src/types.d.ts
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
type CanvasDirection = "LEFT" | "RIGHT" | "DOWN" | "UP"
|
||||
|
||||
interface NodeData<T = any> {
|
||||
id: string;
|
||||
disabled?: boolean;
|
||||
text?: any;
|
||||
height?: number;
|
||||
width?: number;
|
||||
parent?: string;
|
||||
ports?: PortData[];
|
||||
icon?: IconData;
|
||||
nodePadding?: number | [number, number] | [number, number, number, number];
|
||||
data?: T;
|
||||
className?: string;
|
||||
layoutOptions?: ElkNodeLayoutOptions;
|
||||
selectionDisabled?: boolean;
|
||||
}
|
||||
|
||||
interface EdgeData<T = any> {
|
||||
id: string;
|
||||
disabled?: boolean;
|
||||
text?: any;
|
||||
from?: string;
|
||||
to?: string;
|
||||
fromPort?: string;
|
||||
toPort?: string;
|
||||
data?: T;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
arrowHeadType?: any;
|
||||
parent?: string;
|
||||
selectionDisabled?: boolean;
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
import { NodeData, EdgeData } from "reaflow/dist/types";
|
||||
|
||||
export const getChildrenEdges = (
|
||||
nodes: NodeData[],
|
||||
edges: EdgeData[]
|
||||
|
15
src/utils/getNextLayout.ts
Normal file
15
src/utils/getNextLayout.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export function getNextLayout(layout: "LEFT" | "RIGHT" | "DOWN" | "UP") {
|
||||
switch (layout) {
|
||||
case "RIGHT":
|
||||
return "DOWN";
|
||||
|
||||
case "DOWN":
|
||||
return "LEFT";
|
||||
|
||||
case "LEFT":
|
||||
return "UP";
|
||||
|
||||
default:
|
||||
return "RIGHT";
|
||||
}
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
import { NodeData, EdgeData } from "reaflow/dist/types";
|
||||
|
||||
export const getOutgoers = (
|
||||
nodeId: string,
|
||||
nodes: NodeData[],
|
||||
|
@ -1,105 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2022 Aykut Saraç - All Rights Reserved
|
||||
*/
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
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, nextId: () => string) {
|
||||
if (!(object instanceof Object)) object = [object];
|
||||
|
||||
return Object.entries(object)
|
||||
.filter(filterChild)
|
||||
.flatMap(([k, v]) => {
|
||||
// const isObject = v instanceof Object && !Array.isArray(v);
|
||||
|
||||
// if (isObject) {
|
||||
// return [
|
||||
// {
|
||||
// id: nextId(),
|
||||
// text: k,
|
||||
// parent: true,
|
||||
// children: generateChildren(v, nextId),
|
||||
// },
|
||||
// ];
|
||||
// }
|
||||
|
||||
return [
|
||||
{
|
||||
id: nextId(),
|
||||
text: k,
|
||||
parent: true,
|
||||
children: extract(v, nextId),
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
function generateNodeData(object: Object | number) {
|
||||
const isObject = object instanceof Object;
|
||||
|
||||
if (isObject) {
|
||||
const entries = Object.entries(object).filter(filterValues);
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
return String(object);
|
||||
}
|
||||
|
||||
const extract = (
|
||||
os: string[] | object[] | null,
|
||||
nextId = (
|
||||
(id) => () =>
|
||||
String(++id)
|
||||
)(0)
|
||||
) => {
|
||||
if (!os) return [];
|
||||
|
||||
return [os].flat().map((o) => ({
|
||||
id: nextId(),
|
||||
text: generateNodeData(o),
|
||||
children: generateChildren(o, nextId),
|
||||
parent: false,
|
||||
}));
|
||||
};
|
||||
|
||||
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),
|
||||
]);
|
||||
};
|
||||
|
||||
export const parser = (input: string | string[]) => {
|
||||
try {
|
||||
if (!Array.isArray(input)) input = [input];
|
||||
|
||||
const mappedElements = extract(input);
|
||||
const res = [...flatten(mappedElements), ...relationships(mappedElements)];
|
||||
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("An error occured while parsing JSON data!");
|
||||
return [];
|
||||
}
|
||||
};
|
159
src/utils/jsonParser.ts
Normal file
159
src/utils/jsonParser.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
const calculateSize = (
|
||||
text: string | [string, string][],
|
||||
isParent = false,
|
||||
isExpanded: boolean
|
||||
) => {
|
||||
let value = "";
|
||||
|
||||
if (typeof text === "string") value = text;
|
||||
else value = text.map(([k, v]) => `${k}: ${v}`).join("\n");
|
||||
|
||||
const lineCount = value.split("\n");
|
||||
const lineLengths = lineCount.map((line) => line.length);
|
||||
const longestLine = lineLengths.sort((a, b) => b - a)[0];
|
||||
|
||||
const getWidth = () => {
|
||||
if (isExpanded) return 35 + longestLine * 8 + (isParent ? 60 : 0);
|
||||
if (isParent) return 150;
|
||||
return 200;
|
||||
};
|
||||
|
||||
const getHeight = () => {
|
||||
if (lineCount.length * 17.8 < 30) return 40;
|
||||
return (lineCount.length + 1) * 18;
|
||||
};
|
||||
|
||||
return {
|
||||
width: getWidth(),
|
||||
height: getHeight(),
|
||||
};
|
||||
};
|
||||
|
||||
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,
|
||||
isExpanded = true,
|
||||
nextId: () => string
|
||||
) {
|
||||
if (!(object instanceof Object)) object = [object];
|
||||
|
||||
return Object.entries(object)
|
||||
.filter(filterChild)
|
||||
.flatMap(([key, v]) => {
|
||||
const { width, height } = calculateSize(key, true, isExpanded);
|
||||
const children = extract(v, isExpanded, nextId);
|
||||
|
||||
return [
|
||||
{
|
||||
id: nextId(),
|
||||
text: key,
|
||||
children,
|
||||
width,
|
||||
height,
|
||||
data: {
|
||||
isParent: true,
|
||||
hasChild: !!children.length,
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
function generateNodeData(object: Object) {
|
||||
if (object instanceof Object) {
|
||||
const entries = Object.entries(object).filter(filterValues);
|
||||
return entries;
|
||||
}
|
||||
|
||||
return String(object);
|
||||
}
|
||||
|
||||
const extract = (
|
||||
os: string[] | object[] | null,
|
||||
isExpanded = true,
|
||||
nextId = (
|
||||
(id) => () =>
|
||||
String(++id)
|
||||
)(0)
|
||||
) => {
|
||||
if (!os) return [];
|
||||
|
||||
return [os].flat().map((o) => {
|
||||
const text = generateNodeData(o);
|
||||
const { width, height } = calculateSize(text, false, isExpanded);
|
||||
|
||||
return {
|
||||
id: nextId(),
|
||||
text,
|
||||
width,
|
||||
height,
|
||||
children: generateChildren(o, isExpanded, nextId),
|
||||
data: {
|
||||
isParent: false,
|
||||
hasChild: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
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),
|
||||
]);
|
||||
};
|
||||
|
||||
export const parser = (jsonStr: string, isExpanded = true) => {
|
||||
try {
|
||||
let json = JSON.parse(jsonStr);
|
||||
if (!Array.isArray(json)) json = [json];
|
||||
const nodes: NodeData[] = [];
|
||||
const edges: EdgeData[] = [];
|
||||
|
||||
const mappedElements = extract(json, isExpanded);
|
||||
const res = [...flatten(mappedElements), ...relationships(mappedElements)];
|
||||
|
||||
res.forEach((data) => {
|
||||
if (isNode(data)) {
|
||||
nodes.push(data);
|
||||
} else {
|
||||
edges.push(data);
|
||||
}
|
||||
});
|
||||
|
||||
return { nodes, edges };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("An error occured while parsing JSON data!");
|
||||
return {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function isNode(element: NodeData | EdgeData) {
|
||||
if ("text" in element) return true;
|
||||
return false;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user