Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: basic navigation #92

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -7,6 +7,7 @@
"dev": "next dev",
"build": "next build && next export",
"test": "jest",
"watch": "jest --watchAll",
"start": "next start",
"lint": "next lint",
"deploy": "gh-pages -d out -t true"
Expand Down
37 changes: 29 additions & 8 deletions src/components/Graph/index.tsx
Expand Up @@ -7,10 +7,14 @@ import {
import { Canvas, EdgeData, ElkRoot, NodeData, NodeProps } from "reaflow";
import { CustomNode } from "src/components/CustomNode";
import { NodeModal } from "src/containers/Modals/NodeModal";
import { getEdgeNodes } from "src/containers/Editor/LiveEditor/helpers";
import {
getEdgeNodes,
searchSubTree,
} from "src/containers/Editor/LiveEditor/helpers";
import useConfig from "src/hooks/store/useConfig";
import styled from "styled-components";
import shallow from "zustand/shallow";
import { flattenTree, extractTree } from "src/utils/json-editor-parser";

interface LayoutProps {
isWidget: boolean;
Expand Down Expand Up @@ -40,24 +44,30 @@ const MemoizedGraph = React.memo(function Layout({
}: LayoutProps) {
const json = useConfig((state) => state.json);
const [nodes, setNodes] = React.useState<NodeData[]>([]);
const [mainTree, setMainTree] = React.useState([]);
const [edges, setEdges] = React.useState<EdgeData[]>([]);
const [size, setSize] = React.useState({
width: 2000,
height: 2000,
});

const setConfig = useConfig((state) => state.setConfig);
const [expand, layout] = useConfig(
(state) => [state.expand, state.layout],
const [expand, layout, navigationMode] = useConfig(
(state) => [state.expand, state.layout, state.navigationMode],
shallow
);

React.useEffect(() => {
const { nodes, edges } = getEdgeNodes(json, expand);
let parsedJson = JSON.parse(json);
if (!Array.isArray(parsedJson)) parsedJson = [parsedJson];
const mainTree = extractTree(parsedJson);
const flatTree = flattenTree(mainTree);
const { nodes, edges } = getEdgeNodes(flatTree, expand);

setMainTree(mainTree);
setNodes(nodes);
setEdges(edges);
}, [json, expand]);
}, [json, expand, navigationMode]);

const onInit = (ref: ReactZoomPanPinchRef) => {
setConfig("zoomPanPinch", ref);
Expand All @@ -75,10 +85,21 @@ const MemoizedGraph = React.memo(function Layout({

const handleNodeClick = React.useCallback(
(e: React.MouseEvent<SVGElement>, props: NodeProps) => {
setSelectedNode(props.properties.text);
openModal();
if (navigationMode) {
const subTree = searchSubTree(mainTree, props.id);

if (subTree.length) {
const flatTree = flattenTree(subTree);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flattenTree from json-editor-parser.ts is applied to the sub tree whose node is clicked by the user. Here you apply the same function applied on line 64 of this file into a subtree. FRACTAL.

const { nodes, edges } = getEdgeNodes(flatTree, expand);
setNodes(nodes);
setEdges(edges);
}
} else {
setSelectedNode(props.properties.text);
openModal();
}
},
[openModal, setSelectedNode]
[expand, mainTree, navigationMode, openModal, setSelectedNode]
);

const node = React.useCallback(
Expand Down
26 changes: 24 additions & 2 deletions src/components/Sidebar/index.tsx
Expand Up @@ -16,6 +16,7 @@ import {
AiOutlineSave,
AiOutlineFileAdd,
AiOutlineLink,
AiOutlineApartment,
} from "react-icons/ai";

import { Tooltip } from "src/components/Tooltip";
Expand Down Expand Up @@ -144,8 +145,13 @@ export const Sidebar: React.FC = () => {
const [shareVisible, setShareVisible] = React.useState(false);
const { push } = useRouter();

const [expand, performanceMode, layout] = useConfig(
(state) => [state.expand, state.performanceMode, state.layout],
const [expand, performanceMode, layout, navigationMode] = useConfig(
(state) => [
state.expand,
state.performanceMode,
state.layout,
state.navigationMode,
],
shallow
);

Expand Down Expand Up @@ -176,6 +182,15 @@ export const Sidebar: React.FC = () => {
setConfig("performanceMode", !performanceMode);
};

const toggleNavigationMode = () => {
const toastMsg = navigationMode
? "Disabled Navigation Mode"
: "Enabled Navigation Mode";

setConfig("navigationMode", !navigationMode);
toast(toastMsg);
};

const toggleLayout = () => {
const nextLayout = getNextLayout(layout);
setConfig("layout", nextLayout);
Expand All @@ -202,6 +217,13 @@ export const Sidebar: React.FC = () => {
<StyledFlowIcon rotate={rotateLayout(layout)} />
</StyledElement>
</Tooltip>
<Tooltip title="Navigation mode">
<StyledElement onClick={toggleNavigationMode}>
<AiOutlineApartment
color={navigationMode ? "#0073FF" : undefined}
/>
</StyledElement>
</Tooltip>
<Tooltip title={expand ? "Shrink Nodes" : "Expand Nodes"}>
<StyledElement
title="Toggle Expand/Collapse"
Expand Down
190 changes: 190 additions & 0 deletions src/components/__tests__/utils/json-editor-parser/extractTree.test.js
@@ -0,0 +1,190 @@
import { extractTree } from "src/utils/json-editor-parser"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test files


describe("extractTree", () => {
it("takes a parsed JSON and extracts a tree representation of the JSON", () => {
const parsedJson = [
{
"name": "root",
"colors": [
"red",
"green",
"blue"
]
}
]
const tree = [
{
"id": "1",
"text": {
"name": "root"
},
"children": [
{
"id": "2",
"text": "colors",
"parent": true,
"parent_id": "1",
"children": [
{
"id": "3",
"text": "red",
"children": [],
"parent_id": "2",
"parent": false
},
{
"id": "4",
"text": "green",
"children": [],
"parent_id": "2",
"parent": false
},
{
"id": "5",
"text": "blue",
"children": [],
"parent_id": "2",
"parent": false
}
]
}
],
"parent": false,
"parent_id": null
}
]
expect(extractTree(parsedJson)).toStrictEqual(tree)
})

it("simple object with two sibblings arrays", () => {
const simpleObject = {
"name": "root",
"colors": [
"red",
"blue,"
],
"tags": [
"good-first-issue",
"bug"
]
}
const result = [
{
"id": "1",
"text": {
"name": "root"
},
"children": [
{
"id": "2",
"text": "colors",
"parent": true,
"parent_id": "1",
"children": [
{
"id": "4",
"text": "red",
"children": [],
"parent_id": "2",
"parent": false
},
{
"id": "5",
"text": "blue,",
"children": [],
"parent_id": "2",
"parent": false
}
]
},
{
"id": "3",
"text": "tags",
"parent": true,
"parent_id": "1",
"children": [
{
"id": "6",
"text": "good-first-issue",
"children": [],
"parent_id": "3",
"parent": false
},
{
"id": "7",
"text": "bug",
"children": [],
"parent_id": "3",
"parent": false
}
]
}
],
"parent_id": null,
"parent": false
}
]
expect(extractTree(simpleObject)).toStrictEqual(result)
})

it("simple object with no children", () => {
const simpleObject = [
{
"first_name": "jane",
"last_name": "doe"
}
]
const result = [
{
"id": "1",
"text": {
"first_name": "jane",
"last_name": "doe"
},
"children": [],
"parent_id": null,
"parent": false
}
]
expect(extractTree(simpleObject)).toStrictEqual(result)
})

it("simple object with only one element inside array in children", () => {
const simpleObject = [
{
"name": "root",
"colors": [
"red",
]
}
]
const result = [
{
"id": "1",
"text": {
"name": "root"
},
"children": [
{
"id": "2",
"text": "colors",
"parent": true,
"parent_id": "1",
"children": [
{
"id": "3",
"text": "red",
"children": [],
"parent_id": "2",
"parent": false
}
]
}
],
"parent": false,
"parent_id": null
}
]
expect(extractTree(simpleObject)).toStrictEqual(result)
})
})
@@ -0,0 +1,56 @@
import { flattenTree } from "src/utils/json-editor-parser"

describe("flattenTree", () => {
it("takes a tree representation of a JSON and flattens it into a set of nodes and edges", () => {
const tree = [
{
"id": "2",
"text": "colors",
"parent": true,
"parent_id": "1",
"children": [
{
"id": "3",
"text": "red",
"parent_id": "2",
"children": [],
"parent": false
}
]
}
]

const flatTree = [
{
"id": "1",
"text": "parent",
"parent": true
},
{
"id": "2",
"text": "colors",
"parent": true,
"parent_id": "1"
},
{
"id": "3",
"text": "red",
"parent_id": "2",
"parent": false
},
{
"id": "e2-1",
"from": "2",
"to": "1"
},
{
"id": "e2-3",
"from": "2",
"to": "3"
}
]

expect(flattenTree(tree)).toStrictEqual(flatTree)
})
})