Browse Source

Merge pull request #4 from AykutSarac/reaflow-implementation

Reaflow Implementation [v1.2.0-beta]
Aykut Saraç 3 years ago
parent
commit
0c37312277

+ 3 - 0
.travis.yml

@@ -8,6 +8,9 @@ cache:
 directories:
   - node_modules
   - .next
+branches:
+  only:
+  - main
 script:
   - npm run test
   - npm run build

+ 7 - 3
next.config.js

@@ -1,11 +1,15 @@
+const withTM = require('next-transpile-modules')(['reaflow']);
+
 /** @type {import('next').NextConfig} */
-const nextConfig = {
+const nextConfig = withTM({
   exportPathMap: () => ({
     '/': { page: '/' },
     '/editor': { page: '/editor' },
   }),
-  reactStrictMode: true,
   trailingSlash: true,
-};
+  experimental: {
+    esmExternals: 'loose'
+  }
+});
 
 module.exports = nextConfig;

+ 4 - 2
package.json

@@ -1,7 +1,7 @@
 {
   "name": "json-visio-next",
   "private": true,
-  "version": "v1.0.0-beta",
+  "version": "v1.2.0-beta",
   "homepage": "https://aykutsarac.github.io",
   "scripts": {
     "dev": "next dev",
@@ -14,12 +14,14 @@
   "dependencies": {
     "dagre": "^0.8.5",
     "next": "12.0.9",
+    "next-transpile-modules": "^9.0.0",
     "react": "17.0.2",
     "react-dom": "17.0.2",
-    "react-flow-renderer": "^9.7.3",
     "react-icons": "^4.3.1",
     "react-json-editor-ajrm": "^2.5.13",
     "react-split-pane": "^0.1.92",
+    "react-zoom-pan-pinch": "^2.1.3",
+    "reaflow": "^4.2.15",
     "styled-components": "^5.3.3",
     "usehooks-ts": "^2.3.0"
   },

+ 3 - 2
src/components/Footer/index.tsx

@@ -1,7 +1,8 @@
-import Link from "next/link";
 import React from "react";
-import { AiFillGithub, AiOutlineTwitter } from "react-icons/ai";
+import Link from "next/link";
 import styled from "styled-components";
+import { AiFillGithub, AiOutlineTwitter } from "react-icons/ai";
+
 import pkg from "../../../package.json";
 
 const StyledFooter = styled.footer`

+ 1 - 1
src/components/Navbar/index.tsx

@@ -1,5 +1,5 @@
-import Link from "next/link";
 import React from "react";
+import Link from "next/link";
 import styled from "styled-components";
 
 interface NavbarProps {

+ 6 - 5
src/components/Sidebar/index.tsx

@@ -1,6 +1,9 @@
-import Link from "next/link";
 import React from "react";
+import Link from "next/link";
 import styled from "styled-components";
+import { useLocalStorage } from "usehooks-ts";
+import { FaFileImport, FaMap } from "react-icons/fa";
+import { MdAutoGraph } from "react-icons/md";
 import {
   AiFillHome,
   AiOutlineClear,
@@ -8,9 +11,7 @@ import {
   AiOutlineTwitter,
   AiFillControl,
 } from "react-icons/ai";
-import { FaFileImport, FaMap } from "react-icons/fa";
-import { MdAutoGraph } from "react-icons/md";
-import { useLocalStorage } from "usehooks-ts";
+
 import { defaultValue } from "src/containers/JsonEditor";
 import { getNextLayout } from "src/containers/LiveEditor/helpers";
 import { StorageConfig } from "src/typings/global";
@@ -91,7 +92,7 @@ export const Sidebar = () => {
   const [jsonFile, setJsonFile] = React.useState<File | null>(null);
   const [json, setJson] = useLocalStorage("json", JSON.stringify(defaultValue));
   const [config, setConfig] = useLocalStorage<StorageConfig>("config", {
-    layout: "RL",
+    layout: "LEFT",
     minimap: true,
     controls: true,
   });

+ 9 - 5
src/containers/JsonEditor/index.tsx

@@ -54,12 +54,13 @@ export const defaultValue = [
 ];
 
 export const JsonEditor: React.FC = () => {
+  const [clear, setClear] = React.useState(0);
   const [json, setJson] = useLocalStorage("json", JSON.stringify(defaultValue));
-  const [initialJson, setInitialJson] = React.useState(json);
+  const initialJson = React.useMemo(() => JSON.parse(json), [clear]);
 
   React.useEffect(() => {
-    setInitialJson(json);
-  }, []);
+    if (json === "[]") setClear((c) => c + 1);
+  }, [json]);
 
   React.useEffect(() => {
     const element = document.querySelector(
@@ -71,12 +72,15 @@ export const JsonEditor: React.FC = () => {
   }, []);
 
   const handleChange = (data: JsonData) => {
-    if (!data.error) setJson(data.json);
+    if (!data.error) {
+      if (data.json === "") return setJson("[]");
+      setJson(data.json);
+    }
   };
 
   return (
     <StyledJSONInput
-      placeholder={JSON.parse(initialJson)}
+      placeholder={initialJson}
       onChange={handleChange}
       locale={locale}
       height="100%"

+ 79 - 0
src/containers/LiveEditor/CustomNode.tsx

@@ -0,0 +1,79 @@
+import React, { memo } from "react";
+import { Label, Node, Port, NodeChildProps, NodeProps } from "reaflow";
+import styled from "styled-components";
+
+const StyledNode = styled(Node)`
+  stroke: black;
+  fill: ${({ theme }) => theme.BLACK_PRIMARY};
+  stroke-width: 1;
+`;
+
+const StyledTextWrapper = styled.div`
+  position: absolute;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: 12px;
+  width: 100%;
+  height: 100%;
+`;
+
+const StyledText = styled.pre<{ width: number; height: number }>`
+  width: ${({ width }) => width};
+  height: ${({ height }) => height};
+  color: ${({ theme }) => theme.SILVER};
+`;
+
+const StyledForeignObject = styled.foreignObject<{
+  width: number;
+  height: number;
+}>`
+  position: "relative" !important;
+  pointer-events: "none" !important;
+  width: ${({ width }) => width + "px"};
+  height: ${({ height }) => height + "px"};
+`;
+
+const baseLabelStyle = {
+  fill: "transparent",
+  stroke: "transparent",
+  strokeWidth: 0,
+};
+
+const basePortStyle = {
+  fill: "black",
+};
+
+const CustomNode = ({ nodeProps }) => {
+  const { properties: data } = nodeProps;
+
+  return (
+    <StyledNode
+      label={<Label style={baseLabelStyle} />}
+      port={<Port style={basePortStyle} rx={10} ry={10} />}
+      {...nodeProps}
+    >
+      {(nodeProps: NodeChildProps) => {
+        const { width, height } = nodeProps;
+
+        return (
+          <StyledForeignObject width={width} height={height} x={0} y={0}>
+            <StyledTextWrapper>
+              <StyledText width={width} height={height}>
+                {data.text}
+              </StyledText>
+            </StyledTextWrapper>
+          </StyledForeignObject>
+        );
+      }}
+    </StyledNode>
+  );
+};
+
+export const NodeWrapper = (nodeProps: NodeProps) => {
+  return nodeProps.properties?.data?.type ? (
+    <CustomNode nodeProps={nodeProps} />
+  ) : (
+    <Node {...nodeProps} />
+  );
+};

+ 0 - 77
src/containers/LiveEditor/FlowWrapper.tsx

@@ -1,77 +0,0 @@
-import React from "react";
-import ReactFlow, {
-  ControlButton,
-  Controls,
-  Elements,
-  MiniMap,
-  NodeExtent,
-  OnLoadParams,
-} from "react-flow-renderer";
-import { StorageConfig } from "src/typings/global";
-import { useLocalStorage } from "usehooks-ts";
-import { defaultValue } from "../JsonEditor";
-import { getLayout, getLayoutPosition } from "./helpers";
-import { CustomNodeComponent } from "./Node";
-
-const nodeExtent: NodeExtent = [
-  [0, 0],
-  [1000, 1000],
-];
-
-const nodeTypes = {
-  special: CustomNodeComponent,
-};
-
-export const FlowWrapper: React.FC = () => {
-  const [json] = useLocalStorage("json", JSON.stringify(defaultValue));
-  const [config] = useLocalStorage<StorageConfig>("config", {
-    layout: "RL",
-    minimap: true,
-    controls: true,
-  });
-
-  const [elements, setElements] = React.useState<Elements>([]);
-  const [rfInstance, setRfInstance] = React.useState<OnLoadParams | null>(null);
-  const [valid, setValid] = React.useState(true);
-
-  const handleClick = () => {
-    setElements(getLayoutPosition(config.layout, elements));
-  };
-
-  React.useEffect(() => {
-    if (rfInstance) rfInstance.fitView();
-  }, [rfInstance]);
-
-  React.useEffect(() => {
-    try {
-      const layoutedElements = getLayout(config.layout, json);
-      setElements(layoutedElements);
-      setValid(true);
-    } catch (error) {
-      setValid(false);
-    }
-  }, [rfInstance, json, config]);
-
-  if (!valid) return null;
-
-  return (
-    <ReactFlow
-      nodeExtent={nodeExtent}
-      elements={elements}
-      nodeTypes={nodeTypes}
-      onLoad={setRfInstance}
-    >
-      {config.minimap && <MiniMap />}
-      {config.controls && (
-        <Controls>
-          <ControlButton
-            onClick={handleClick}
-            style={{ gridColumn: "1 / 3", width: "auto" }}
-          >
-            Format
-          </ControlButton>
-        </Controls>
-      )}
-    </ReactFlow>
-  );
-};

+ 0 - 96
src/containers/LiveEditor/Node.tsx

@@ -1,96 +0,0 @@
-import React from "react";
-import { Handle, Position } from "react-flow-renderer";
-import styled from "styled-components";
-
-type Data = { label: string | object };
-
-const StyledWrapper = styled.div<{
-  isArray?: boolean;
-  isElement?: boolean;
-  circle?: boolean;
-}>`
-  background: ${({ theme }) => theme.BLACK_PRIMARY};
-  border: 1px solid ${({ theme }) => theme.BLACK};
-  border-radius: 5px;
-  padding: 16px;
-  color: ${({ theme, isArray, isElement }) =>
-    isArray ? theme.SEAGREEN : isElement && theme.ORANGE};
-  width: ${({ circle }) => (circle ? "20px" : "auto")};
-  height: ${({ circle }) => (circle ? "20px" : "auto")};
-  min-width: ${({ circle }) => !circle && "100px"};
-  max-width: 400px;
-  text-align: ${({ isArray }) => isArray && "center"};
-  border-radius: ${({ circle }) => circle && "50%"};
-`;
-
-const StyledKey = styled.span`
-  color: ${({ theme }) => theme.BLURPLE};
-`;
-
-const StyledLineWrapper = styled.div`
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-`;
-
-export const CustomNodeComponent: React.FC<{ data: Data; id: string }> = ({
-  id,
-  data,
-}) => {
-  const { label: json } = data;
-
-  if (Object.keys(json).length === 0) {
-    return (
-      <StyledWrapper circle>
-        <Handle type="source" position={Position.Left} />
-        <Handle type="target" position={Position.Right} />
-      </StyledWrapper>
-    );
-  }
-
-  if (typeof json === "string") {
-    return (
-      <StyledWrapper isArray>
-        {json}
-        <Handle type="source" position={Position.Left} />
-        <Handle type="target" position={Position.Right} />
-      </StyledWrapper>
-    );
-  }
-
-  if (json instanceof Object) {
-    const keyPairs = Object.entries(json);
-
-    // temporary solution for array items
-    if (Object.keys(json).every((val) => !isNaN(+val))) {
-      return (
-        <StyledWrapper isElement>
-          {Object.values(json).join("")}
-          <Handle type="source" position={Position.Left} />
-          <Handle type="target" position={Position.Right} />
-        </StyledWrapper>
-      );
-    }
-
-    return (
-      <StyledWrapper>
-        {keyPairs.map((entries) => {
-          const isValidValue =
-            typeof entries[1] === "string" || typeof entries[1] === "number";
-
-          return (
-            <StyledLineWrapper key={entries[0]}>
-              <StyledKey>{entries[0]}: </StyledKey>
-              {isValidValue && entries[1]}
-            </StyledLineWrapper>
-          );
-        })}
-
-        <Handle type="source" id={id} position={Position.Left} />
-        <Handle type="target" id={id} position={Position.Right} />
-      </StyledWrapper>
-    );
-  }
-
-  return null;
-};

+ 58 - 44
src/containers/LiveEditor/helpers.ts

@@ -1,64 +1,78 @@
-import dagre from "dagre";
-import { Elements, isNode, Position } from "react-flow-renderer";
-import { Layout } from "src/typings/global";
+import { CanvasDirection, NodeData, EdgeData } from "reaflow";
 import { parser } from "src/utils/json-editor-parser";
 
-const dagreGraph = new dagre.graphlib.Graph();
-dagreGraph.setDefaultEdgeLabel(() => ({}));
+export function getEdgeNodes(graph: any): any {
+  graph = JSON.parse(graph);
+  const elements = parser(graph);
 
-export const getLayoutPosition = (direction: string, elements: Elements, dynamic = false) => {
-  const isHorizontal = direction === "LR";
-  dagreGraph.setGraph({ rankdir: direction });
+  let nodes: NodeData[] = [],
+    edges: EdgeData[] = [];
+
+  for (let i = 0; i < elements.length; i++) {
+    const el = elements[i];
 
-  elements.forEach((el) => {
     if (isNode(el)) {
-      dagreGraph.setNode(el.id, {
-        width: dynamic ? el.__rf.width : 400,
-        height: dynamic ? el.__rf.height : 100,
+      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];
+
+      nodes.push({
+        id: el.id,
+        text: text,
+        width: 35 + longestLine * 8,
+        height: 30 + lines.length * 10,
+        data: { type: "special" },
       });
     } else {
-      dagreGraph.setEdge(el.source, el.target);
-    }
-  });
-
-  dagre.layout(dagreGraph);
-
-  const layoutedElements = elements.map((el) => {
-    if (isNode(el)) {
-      const nodeWithPosition = dagreGraph.node(el.id);
-      el.targetPosition = isHorizontal ? Position.Left : Position.Top;
-      el.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
-      el.position = {
-        x: nodeWithPosition.x + Math.random() / 1000,
-        y: nodeWithPosition.y,
-      };
+      edges.push(el);
     }
+  }
 
-    return el;
-  });
-
-  return layoutedElements;
-};
+  return {
+    nodes,
+    edges,
+  };
+}
 
-export function getNextLayout(layout: Layout) {
+export function getNextLayout(layout: CanvasDirection) {
   switch (layout) {
-    case "TB":
-      return "BT";
+    case "LEFT":
+      return "UP";
 
-    case "BT":
-      return "RL";
+    case "UP":
+      return "RIGHT";
 
-    case "RL":
-      return "LR";
+    case "RIGHT":
+      return "DOWN";
 
     default:
-      return "TB";
+      return "LEFT";
   }
 }
 
-export function getLayout(layout: Layout, json: string, dynamic = false) {
-  const jsonToGraph = parser(json);
-  const layoutedElements = getLayoutPosition(layout, jsonToGraph, dynamic);
+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]}: ${entry[1]}\n`;
+    });
+
+    return temp;
+  }
+
+  return value;
+}
 
-  return layoutedElements;
+function isNode(element: NodeData | EdgeData) {
+  if ("text" in element) return true;
+  return false;
 }

+ 122 - 39
src/containers/LiveEditor/index.tsx

@@ -1,53 +1,136 @@
-import React from "react";
+import React, { ComponentType } from "react";
 import styled from "styled-components";
-import { ReactFlowProvider } from "react-flow-renderer";
-import { FlowWrapper } from "./FlowWrapper";
+import {
+  TransformWrapper,
+  TransformComponent,
+  ReactZoomPanPinchRef,
+} from "react-zoom-pan-pinch";
+import { useLocalStorage } from "usehooks-ts";
+import { Canvas, CanvasRef } from "reaflow";
+
+import { StorageConfig } from "src/typings/global";
+import { defaultValue } from "../JsonEditor";
+import { getEdgeNodes } from "./helpers";
+import { NodeWrapper } from "./CustomNode";
+import { Button } from "src/components/Button";
+import {
+  AiOutlineZoomIn,
+  AiOutlineZoomOut,
+  AiOutlineFullscreen,
+  AiFillSave,
+} from "react-icons/ai";
 
 const StyledLiveEditor = styled.div`
-  width: 100%;
-  height: 100%;
+  position: relative;
   border-left: 3px solid ${({ theme }) => theme.SILVER_DARK};
+`;
 
-  .react-flow__controls {
-    display: grid;
-    grid-auto-flow: dense;
-    grid-template-columns: 1fr 1fr;
-    grid-auto-rows: 1fr;
-    gap: 8px;
-    right: 10px;
-    left: unset;
-  }
-
-  .react-flow__minimap {
-    top: 8px;
-    right: 8px;
-    background: transparent;
-
-    .react-flow__minimap-mask {
-      fill: ${({ theme }) => theme.SILVER_DARK};
-      opacity: 0.5;
-    }
-  }
-
-  .react-flow__controls-button {
-    background: ${({ theme }) => theme.BLACK_PRIMARY};
-    fill: ${({ theme }) => theme.SILVER};
-    color: ${({ theme }) => theme.SILVER};
-    font-weight: 600;
-    border: 1px solid ${({ theme }) => theme.BLACK};
+const StyledEditorWrapper = styled.div`
+  position: absolute;
+`;
 
-    &:hover {
-      background: unset;
-    }
-  }
+const StyledControls = styled.div`
+  position: fixed;
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  grid-template-rows: 1fr 1fr;
+  gap: 8px;
+  bottom: 8px;
+  right: 8px;
+  opacity: 0.8;
 `;
 
 export const LiveEditor: React.FC = () => {
+  const canvasRef = React.useRef<CanvasRef | null>(null);
+  const wrapperRef = React.useRef<ReactZoomPanPinchRef | null>(null);
+
+  const [json] = useLocalStorage("json", JSON.stringify(defaultValue));
+  const [config] = useLocalStorage<StorageConfig>("config", {
+    layout: "LEFT",
+    minimap: true,
+    controls: true,
+  });
+
+  const { nodes, edges } = getEdgeNodes(json);
+
+  const onLayoutChange = () => {
+    wrapperRef.current?.resetTransform();
+  };
+
+  const zoomIn = (scale: number) => {
+    if (
+      wrapperRef.current?.state.scale &&
+      wrapperRef.current?.state.scale < 2
+    ) {
+      wrapperRef.current?.setTransform(
+        wrapperRef.current.instance.transformState.positionX - 200,
+        wrapperRef.current.instance.transformState.positionY - 200,
+        wrapperRef.current.state.scale + scale
+      );
+    }
+  };
+
+  const zoomOut = (scale: number) => {
+    if (
+      wrapperRef.current?.state.scale &&
+      wrapperRef.current?.state.scale > 0.4
+    ) {
+      wrapperRef.current?.setTransform(
+        wrapperRef.current.instance.transformState.positionX + 200,
+        wrapperRef.current.instance.transformState.positionY + 200,
+        wrapperRef.current.state.scale - scale
+      );
+    }
+  };
+
   return (
     <StyledLiveEditor>
-      <ReactFlowProvider>
-        <FlowWrapper />
-      </ReactFlowProvider>
+      <StyledEditorWrapper>
+        <TransformWrapper
+          maxScale={2}
+          minScale={0.4}
+          initialScale={0.8}
+          ref={wrapperRef}
+          limitToBounds={false}
+          wheel={{
+            step: 0.4,
+          }}
+        >
+          <TransformComponent>
+            <Canvas
+              ref={canvasRef}
+              nodes={nodes}
+              edges={edges}
+              layoutOptions={{
+                "elk.direction": config.layout,
+              }}
+              maxWidth={20000}
+              maxHeight={20000}
+              center={false}
+              zoomable={false}
+              fit
+              readonly
+              animated
+              node={NodeWrapper}
+              onLayoutChange={onLayoutChange}
+            />
+          </TransformComponent>
+        </TransformWrapper>
+      </StyledEditorWrapper>
+      <StyledControls>
+        <Button onClick={() => zoomIn(0.8)}>
+          <AiOutlineZoomIn size={20} />
+        </Button>
+        <Button onClick={() => zoomOut(0.4)}>
+          <AiOutlineZoomOut size={20} />
+        </Button>
+        <Button onClick={() => wrapperRef.current?.resetTransform()}>
+          <AiOutlineFullscreen size={20} />
+        </Button>
+        <Button>
+          <AiFillSave size={20} />
+        </Button>
+      </StyledControls>
     </StyledLiveEditor>
   );
 };

+ 2 - 1
src/pages/404/index.tsx

@@ -1,7 +1,8 @@
 import React from "react";
+import { useRouter } from "next/router";
 import styled from "styled-components";
+
 import { Button } from "src/components/Button";
-import { useRouter } from "next/router";
 import { Image } from "src/components/Image";
 
 const StyledNotFound = styled.div`

+ 5 - 4
src/pages/_app.tsx

@@ -1,11 +1,12 @@
 import React from "react";
-import GlobalStyle from "src/constants/globalStyle";
-import { darkTheme } from "src/constants/theme";
+import Head from "next/head";
+import { useRouter } from "next/router";
 import type { AppProps } from "next/app";
 import { ThemeProvider } from "styled-components";
-import { useRouter } from "next/router";
+
+import GlobalStyle from "src/constants/globalStyle";
+import { darkTheme } from "src/constants/theme";
 import { Loading } from "src/components/Loading";
-import Head from "next/head";
 
 function AykutSarac({ Component, pageProps }: AppProps) {
   const router = useRouter();

+ 6 - 4
src/pages/editor/index.tsx

@@ -1,13 +1,14 @@
-import { useRouter } from "next/router";
 import React from "react";
+import Head from "next/head";
+import { useRouter } from "next/router";
+import styled from "styled-components";
+import SplitPane from "react-split-pane";
+
 import { Button } from "src/components/Button";
 import { Sidebar } from "src/components/Sidebar";
-import styled from "styled-components";
 import { JsonEditor } from "src/containers/JsonEditor";
 import { LiveEditor } from "src/containers/LiveEditor";
-import SplitPane from "react-split-pane";
 
-import Head from "next/head";
 
 const StyledPageWrapper = styled.div`
   display: flex;
@@ -45,6 +46,7 @@ const StyledIncompatible = styled.div`
 
 const StyledEditorWrapper = styled.div`
   width: 100%;
+  overflow: hidden;
 
   @media only screen and (max-width: 568px) {
     display: none;

+ 2 - 2
src/typings/global.ts

@@ -1,7 +1,7 @@
-export type Layout = "TB" | "BT" | "LR" | "RL";
+import { CanvasDirection } from "reaflow";
 
 export interface StorageConfig {
-    layout: Layout;
+    layout: CanvasDirection;
     minimap: boolean;
     controls: boolean;
   }

+ 12 - 20
src/utils/json-editor-parser.ts

@@ -1,6 +1,4 @@
-import { FlowElement } from "react-flow-renderer";
-
-export const parser = (input: string | string[]): FlowElement[] => {
+export const parser = (input: string | string[]) => {
   try {
     if (typeof input !== "object") input = JSON.parse(input);
     if (!Array.isArray(input)) input = [input];
@@ -16,35 +14,29 @@ export const parser = (input: string | string[]): FlowElement[] => {
 
       return [os].flat().map((o) => ({
         id: nextId(),
-        data: {
-          label: Object.fromEntries(
-            Object.entries(o).filter(
-              ([k, v]) => !Array.isArray(v) && !(v instanceof Object)
-            )
-          ),
-        },
-        position: { x: 0, y: 0 },
-        type: "special",
+        text: Object.fromEntries(
+          Object.entries(o).filter(
+            ([k, v]) => !Array.isArray(v) && !(v instanceof Object)
+          )
+        ),
         children: Object.entries(o)
           .filter(([k, v]) => Array.isArray(v) || typeof v === "object")
           .flatMap(([k, v]) => [
             {
               id: nextId(),
-              data: { label: k },
-              position: { x: 0, y: 0 },
+              text: k,
               children: extract(v, nextId),
-              type: "special",
             },
           ]),
       }));
     };
 
     const relationships = (xs: { id: string; children: never[] }[]) => {
-      return xs.flatMap(({ id: target, children = [] }) => [
-        ...children.map(({ id: source }) => ({
-          id: `e${source}-${target}`,
-          source,
-          target,
+      return xs.flatMap(({ id: to, children = [] }) => [
+        ...children.map(({ id: from }) => ({
+          id: `e${from}-${to}`,
+          from,
+          to,
         })),
         ...relationships(children),
       ]);

+ 314 - 134
yarn.lock

@@ -285,7 +285,7 @@
     core-js-pure "^3.20.2"
     regenerator-runtime "^0.13.4"
 
-"@babel/runtime@^7.0.0-rc.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.0.0-rc.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3", "@babel/runtime@^7.9.2":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
   integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
@@ -373,7 +373,7 @@
   dependencies:
     "@cspotcode/source-map-consumer" "0.8.0"
 
-"@emotion/is-prop-valid@^0.8.8":
+"@emotion/is-prop-valid@^0.8.2", "@emotion/is-prop-valid@^0.8.8":
   version "0.8.8"
   resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
   integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
@@ -863,7 +863,7 @@
   dependencies:
     "@types/node" "*"
 
-"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0":
+"@types/hoist-non-react-statics@*":
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
   integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
@@ -935,16 +935,6 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react-redux@^7.1.20":
-  version "7.1.22"
-  resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.22.tgz#0eab76a37ef477cc4b53665aeaf29cb60631b72a"
-  integrity sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ==
-  dependencies:
-    "@types/hoist-non-react-statics" "^3.3.0"
-    "@types/react" "*"
-    hoist-non-react-statics "^3.3.0"
-    redux "^4.0.0"
-
 "@types/react-splitter-layout@^3.0.2":
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/@types/react-splitter-layout/-/react-splitter-layout-3.0.2.tgz#72a3c8b9329ee2a626d8c4c456852473a94a5e8b"
@@ -1431,7 +1421,12 @@ buffer-from@^1.0.0:
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
   integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
 
-call-bind@^1.0.0, call-bind@^1.0.2:
+calculate-size@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/calculate-size/-/calculate-size-1.1.1.tgz#ae7caa1c7795f82c4f035dc7be270e3581dae3ee"
+  integrity sha1-rnyqHHeV+CxPA13HvicONYHa4+4=
+
+call-bind@^1.0.0, call-bind@^1.0.2, call-bind@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
   integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
@@ -1533,10 +1528,10 @@ cjs-module-lexer@^1.0.0:
   resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
   integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
 
-classcat@^5.0.3:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.3.tgz#38eaa0ec6eb1b10faf101bbcef2afb319c23c17b"
-  integrity sha512-6dK2ke4VEJZOFx2ZfdDAl5OhEL8lvkl6EHF92IfRePfHxQTqir5NlcNVUv+2idjDqCX2NDc8m8YSAI5NI975ZQ==
+classnames@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
+  integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
 
 cliui@^7.0.2:
   version "7.0.4"
@@ -1547,11 +1542,6 @@ cliui@^7.0.2:
     strip-ansi "^6.0.0"
     wrap-ansi "^7.0.0"
 
-clsx@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
-  integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
-
 co@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -1700,67 +1690,17 @@ csstype@^3.0.2:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
   integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
 
-"d3-color@1 - 3":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.0.1.tgz#03316e595955d1fcd39d9f3610ad41bb90194d0a"
-  integrity sha512-6/SlHkDOBLyQSJ1j1Ghs82OIUXpKWlR0hCsw0XrLSQhuUPuCSmLQ1QPH98vpnQxMUQM2/gfAkUEWsupVpd9JGw==
-
-"d3-dispatch@1 - 3":
+"d3-path@1 - 3":
   version "3.0.1"
-  resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
-  integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
+  resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e"
+  integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==
 
-"d3-drag@2 - 3":
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
-  integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
-  dependencies:
-    d3-dispatch "1 - 3"
-    d3-selection "3"
-
-"d3-ease@1 - 3":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
-  integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
-
-"d3-interpolate@1 - 3":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
-  integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
-  dependencies:
-    d3-color "1 - 3"
-
-"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
-  integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
-
-"d3-timer@1 - 3":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
-  integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
-
-"d3-transition@2 - 3":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
-  integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
-  dependencies:
-    d3-color "1 - 3"
-    d3-dispatch "1 - 3"
-    d3-ease "1 - 3"
-    d3-interpolate "1 - 3"
-    d3-timer "1 - 3"
-
-d3-zoom@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
-  integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
+d3-shape@^3.0.1:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.1.0.tgz#c8a495652d83ea6f524e482fca57aa3f8bc32556"
+  integrity sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==
   dependencies:
-    d3-dispatch "1 - 3"
-    d3-drag "2 - 3"
-    d3-interpolate "1 - 3"
-    d3-selection "2 - 3"
-    d3-transition "2 - 3"
+    d3-path "1 - 3"
 
 dagre@^0.8.5:
   version "0.8.5"
@@ -1820,6 +1760,23 @@ dedent@^0.7.0:
   resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
   integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
 
+deep-copy@^1.4.1:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/deep-copy/-/deep-copy-1.4.2.tgz#0622719257e4bd60240e401ea96718211c5c4697"
+  integrity sha512-VxZwQ/1+WGQPl5nE67uLhh7OqdrmqI1OazrraO9Bbw/M8Bt6Mol/RxzDA6N6ZgRXpsG/W9PgUj8E1LHHBEq2GQ==
+
+deep-equal@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
+  integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
+  dependencies:
+    is-arguments "^1.0.4"
+    is-date-object "^1.0.1"
+    is-regex "^1.0.4"
+    object-is "^1.0.1"
+    object-keys "^1.1.1"
+    regexp.prototype.flags "^1.2.0"
+
 deep-is@^0.1.3, deep-is@~0.1.3:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
@@ -1830,6 +1787,13 @@ deepmerge@^4.2.2:
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
   integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
 
+defaulty@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/defaulty/-/defaulty-2.1.0.tgz#567bece2480d383e6a51edec82b350217c951a32"
+  integrity sha512-dNWjHNxL32khAaX/kS7/a3rXsgvqqp7cptqt477wAVnJLgaOKjcQt+53jKgPofn6hL2xyG51MegPlB5TKImXjA==
+  dependencies:
+    deep-copy "^1.4.1"
+
 define-properties@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -1837,6 +1801,11 @@ define-properties@^1.1.3:
   dependencies:
     object-keys "^1.0.12"
 
+defined@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+  integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
+
 delayed-stream@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -1925,11 +1894,30 @@ domutils@^2.5.2, domutils@^2.7.0, domutils@^2.8.0:
     domelementtype "^2.2.0"
     domhandler "^4.2.0"
 
+dotignore@~0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/dotignore/-/dotignore-0.1.2.tgz#f942f2200d28c3a76fbdd6f0ee9f3257c8a2e905"
+  integrity sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==
+  dependencies:
+    minimatch "^3.0.4"
+
 electron-to-chromium@^1.4.17:
   version "1.4.63"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.63.tgz#866db72d1221fda89419dc22669d03833e11625d"
   integrity sha512-e0PX/LRJPFRU4kzJKLvTobxyFdnANCvcoDCe8XcyTqP58nTWIwdsHvXLIl1RkB39X5yaosLaroMASWB0oIsgCA==
 
+elkjs@^0.7.1:
+  version "0.7.1"
+  resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.7.1.tgz#4751c5e918a4988139baf7f214e010aea22de969"
+  integrity sha512-lD86RWdh480/UuRoHhRcnv2IMkIcK6yMDEuT8TPBIbO3db4HfnVF+1lgYdQi99Ck0yb+lg5Eb46JCHI5uOsmAw==
+
+ellipsize@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/ellipsize/-/ellipsize-0.2.0.tgz#06515dd6b9a1cb65e516b013a235c08a576d8b02"
+  integrity sha512-InJhblLPZbBjw3N49knOWonfprgKPLKGySmG6bGHi7WsD5OkXIIlLkU4AguROmaMZ0v1BRdo267wEc0Pexw8ww==
+  dependencies:
+    tape "^4.9.0"
+
 email-addresses@^3.0.1:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-3.1.0.tgz#cabf7e085cbdb63008a70319a74e6136188812fb"
@@ -1950,6 +1938,14 @@ emoji-regex@^9.2.2:
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
   integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
 
+enhanced-resolve@^5.7.0:
+  version "5.9.0"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz#49ac24953ac8452ed8fed2ef1340fc8e043667ee"
+  integrity sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA==
+  dependencies:
+    graceful-fs "^4.2.4"
+    tapable "^2.2.0"
+
 entities@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
@@ -2306,6 +2302,11 @@ execa@^5.0.0:
     signal-exit "^3.0.3"
     strip-final-newline "^2.0.0"
 
+exenv@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
+  integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
+
 exit@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
@@ -2321,6 +2322,11 @@ expect@^27.4.6:
     jest-matcher-utils "^27.4.6"
     jest-message-util "^27.4.6"
 
+fast-deep-equal@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
+  integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=
+
 fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -2426,6 +2432,13 @@ flatted@^3.1.0:
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
   integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==
 
+for-each@~0.3.3:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
+  integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
+  dependencies:
+    is-callable "^1.1.3"
+
 form-data@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
@@ -2435,6 +2448,26 @@ form-data@^3.0.0:
     combined-stream "^1.0.8"
     mime-types "^2.1.12"
 
+framer-motion@^4.1.17:
+  version "4.1.17"
+  resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-4.1.17.tgz#4029469252a62ea599902e5a92b537120cc89721"
+  integrity sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==
+  dependencies:
+    framesync "5.3.0"
+    hey-listen "^1.0.8"
+    popmotion "9.3.6"
+    style-value-types "4.1.4"
+    tslib "^2.1.0"
+  optionalDependencies:
+    "@emotion/is-prop-valid" "^0.8.2"
+
[email protected]:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.3.0.tgz#0ecfc955e8f5a6ddc8fdb0cc024070947e1a0d9b"
+  integrity sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==
+  dependencies:
+    tslib "^2.1.0"
+
 fs-extra@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@@ -2555,7 +2588,7 @@ [email protected]:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.7:
+glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.7, glob@~7.2.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
   integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
@@ -2641,14 +2674,19 @@ has-tostringtag@^1.0.0:
   dependencies:
     has-symbols "^1.0.2"
 
-has@^1.0.3:
+has@^1.0.3, has@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
   integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
   dependencies:
     function-bind "^1.1.1"
 
-hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+hey-listen@^1.0.8:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
+  integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
+
+hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
   integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -2758,7 +2796,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@^2.0.4:
+inherits@2, inherits@^2.0.4, inherits@~2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -2772,6 +2810,14 @@ internal-slot@^1.0.3:
     has "^1.0.3"
     side-channel "^1.0.4"
 
+is-arguments@^1.0.4:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
+  integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
+  dependencies:
+    call-bind "^1.0.2"
+    has-tostringtag "^1.0.0"
+
 is-bigint@^1.0.1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@@ -2787,7 +2833,7 @@ is-boolean-object@^1.0.1, is-boolean-object@^1.1.0:
     call-bind "^1.0.2"
     has-tostringtag "^1.0.0"
 
-is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.4:
+is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.4:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
   integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
@@ -2850,7 +2896,7 @@ is-potential-custom-element-name@^1.0.1:
   resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
   integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
 
-is-regex@^1.0.5, is-regex@^1.1.4:
+is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.4, is-regex@~1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
   integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
@@ -3447,6 +3493,30 @@ jsonfile@^4.0.0:
     array-includes "^3.1.3"
     object.assign "^4.1.2"
 
+kld-affine@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/kld-affine/-/kld-affine-2.1.1.tgz#e28296585f305230eaad3b1e346d3ac5ca4c72b3"
+  integrity sha512-NIS9sph8ZKdnQxZa5TcggaFs/Qr9zX3brFlGwE0+0Z4EzFIvAFuqLSwNeU4GkEpaX8ndh3ggGmWV7BPPcS3vjQ==
+
+kld-intersections@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/kld-intersections/-/kld-intersections-0.7.0.tgz#a6907c103553654e268c094701c72659b3cafb2f"
+  integrity sha512-/KuBU7Y5bRPGfc0yQ3QIoXPKqOQ6cBWDRl1XVMMa3pm4V6Ydbgy9e2fZoRxlSIU0gZSBt1c6gWLOzSGKbU8I3A==
+  dependencies:
+    kld-affine "^2.1.1"
+    kld-path-parser "^0.2.1"
+    kld-polynomial "^0.3.0"
+
+kld-path-parser@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/kld-path-parser/-/kld-path-parser-0.2.1.tgz#ad1f7c8ebc9c9d67409413fc4de459293394e86b"
+  integrity sha512-C1EqY6vzqv5tdKeMF31L+JXq97n5zo67LiSEhZf4sPq8YeM+8ytp/qMGSKN8VdSPvFa6h1SR35aF4+T2JtxZww==
+
+kld-polynomial@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/kld-polynomial/-/kld-polynomial-0.3.0.tgz#cb7f3f37c679925d895f4cd737ed713a3b379921"
+  integrity sha512-PEfxjQ6tsxL9DHBIhM2UZsSes0GI+OIMjbE0kj60jr80Biq/xXl1eGfnyzmfoackAMdKZtw2060L09HdjkPP5w==
+
 kleur@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@@ -3610,7 +3680,7 @@ minimatch@^3.0.4:
   dependencies:
     brace-expansion "^1.1.7"
 
-minimist@^1.2.0, minimist@^1.2.5:
+minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
@@ -3620,6 +3690,11 @@ moo@^0.5.0:
   resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4"
   integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==
 
+mousetrap@^1.6.5:
+  version "1.6.5"
+  resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9"
+  integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==
+
 [email protected]:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -3655,6 +3730,14 @@ nearley@^2.7.10:
     railroad-diagrams "^1.0.0"
     randexp "0.4.6"
 
+next-transpile-modules@^9.0.0:
+  version "9.0.0"
+  resolved "https://registry.yarnpkg.com/next-transpile-modules/-/next-transpile-modules-9.0.0.tgz#133b1742af082e61cc76b02a0f12ffd40ce2bf90"
+  integrity sha512-VCNFOazIAnXn1hvgYYSTYMnoWgKgwlYh4lm1pKbSfiB3kj5ZYLcKVhfh3jkPOg1cnd9DP+pte9yCUocdPEUBTQ==
+  dependencies:
+    enhanced-resolve "^5.7.0"
+    escalade "^3.1.1"
+
 [email protected]:
   version "12.0.9"
   resolved "https://registry.yarnpkg.com/next/-/next-12.0.9.tgz#4eb3006b63bb866f5c2918ca0003e98f4259e063"
@@ -3717,12 +3800,12 @@ object-assign@^4.0.1, object-assign@^4.1.1:
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
 
-object-inspect@^1.11.0, object-inspect@^1.7.0, object-inspect@^1.9.0:
+object-inspect@^1.11.0, object-inspect@^1.7.0, object-inspect@^1.9.0, object-inspect@~1.12.0:
   version "1.12.0"
   resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
   integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
 
-object-is@^1.0.2, object-is@^1.1.2:
+object-is@^1.0.1, object-is@^1.0.2, object-is@^1.1.2:
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
   integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
@@ -3818,6 +3901,11 @@ optionator@^0.9.1:
     type-check "^0.4.0"
     word-wrap "^1.2.3"
 
+p-cancelable@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050"
+  integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==
+
 p-limit@^1.1.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
@@ -3949,6 +4037,21 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
   dependencies:
     find-up "^4.0.0"
 
[email protected]:
+  version "9.3.6"
+  resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.6.tgz#b5236fa28f242aff3871b9e23721f093133248d1"
+  integrity sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==
+  dependencies:
+    framesync "5.3.0"
+    hey-listen "^1.0.8"
+    style-value-types "4.1.4"
+    tslib "^2.1.0"
+
+popper.js@^1.16.1:
+  version "1.16.1"
+  resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
+  integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
+
 postcss-value-parser@^4.0.2:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
@@ -3995,7 +4098,7 @@ prompts@^2.0.1:
     kleur "^3.0.3"
     sisteransi "^1.0.5"
 
-prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.7.2:
+prop-types@^15.5.4, prop-types@^15.7.2:
   version "15.8.1"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
   integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -4039,6 +4142,20 @@ [email protected]:
     discontinuous-range "1.0.0"
     ret "~0.1.10"
 
+rdk@^5.1.6:
+  version "5.1.6"
+  resolved "https://registry.yarnpkg.com/rdk/-/rdk-5.1.6.tgz#93f2a0f53afea34bf754d1b5bd9a5a084441bbf2"
+  integrity sha512-fUzlSJjwD5MtXOOBvopdxdWBhztl7WTlE27WALNtbUD66ApGjZffGbhBMN6i9A3e4Dd8pDnGVe590Nk2lEN4lw==
+  dependencies:
+    classnames "^2.3.1"
+    popper.js "^1.16.1"
+    react-scrolllock "^5.0.1"
+
+react-cool-dimensions@^2.0.7:
+  version "2.0.7"
+  resolved "https://registry.yarnpkg.com/react-cool-dimensions/-/react-cool-dimensions-2.0.7.tgz#2fe6657608f034cd7c89f149ed14e79cf1cb2d50"
+  integrity sha512-z1VwkAAJ5d8QybDRuYIXTE41RxGr5GYsv1bQhbOBE8cMfoZQZpcF0odL64vdgrQVzat2jayedj1GoYi80FWcbA==
+
 [email protected]:
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
@@ -4048,27 +4165,10 @@ [email protected]:
     object-assign "^4.1.1"
     scheduler "^0.20.2"
 
-react-draggable@^4.4.4:
-  version "4.4.4"
-  resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.4.tgz#5b26d9996be63d32d285a426f41055de87e59b2f"
-  integrity sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==
-  dependencies:
-    clsx "^1.1.1"
-    prop-types "^15.6.0"
-
-react-flow-renderer@^9.7.3:
-  version "9.7.3"
-  resolved "https://registry.yarnpkg.com/react-flow-renderer/-/react-flow-renderer-9.7.3.tgz#56779ef5f4590edfabca380dd90e62bd37375a83"
-  integrity sha512-yMSGdZ2mtpiNsi7fGgkmMC6/q/a2LI+G4Par9zcIBWDVP5ZsujSeBpehXprPEXKtUjZTi3ToNLq0EQ5Z7/EU4Q==
-  dependencies:
-    "@babel/runtime" "^7.16.7"
-    classcat "^5.0.3"
-    d3-selection "^3.0.0"
-    d3-zoom "^3.0.0"
-    fast-deep-equal "^3.1.3"
-    react-draggable "^4.4.4"
-    react-redux "^7.2.6"
-    redux "^4.1.2"
+react-fast-compare@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
+  integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
 
 react-icons@^4.3.1:
   version "4.3.1"
@@ -4080,7 +4180,7 @@ react-is@^16.13.1, react-is@^16.7.0:
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
-react-is@^17.0.1, react-is@^17.0.2:
+react-is@^17.0.1:
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
   integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
@@ -4097,17 +4197,12 @@ react-lifecycles-compat@^3.0.4:
   resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
   integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
 
-react-redux@^7.2.6:
-  version "7.2.6"
-  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa"
-  integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==
+react-scrolllock@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/react-scrolllock/-/react-scrolllock-5.0.1.tgz#da1cfb7b6d55c86ae41dbad5274b778c307752b7"
+  integrity sha512-poeEsjnZAlpA6fJlaNo4rZtcip2j6l5mUGU/SJe1FFlicEudS943++u7ZSdA7lk10hoyYK3grOD02/qqt5Lxhw==
   dependencies:
-    "@babel/runtime" "^7.15.4"
-    "@types/react-redux" "^7.1.20"
-    hoist-non-react-statics "^3.3.2"
-    loose-envify "^1.4.0"
-    prop-types "^15.7.2"
-    react-is "^17.0.2"
+    exenv "^1.2.2"
 
 react-split-pane@^0.1.92:
   version "0.1.92"
@@ -4125,6 +4220,16 @@ react-style-proptype@^3.2.2:
   dependencies:
     prop-types "^15.5.4"
 
+react-use-gesture@^8.0.1:
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/react-use-gesture/-/react-use-gesture-8.0.1.tgz#4360c0f7c9e26baf9fbe58f63fc9de7ef699c17f"
+  integrity sha512-CXzUNkulUdgouaAlvAsC5ZVo0fi9KGSBSk81WrE4kOIcJccpANe9zZkAYr5YZZhqpicIFxitsrGVS4wmoMun9A==
+
+react-zoom-pan-pinch@^2.1.3:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.1.3.tgz#3b84594200343136c0d4397c33fec38dc0ee06ad"
+  integrity sha512-a5AChOWhjo0RmxsNZXGQIlNh3e3nLU6m4V6M+6dlbPNk5d+MtMxgKWyA5zpR06Lp3OZkZVF9nR8JeWSvKwck9g==
+
 [email protected]:
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
@@ -4133,6 +4238,34 @@ [email protected]:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
 
+reaflow@^4.2.15:
+  version "4.2.15"
+  resolved "https://registry.yarnpkg.com/reaflow/-/reaflow-4.2.15.tgz#506e694874784d376a0264638ee0d8bb4fb2f876"
+  integrity sha512-BzeMdR9/edtpmTnoxyUO3vBcRh8U6rOZRdHBGHBXBhtVVYn60fp2TcMMQ0OOdXOUyDzGqZcKucntkIRi4pbAYw==
+  dependencies:
+    calculate-size "^1.1.1"
+    classnames "^2.3.1"
+    d3-shape "^3.0.1"
+    elkjs "^0.7.1"
+    ellipsize "^0.2.0"
+    framer-motion "^4.1.17"
+    kld-affine "^2.1.1"
+    kld-intersections "^0.7.0"
+    p-cancelable "^3.0.0"
+    rdk "^5.1.6"
+    react-cool-dimensions "^2.0.7"
+    react-fast-compare "^3.2.0"
+    react-use-gesture "^8.0.1"
+    reakeys "^1.2.6"
+    undoo "^0.5.0"
+
+reakeys@^1.2.6:
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/reakeys/-/reakeys-1.2.6.tgz#124b725d1de1b53a72feb13ebb4636b3dfc6b845"
+  integrity sha512-IFTw00xir8C+CV/F7ekg7zwY5Lz/4o0fXvLMKbZVYSq/I829RuGgCBywad942sRIzX5Iuc9/pa49RrrK6qDkkw==
+  dependencies:
+    mousetrap "^1.6.5"
+
 redent@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
@@ -4141,19 +4274,12 @@ redent@^3.0.0:
     indent-string "^4.0.0"
     strip-indent "^3.0.0"
 
-redux@^4.0.0, redux@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
-  integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
-  dependencies:
-    "@babel/runtime" "^7.9.2"
-
 regenerator-runtime@^0.13.4:
   version "0.13.9"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
   integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
 
-regexp.prototype.flags@^1.3.1:
+regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307"
   integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==
@@ -4193,7 +4319,7 @@ resolve.exports@^1.1.0:
   resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9"
   integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==
 
-resolve@^1.20.0:
+resolve@^1.20.0, resolve@~1.22.0:
   version "1.22.0"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
   integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
@@ -4210,6 +4336,13 @@ resolve@^2.0.0-next.3:
     is-core-module "^2.2.0"
     path-parse "^1.0.6"
 
+resumer@~0.0.0:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759"
+  integrity sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=
+  dependencies:
+    through "~2.3.4"
+
 ret@~0.1.10:
   version "0.1.15"
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@@ -4404,7 +4537,7 @@ string.prototype.matchall@^4.0.6:
     regexp.prototype.flags "^1.3.1"
     side-channel "^1.0.4"
 
-string.prototype.trim@^1.2.1:
+string.prototype.trim@^1.2.1, string.prototype.trim@~1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.5.tgz#a587bcc8bfad8cb9829a577f5de30dd170c1682c"
   integrity sha512-Lnh17webJVsD6ECeovpVN17RlAKjmz4rF9S+8Y45CkMc/ufVpTkU3vZIyIC7sllQ1FCvObZnnCdNs/HXTUOTlg==
@@ -4470,6 +4603,14 @@ strip-outer@^1.0.1:
   dependencies:
     escape-string-regexp "^1.0.2"
 
[email protected]:
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"
+  integrity sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==
+  dependencies:
+    hey-listen "^1.0.8"
+    tslib "^2.1.0"
+
 styled-components@^5.3.3:
   version "5.3.3"
   resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.3.tgz#312a3d9a549f4708f0fb0edc829eb34bde032743"
@@ -4530,6 +4671,32 @@ symbol-tree@^3.2.4:
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
+tapable@^2.2.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
+  integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
+
+tape@^4.9.0:
+  version "4.15.0"
+  resolved "https://registry.yarnpkg.com/tape/-/tape-4.15.0.tgz#1b8a9563b4bc7e51302216c137732fb2ce6d1a99"
+  integrity sha512-SfRmG2I8QGGgJE/MCiLH8c11L5XxyUXxwK9xLRD0uiK5fehRkkSZGmR6Y1pxOt8vJ19m3sY+POTQpiaVv45/LQ==
+  dependencies:
+    call-bind "~1.0.2"
+    deep-equal "~1.1.1"
+    defined "~1.0.0"
+    dotignore "~0.1.2"
+    for-each "~0.3.3"
+    glob "~7.2.0"
+    has "~1.0.3"
+    inherits "~2.0.4"
+    is-regex "~1.1.4"
+    minimist "~1.2.5"
+    object-inspect "~1.12.0"
+    resolve "~1.22.0"
+    resumer "~0.0.0"
+    string.prototype.trim "~1.2.5"
+    through "~2.3.8"
+
 terminal-link@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
@@ -4557,6 +4724,11 @@ throat@^6.0.1:
   resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375"
   integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==
 
+through@~2.3.4, through@~2.3.8:
+  version "2.3.8"
+  resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+  integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
+
 [email protected]:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@@ -4630,7 +4802,7 @@ tslib@^1.8.1:
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
-tslib@^2.2.0:
+tslib@^2.1.0, tslib@^2.2.0:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
   integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
@@ -4693,6 +4865,14 @@ unbox-primitive@^1.0.1:
     has-symbols "^1.0.2"
     which-boxed-primitive "^1.0.2"
 
+undoo@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/undoo/-/undoo-0.5.0.tgz#41e94930e3a7a4a4484b735d9397e2431c2f92ca"
+  integrity sha512-SPlDcde+AUHoFKeVlH2uBJxqVkw658I4WR2rPoygC1eRCzm3GeoP8S6xXZVJeBVOQQid8X2xUBW0N4tOvvHH3Q==
+  dependencies:
+    defaulty "^2.1.0"
+    fast-deep-equal "^1.0.0"
+
 universalify@^0.1.0, universalify@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"