diff --git a/.gitignore b/.gitignore
index 1437c53..e928339 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,6 @@ yarn-error.log*
# vercel
.vercel
+
+# WebStorm
+.idea
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7e9d185
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2019 Unly
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/README.md b/README.md
index 514bf1d..16cea9c 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,76 @@
-# TypeScript Next.js example
+# POC Next.js + Reaflow
-This is a really simple project that shows the usage of Next.js with TypeScript.
+> This project is a POC of [Reaflow](https://github.com/reaviz/reaflow) used with the Next.js framework. It is hosted on Vercel.
-## Deploy your own
+It is a single-page application (using a static page) that aims at showing an **advanced use-case with Reaflow**.
+
+## Online demo
+
+[Demo](https://poc-nextjs-reaflow.vercel.app/) (automatically updated from the `master` branch).
+
+
-Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
+## Features
-[](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-typescript&project-name=with-typescript&repository-name=with-typescript)
+It comes with the following features:
+- Source code heavily **documented**
+- Strong TS typings
+- Different kinds of node (`start`, `if`, `information`, `question`) with different layouts for each type _(see [NodeRouter component](blob/main/src/components/nodes/NodeRouter.tsx))_
+- Nodes use `foreignObject`, which complicates things quite a bit (events, css), but it's the only way of writing HTML/CSS within an SVG `rect` (custom nodes UI)
+- Advanced support for **`foreignObject`** and best-practices
+- Support for **Emotion 11**
+- Reaflow Nodes, Edges and Ports are properly extended (**BaseNode** component, **BaseNodeData** type, **BaseEdge** component, **BaseEdgeData** type, etc.),
+ which makes it easy to quickly change the properties of all nodes, edges, ports, etc.
+- Creation of nodes through the `BlockPickerMenu` component, which displays either at the bottom of the canvas, or at the mouse pointer position (e.g: when dropping edges)
+- **Undo/redo** support (with shortcuts)
+- Node/edge **deletion**
+- Node **duplication**
+- **Selection** of nodes and edges, one at a time
+- Uses **`Recoil`** for shared state management
+- Automatically re-calculate the **height** of nodes when jumping lines in `textarea`
+- Graph data (nodes, edges) are **persisted** in the browser **localstorage** and loaded upon page reload
-## How to use it?
+Known limitations:
+- Editor direction is `RIGHT` (hardcoded) and adding nodes will add them to the right side, always (even if you change the direction)
+ - I don't plan on changing that at the moment
-Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
+> This POC can be used as a boilerplate to start your own project using Reaflow.
-```bash
-npx create-next-app --example with-typescript with-typescript-app
-# or
-yarn create next-app --example with-typescript with-typescript-app
-```
+## Getting started
+
+- `yarn`
+- `yarn start`
+- Open browser at [http://localhost:8890](http://localhost:8890)
+
+## Deploy your own
-Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
+Deploy the example using [Vercel](https://vercel.com):
-## Notes
+[](https://vercel.com/new/git/external?repository-url=https://github.com/Vadorequest/poc-nextjs-reaflow&project-name=poc-nextjs-reaflow&repository-name=poc-nextjs-reaflow)
-This example shows how to integrate the TypeScript type system into Next.js. Since TypeScript is supported out of the box with Next.js, all we have to do is to install TypeScript.
+## Advanced - ELK
-```
-npm install --save-dev typescript
-```
+ELKjs (and ELK) are used to draw the graph (nodes, edges).
+It's what Reaflow uses in the background.
+ELK stands for **Eclipse Layout Kernel**.
-To enable TypeScript's features, we install the type declarations for React and Node.
+It seems to be one of the best Layout manager out there.
-```
-npm install --save-dev @types/react @types/react-dom @types/node
-```
+Unfortunately, it is quite complicated and lacks a comprehensive documentation.
-When we run `next dev` the next time, Next.js will start looking for any `.ts` or `.tsx` files in our project and builds it. It even automatically creates a `tsconfig.json` file for our project with the recommended settings.
+You'll need to dig into the ELK documentation and issues if you're trying to change **how the graph's layout behaves**.
+Here are some good places to start and useful links I've compiled for my own sake.
-Next.js has built-in TypeScript declarations, so we'll get autocompletion for Next.js' modules straight away.
+- [ELKjs GitHub](https://github.com/kieler/elkjs)
+- [ELK official website](https://www.eclipse.org/elk/)
+- [ELK Demonstrators](https://rtsys.informatik.uni-kiel.de/elklive/index.html)
+ - [Tool to convert `elkt <=> json` both ways](https://rtsys.informatik.uni-kiel.de/elklive/conversion.html)
+ - [Tool to convert `elkt` to a graph](https://rtsys.informatik.uni-kiel.de/elklive/elkgraph.html)
+ - [Java ELK implementation of the `layered` algorithm](https://github.com/eclipse/elk/tree/master/plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/p2layers)
+ - [Community examples soure code](https://github.com/eclipse/elk-models/tree/master/examples) _(which are displayed on [ELK examples](https://rtsys.informatik.uni-kiel.de/elklive/examples.html))_
+ - [Klayjs example](http://kieler.github.io/klayjs-d3/examples/interactive) (ELK is the sucessor of KlayJS and [should support the same options](https://github.com/kieler/elkjs/issues/122#issuecomment-777781503))
+- [Issues opened by Austin](https://github.com/kieler/elkjs/issues?q=is%3Aissue+sort%3Aupdated-desc+author%3Aamcdnl)
+- [Issues opened by Vadorequest](https://github.com/kieler/elkjs/issues?q=is%3Aissue+sort%3Aupdated-desc+author%3Avadorequest)
-A `type-check` script is also added to `package.json`, which runs TypeScript's `tsc` CLI in `noEmit` mode to run type-checking separately. You can then include this, for example, in your `test` scripts.
+Known limitations:
+- [Tracking issue - Manually positioning the nodes ("Standalone Edge Routing")](https://github.com/eclipse/elk/issues/315)
diff --git a/app.d.ts b/app.d.ts
new file mode 100644
index 0000000..7398efe
--- /dev/null
+++ b/app.d.ts
@@ -0,0 +1,2 @@
+declare module '@unly/utils';
+
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 0000000..9ca5a22
--- /dev/null
+++ b/babel.config.js
@@ -0,0 +1,24 @@
+/**
+ * Babel configuration for Next.js
+ *
+ * The official documentation uses a ".babelrc" file, but we prefer using "babel.config.js" for better documentation support.
+ *
+ * @see https://nextjs.org/docs/advanced-features/customizing-babel-config Official doc reference v10
+ * @see https://github.com/vercel/next.js/blob/canary/packages/next/build/babel/preset.ts You can take a look at this file to learn about the presets included by next/babel.
+ * @see https://emotion.sh/docs/css-prop#babel-preset Configuring Emotion 11
+ * @example https://github.com/vercel/next.js/tree/canary/examples/with-custom-babel-config Next.js official example of customizing Babel
+ */
+module.exports = {
+ presets: [
+ [
+ 'next/babel',
+ {
+ 'preset-react': {
+ 'runtime': 'automatic',
+ 'importSource': '@emotion/react',
+ },
+ },
+ ],
+ ],
+ plugins: ['@emotion/babel-plugin'],
+};
diff --git a/browser.d.ts b/browser.d.ts
new file mode 100644
index 0000000..1f2c2e6
--- /dev/null
+++ b/browser.d.ts
@@ -0,0 +1,6 @@
+/**
+ * Extends the native browser Window object by adding our custom keys.
+ */
+interface Window {
+ initialCanvasDataset?: any;
+}
diff --git a/components/Layout.tsx b/components/Layout.tsx
deleted file mode 100644
index 8f111e1..0000000
--- a/components/Layout.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React, { ReactNode } from 'react'
-import Link from 'next/link'
-import Head from 'next/head'
-
-type Props = {
- children?: ReactNode
- title?: string
-}
-
-const Layout = ({ children, title = 'This is the default title' }: Props) => (
-
-
-
{title}
-
-
-
-
- {children}
-
-
- I'm here to stay (Footer)
-
-
-)
-
-export default Layout
diff --git a/components/List.tsx b/components/List.tsx
deleted file mode 100644
index 5fe5ef2..0000000
--- a/components/List.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as React from 'react'
-import ListItem from './ListItem'
-import { User } from '../interfaces'
-
-type Props = {
- items: User[]
-}
-
-const List = ({ items }: Props) => (
-
- {items.map((item) => (
-
-
-
- ))}
-
-)
-
-export default List
diff --git a/components/ListDetail.tsx b/components/ListDetail.tsx
deleted file mode 100644
index fa3f9c3..0000000
--- a/components/ListDetail.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import * as React from 'react'
-
-import { User } from '../interfaces'
-
-type ListDetailProps = {
- item: User
-}
-
-const ListDetail = ({ item: user }: ListDetailProps) => (
-
-
Detail for {user.name}
-
ID: {user.id}
-
-)
-
-export default ListDetail
diff --git a/components/ListItem.tsx b/components/ListItem.tsx
deleted file mode 100644
index b10b9c8..0000000
--- a/components/ListItem.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react'
-import Link from 'next/link'
-
-import { User } from '../interfaces'
-
-type Props = {
- data: User
-}
-
-const ListItem = ({ data }: Props) => (
-
-
- {data.id}: {data.name}
-
-
-)
-
-export default ListItem
diff --git a/interfaces/index.ts b/interfaces/index.ts
deleted file mode 100644
index 68528c5..0000000
--- a/interfaces/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-// You can include shared interfaces/types in a separate file
-// and then use them in any component by importing them. For
-// example, to import the interface below do:
-//
-// import { User } from 'path/to/interfaces';
-
-export type User = {
- id: number
- name: string
-}
diff --git a/package.json b/package.json
index 71b0fd8..b1f089b 100644
--- a/package.json
+++ b/package.json
@@ -1,22 +1,73 @@
{
- "name": "with-typescript",
- "version": "1.0.0",
+ "name": "poc-nextjs-reaflow",
+ "license": "MIT",
"scripts": {
- "dev": "next",
"build": "next build",
- "start": "next start",
- "type-check": "tsc"
+ "start": "next dev --port 8890",
+ "type-check": "tsc",
+ "link:reaflow": "yarn link reaflow && yarn link react && yarn link react-dom"
},
"dependencies": {
- "next": "latest",
- "react": "^16.12.0",
- "react-dom": "^16.12.0"
+ "@chakra-ui/icons": "1.0.4",
+ "@chakra-ui/react": "1.2.1",
+ "@chakra-ui/theme-tools": "1.0.3",
+ "@emotion/react": "11.1.4",
+ "@emotion/styled": "11.0.0",
+ "@fortawesome/fontawesome-svg-core": "1.2.34",
+ "@fortawesome/free-brands-svg-icons": "5.15.2",
+ "@fortawesome/free-solid-svg-icons": "5.15.2",
+ "@fortawesome/react-fontawesome": "0.1.14",
+ "@types/lodash.clonedeep": "4.5.6",
+ "@types/lodash.merge": "4.6.6",
+ "@types/lodash.now": "4.0.6",
+ "@types/lodash.size": "4.2.6",
+ "@unly/utils": "1.0.3",
+ "@welldone-software/why-did-you-render": "6.0.5",
+ "animate.css": "4.1.1",
+ "classnames": "2.2.6",
+ "framer-motion": "3.2.1",
+ "lodash.capitalize": "4.2.1",
+ "lodash.clonedeep": "4.5.0",
+ "lodash.debounce": "4.0.8",
+ "lodash.filter": "4.6.0",
+ "lodash.includes": "4.3.0",
+ "lodash.isequal": "4.5.0",
+ "lodash.merge": "4.6.2",
+ "lodash.now": "4.0.2",
+ "lodash.remove": "4.7.0",
+ "lodash.size": "4.2.0",
+ "lodash.some": "4.6.0",
+ "lodash.sortby": "4.7.0",
+ "next": "10.0.6",
+ "rdk": "5.0.6",
+ "react": "17.0.1",
+ "react-debounce-input": "3.2.3",
+ "react-dom": "17.0.1",
+ "react-select": "4.0.2",
+ "react-textarea-autosize": "8.3.0",
+ "reaflow": "3.0.13",
+ "recoil": "0.1.2",
+ "recoil-devtools-dock": "^0.1.6",
+ "recoil-devtools-log-monitor": "^0.2.7",
+ "recoil-devtools-logger": "^0.1.5",
+ "uuid": "8.3.2"
},
"devDependencies": {
- "@types/node": "^12.12.21",
- "@types/react": "^16.9.16",
- "@types/react-dom": "^16.9.4",
- "typescript": "4.0"
- },
- "license": "MIT"
+ "@emotion/babel-plugin": "11.1.2",
+ "@types/classnames": "2.2.11",
+ "@types/lodash.capitalize": "4.2.6",
+ "@types/lodash.debounce": "4.0.6",
+ "@types/lodash.filter": "4.6.6",
+ "@types/lodash.includes": "4.3.6",
+ "@types/lodash.isequal": "4.5.5",
+ "@types/lodash.remove": "4.7.6",
+ "@types/lodash.some": "4.6.6",
+ "@types/lodash.sortby": "4.7.6",
+ "@types/node": "14.14.22",
+ "@types/react": "17.0.1",
+ "@types/react-dom": "17.0.0",
+ "@types/react-select": "4.0.12",
+ "@types/uuid": "8.3.0",
+ "typescript": "4.1.3"
+ }
}
diff --git a/pages/about.tsx b/pages/about.tsx
deleted file mode 100644
index 4e6f301..0000000
--- a/pages/about.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import Link from 'next/link'
-import Layout from '../components/Layout'
-
-const AboutPage = () => (
-
- About
- This is the about page
-
-
- Go home
-
-
-
-)
-
-export default AboutPage
diff --git a/pages/api/users/index.ts b/pages/api/users/index.ts
deleted file mode 100644
index 4efdba6..0000000
--- a/pages/api/users/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { NextApiRequest, NextApiResponse } from 'next'
-import { sampleUserData } from '../../../utils/sample-data'
-
-const handler = (_req: NextApiRequest, res: NextApiResponse) => {
- try {
- if (!Array.isArray(sampleUserData)) {
- throw new Error('Cannot find user data')
- }
-
- res.status(200).json(sampleUserData)
- } catch (err) {
- res.status(500).json({ statusCode: 500, message: err.message })
- }
-}
-
-export default handler
diff --git a/pages/index.tsx b/pages/index.tsx
deleted file mode 100644
index 57f913a..0000000
--- a/pages/index.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import Link from 'next/link'
-import Layout from '../components/Layout'
-
-const IndexPage = () => (
-
- Hello Next.js đź‘‹
-
-
- About
-
-
-
-)
-
-export default IndexPage
diff --git a/pages/users/[id].tsx b/pages/users/[id].tsx
deleted file mode 100644
index 61720da..0000000
--- a/pages/users/[id].tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { GetStaticProps, GetStaticPaths } from 'next'
-
-import { User } from '../../interfaces'
-import { sampleUserData } from '../../utils/sample-data'
-import Layout from '../../components/Layout'
-import ListDetail from '../../components/ListDetail'
-
-type Props = {
- item?: User
- errors?: string
-}
-
-const StaticPropsDetail = ({ item, errors }: Props) => {
- if (errors) {
- return (
-
-
- Error: {errors}
-
-
- )
- }
-
- return (
-
- {item && }
-
- )
-}
-
-export default StaticPropsDetail
-
-export const getStaticPaths: GetStaticPaths = async () => {
- // Get the paths we want to pre-render based on users
- const paths = sampleUserData.map((user) => ({
- params: { id: user.id.toString() },
- }))
-
- // We'll pre-render only these paths at build time.
- // { fallback: false } means other routes should 404.
- return { paths, fallback: false }
-}
-
-// This function gets called at build time on server-side.
-// It won't be called on client-side, so you can even do
-// direct database queries.
-export const getStaticProps: GetStaticProps = async ({ params }) => {
- try {
- const id = params?.id
- const item = sampleUserData.find((data) => data.id === Number(id))
- // By returning { props: item }, the StaticPropsDetail component
- // will receive `item` as a prop at build time
- return { props: { item } }
- } catch (err) {
- return { props: { errors: err.message } }
- }
-}
diff --git a/pages/users/index.tsx b/pages/users/index.tsx
deleted file mode 100644
index 7b3ee88..0000000
--- a/pages/users/index.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { GetStaticProps } from 'next'
-import Link from 'next/link'
-
-import { User } from '../../interfaces'
-import { sampleUserData } from '../../utils/sample-data'
-import Layout from '../../components/Layout'
-import List from '../../components/List'
-
-type Props = {
- items: User[]
-}
-
-const WithStaticProps = ({ items }: Props) => (
-
- Users List
-
- Example fetching data from inside getStaticProps().
-
- You are currently on: /users
-
-
-
- Go home
-
-
-
-)
-
-export const getStaticProps: GetStaticProps = async () => {
- // Example for including static props in a Next.js function component page.
- // Don't forget to include the respective types for any props passed into
- // the component.
- const items: User[] = sampleUserData
- return { props: { items } }
-}
-
-export default WithStaticProps
diff --git a/src/components/DisplayOnBrowserMount.tsx b/src/components/DisplayOnBrowserMount.tsx
new file mode 100644
index 0000000..9bb7e3b
--- /dev/null
+++ b/src/components/DisplayOnBrowserMount.tsx
@@ -0,0 +1,86 @@
+import size from 'lodash.size';
+import some from 'lodash.some';
+import React, {
+ DependencyList,
+ Fragment,
+ useState,
+} from 'react';
+
+export type Props = {
+ children: React.ReactNode;
+ deps?: DependencyList;
+};
+
+/**
+ * Utility component to properly handle expected differences between server and browser rendering.
+ * Helps to avoid "Text content did not match" warnings, during React rehydration.
+ *
+ * Optionally accepts a "deps" array of dependencies.
+ * It can be used to optimize behaviour to support both SSG/SSR universally with a different behaviour based on the rendering mode.
+ * If any deps is provided, then it'll check if any is null-ish (undefined/null)
+ * If so, it will render "null" and trigger a re-render, because in such case we consider the deps aren't fulfilled
+ * If all deps are defined, then we render the children directly because we consider we don't need to wait (optimisation, no unnecessary re-render)
+ *
+ * @example If you want to display a cookie's value universally:
+ * - If you use SSR, you'll have access to the cookie's value from the server and want to render it immediately
+ * - If you use SSG, you won't have access to the cookie's value until the browser renders the page, so you want to render "null" and then trigger a re-render to display the actual value
+ *
+ * XXX Use this helper to avoid rendering small UI (presentational) components that depend on browser-related data (e.g: localStorage, cookie, session-related data, etc.)
+ * Do not use this helper to avoid rendering big react Providers, or components who define big part of your UI layout
+ *
+ * When a React app rehydrates, it assumes that the DOM structure will match.
+ * When the React app runs on the client for the first time, it builds up a mental picture of what the DOM should look like, by mounting all of your components.
+ * Then it squints at the DOM nodes already on the page, and tries to fit the two together.
+ * It's not playing the “spot-the-differences” game it does during a typical update, it's just trying to snap the two together, so that future updates will be handled correctly.
+ *
+ * If you render something different depending on whether you're on server or browser, you're hacking the system.
+ * Because you're rendering one thing on the server, but then telling React to expect something else on the client.
+ * And this is what causes "Text content did not match" react warning.
+ *
+ * To avoid this situation, we use "useEffect", which only fires after the component has mounted.
+ * Inside the "useEffect" call, we immediately trigger a re-render, setting hasMounted to true. When this value is true, the "real" content gets rendered.
+ * When the React app adapts the DOM during rehydration, useEffect hasn't been called yet, and so we're meeting React's expectation.
+ *
+ * This process is named "Two pass rendering":
+ * The first pass, at compile-time, produces all of the static non-personal content, and leaves holes where the dynamic content will go.
+ * Then, after the React app has mounted on the user's device, a second pass stamps in all the dynamic bits that depend on client state.
+ * The downside to two-pass rendering is that it can waitFor time-to-interactive.
+ *
+ * @param props
+ * @see https://joshwcomeau.com/react/the-perils-of-rehydration/#abstractions Strongly inspired by "ClientOnly" abstraction
+ * @see https://joshwcomeau.com/react/the-perils-of-rehydration/#two-pass-rendering Two pass rendering and performances implications
+ * @see https://twitter.com/Vadorequest/status/1257658553361408002 Discussion with Josh regarding advanced usage
+ */
+const DisplayOnBrowserMount: React.FunctionComponent = (props) => {
+ const {
+ children,
+ deps = [],
+ } = props;
+ // If any dep isn't defined, then it will render "null" first, and then trigger a re-render
+ const isAnyDepsNullish = size(deps) ?
+ // If any deps was provided, check if any is null-ish
+ some(deps, (dependency: any): boolean => {
+ return dependency === null || typeof dependency === 'undefined';
+ })
+ // If no dep is provided, then it should render "null" first anyway, and then trigger a re-render
+ : true;
+ const [hasMounted, setHasMounted] = useState(!isAnyDepsNullish);
+
+ React.useEffect(() => {
+ if (isAnyDepsNullish) {
+ setHasMounted(true);
+ }
+ }, [isAnyDepsNullish]);
+
+ if (!hasMounted) {
+ return null;
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default DisplayOnBrowserMount;
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
new file mode 100644
index 0000000..35ad0c1
--- /dev/null
+++ b/src/components/Footer.tsx
@@ -0,0 +1,92 @@
+import {
+ ExternalLinkIcon,
+ QuestionOutlineIcon,
+} from '@chakra-ui/icons';
+import {
+ Box,
+ Button,
+ Flex,
+ Link as ChakraLink,
+ Spacer,
+ Tag,
+ TagLabel,
+ Tooltip,
+} from '@chakra-ui/react';
+import { css } from '@emotion/react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import React from 'react';
+import settings from '../settings';
+
+type Props = {}
+
+const Footer: React.FunctionComponent = (props) => {
+ return (
+
+
+
+ Demo:
+
+
+ With Local storage
+
+
+
+
+
+
+ Made with
+
+ {' '}Next.js{' '}
+
+
+ {' '}and{' '}
+
+ {' '}Reaflow{' '}
+
+
+
+
+
+
+
+
+
+ {' '}
+ GitHub
+
+
+
+
+
+ );
+};
+
+export default Footer;
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
new file mode 100644
index 0000000..14303c7
--- /dev/null
+++ b/src/components/Layout.tsx
@@ -0,0 +1,39 @@
+import Head from 'next/head';
+import React, { ReactNode } from 'react';
+import Footer from './Footer';
+import Nav from './Nav';
+
+type Props = {
+ children?: ReactNode
+ title?: string
+}
+
+/**
+ * Layout meant to be shared by all Next.js pages.
+ *
+ * Simply displays a header, footer, and the Next.js page in between.
+ */
+const Layout: React.FunctionComponent = (props) => {
+ const {
+ children,
+ title = 'POC Next.js + Reaflow',
+ } = props;
+
+ return (
+
+
+
{title}
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+};
+
+export default Layout;
diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx
new file mode 100644
index 0000000..8264ff5
--- /dev/null
+++ b/src/components/Nav.tsx
@@ -0,0 +1,36 @@
+import {
+ Box,
+ Center,
+} from '@chakra-ui/react';
+import { css } from '@emotion/react';
+import React from 'react';
+import settings from '../settings';
+
+type Props = {}
+
+const Nav: React.FunctionComponent = (props) => {
+ return (
+
+
+
+
+ You are a guest working on your own document. Changes are automatically saved in the browser local storage and cannot be shared or seen by anyone else.
+
+
+
+
+ );
+};
+
+export default Nav;
diff --git a/src/components/RecoilDevtools.tsx b/src/components/RecoilDevtools.tsx
new file mode 100644
index 0000000..a634686
--- /dev/null
+++ b/src/components/RecoilDevtools.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import DockMonitor from 'recoil-devtools-dock';
+import LogMonitor from 'recoil-devtools-log-monitor';
+import { RecoilLogger } from 'recoil-devtools-logger';
+import DisplayOnBrowserMount from './DisplayOnBrowserMount';
+
+/**
+ * Adds Recoil dev tools for easier understanding and debug of what happens in the Recoil store.
+ * Enabled in development mode only.
+ *
+ * @see https://github.com/ulises-jeremias/recoil-devtools
+ */
+export const RecoilDevtools = () => {
+ if (process.env.NODE_ENV !== 'development') {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/RecoilExternalStatePortal.tsx b/src/components/RecoilExternalStatePortal.tsx
new file mode 100644
index 0000000..c4581cb
--- /dev/null
+++ b/src/components/RecoilExternalStatePortal.tsx
@@ -0,0 +1,65 @@
+import {
+ Loadable,
+ RecoilState,
+ RecoilValue,
+ useRecoilCallback,
+ useRecoilTransactionObserver_UNSTABLE,
+} from 'recoil';
+
+/**
+ * Returns a Recoil state value, from anywhere in the app.
+ *
+ * Can be used outside of the React tree (outside a React component), such as in utility scripts, etc.
+
+ * must have been previously loaded in the React tree, or it won't work.
+ * Initialized as a dummy function "() => null", it's reference is updated to a proper Recoil state mutator when RecoilExternalStatePortal is loaded.
+ *
+ * @example const lastCreatedUser = getRecoilExternalLoadable(lastCreatedUserState);
+ */
+export let getRecoilExternalLoadable: (
+ recoilValue: RecoilValue,
+) => Loadable = () => null as any;
+
+/**
+ * Sets a Recoil state value, from anywhere in the app.
+ *
+ * Can be used outside of the React tree (outside a React component), such as in utility scripts, etc.
+ *
+ * must have been previously loaded in the React tree, or it won't work.
+ * Initialized as a dummy function "() => null", it's reference is updated to a proper Recoil state mutator when RecoilExternalStatePortal is loaded.
+ *
+ * @example setRecoilExternalState(lastCreatedUserState, newUser)
+ */
+export let setRecoilExternalState: (
+ recoilState: RecoilState,
+ valOrUpdater: ((currVal: T) => T) | T,
+) => void = () => null as any;
+
+/**
+ * Utility component allowing to use the Recoil state outside of a React component.
+ *
+ * It must be loaded in the _app file, inside the component.
+ * Once it's been loaded in the React tree, it allows using setRecoilExternalState and getRecoilExternalLoadable from anywhere in the app.
+ *
+ * @see https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777300212
+ * @see https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777305884
+ * @see https://recoiljs.org/docs/api-reference/core/Loadable/
+ */
+export function RecoilExternalStatePortal() {
+ // We need to update the getRecoilExternalLoadable every time there's a new snapshot
+ // Otherwise we will load old values from when the component was mounted
+ useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
+ getRecoilExternalLoadable = snapshot.getLoadable;
+ });
+
+ // We only need to assign setRecoilExternalState once because it's not temporally dependent like "get" is
+ useRecoilCallback(({ set }) => {
+ setRecoilExternalState = set;
+
+ return async () => {
+
+ };
+ })();
+
+ return <>>;
+}
diff --git a/src/components/blocks/BasePreviewBlock.tsx b/src/components/blocks/BasePreviewBlock.tsx
new file mode 100644
index 0000000..a5af018
--- /dev/null
+++ b/src/components/blocks/BasePreviewBlock.tsx
@@ -0,0 +1,26 @@
+import styled from '@emotion/styled';
+
+/**
+ * Base style used by all preview blocks.
+ */
+const BasePreviewBlock = styled.button`
+ height: 100px;
+ width: 100px;
+ cursor: grab;
+ background: #f5f5f5;
+ color: black;
+ margin-right: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 5px;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ flex: 1;
+`;
+
+export default BasePreviewBlock;
diff --git a/src/components/blocks/BlockPickerMenu.tsx b/src/components/blocks/BlockPickerMenu.tsx
new file mode 100644
index 0000000..73a23cb
--- /dev/null
+++ b/src/components/blocks/BlockPickerMenu.tsx
@@ -0,0 +1,121 @@
+import { css } from '@emotion/react';
+import classnames from 'classnames';
+import React, {
+ useEffect,
+ useState,
+} from 'react';
+import { useRecoilState } from 'recoil';
+import { blockPickerMenuSelector } from '../../states/blockPickerMenuState';
+import { OnBlockClick } from '../../types/BlockPickerMenu';
+import NodeType from '../../types/NodeType';
+import IfBlock from './IfBlock';
+import InformationBlock from './InformationBlock';
+import QuestionBlock from './QuestionBlock';
+
+type Props = {};
+
+/**
+ * Menu where the editor can select (pick) a block amongst a list of blocks.
+ *
+ * The menu is always displayed using an absolute position, on top of the canvas.
+ * It can be displayed either at the bottom of the canvas, or at a specific location.
+ * It is usually displayed at the drop location, when dropping an edge.
+ */
+const BlockPickerMenu: React.FunctionComponent = (props) => {
+ const [blockPickerMenu, setBlockPickerMenu] = useRecoilState(blockPickerMenuSelector);
+ const {
+ onBlockClick,
+ isDisplayed,
+ displayedFrom,
+ top,
+ left,
+ } = blockPickerMenu;
+
+ const [repeatAnimation, setRepeatAnimation] = useState(true);
+
+ /**
+ * When the source displaying the menu changes, replay the animations of the component.
+ *
+ * It toggles the CSS classes injected in the component to force replaying the animations.
+ * Uses a short timeout that isn't noticeable to the human eye, but is necessary for the toggle to work properly.
+ */
+ useEffect(() => {
+ setRepeatAnimation(false);
+ setTimeout(() => setRepeatAnimation(true), 1);
+ }, [displayedFrom]);
+
+ if (!isDisplayed) {
+ return null;
+ }
+
+ // console.log('displaying BlockPickerMenu');
+
+ /**
+ * Called by the specialized blocks upon click.
+ *
+ * Contains "onClick" business logic that is shared by all blocks.
+ *
+ * @param nodeType
+ */
+ const onSpecializedBlockClick: OnBlockClick = (nodeType: NodeType) => {
+ if (onBlockClick) {
+ // We provide the "blockPickerMenu" so that it is always up-to-date
+ // Otherwise, it would be out-of-date when relying on the blockPickerMenu that was bound when creating the onBlockClick function
+ onBlockClick(nodeType, blockPickerMenu);
+
+ // Automatically hide the block picker menu once a block has been picked
+ setBlockPickerMenu({
+ isDisplayed: false,
+ });
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default BlockPickerMenu;
diff --git a/src/components/blocks/IfBlock.tsx b/src/components/blocks/IfBlock.tsx
new file mode 100644
index 0000000..726d401
--- /dev/null
+++ b/src/components/blocks/IfBlock.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import BaseBlockComponent from '../../types/BaseBlockComponent';
+import { OnBlockClick } from '../../types/BlockPickerMenu';
+
+type Props = {
+ onBlockClick: OnBlockClick;
+};
+
+/**
+ * When clicking on this block (from the ), it creates a component in the canvas.
+ */
+const IfBlock: BaseBlockComponent = (props) => {
+ const {
+ onBlockClick,
+ } = props;
+
+ const onClick = () => {
+ onBlockClick('if');
+ };
+
+ return (
+
+ If/Else
+
+ );
+};
+
+export default IfBlock;
diff --git a/src/components/blocks/InformationBlock.tsx b/src/components/blocks/InformationBlock.tsx
new file mode 100644
index 0000000..027a098
--- /dev/null
+++ b/src/components/blocks/InformationBlock.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import BaseBlockComponent from '../../types/BaseBlockComponent';
+import { OnBlockClick } from '../../types/BlockPickerMenu';
+
+type Props = {
+ onBlockClick: OnBlockClick;
+};
+
+/**
+ * When clicking on this block (from the ), it creates a component in the canvas.
+ */
+const InformationBlock: BaseBlockComponent = (props) => {
+ const {
+ onBlockClick,
+ } = props;
+
+ const onClick = () => {
+ onBlockClick('information');
+ };
+
+ return (
+
+ Information
+
+ );
+};
+
+export default InformationBlock;
diff --git a/src/components/blocks/QuestionBlock.tsx b/src/components/blocks/QuestionBlock.tsx
new file mode 100644
index 0000000..9a8cf89
--- /dev/null
+++ b/src/components/blocks/QuestionBlock.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import BaseBlockComponent from '../../types/BaseBlockComponent';
+import { OnBlockClick } from '../../types/BlockPickerMenu';
+
+type Props = {
+ onBlockClick: OnBlockClick;
+};
+
+/**
+ * When clicking on this block (from the ), it creates a component in the canvas.
+ */
+const QuestionBlock: BaseBlockComponent = (props) => {
+ const {
+ onBlockClick,
+ } = props;
+
+ const onClick = () => {
+ onBlockClick('question');
+ };
+
+ return (
+
+ Question
+
+ );
+};
+
+export default QuestionBlock;
diff --git a/src/components/context/canvasUtilsContext.tsx b/src/components/context/canvasUtilsContext.tsx
new file mode 100644
index 0000000..c04972d
--- /dev/null
+++ b/src/components/context/canvasUtilsContext.tsx
@@ -0,0 +1,53 @@
+import React, { RefObject } from 'react';
+
+/**
+ * Contains utilities exposed by the Canvas ref.
+ *
+ * Contains utilities from both LayoutResult and ZoomResult. (built-in in Reaflow)
+ */
+export type CanvasUtilsContext = {
+
+ /**
+ * Ref to container div.
+ */
+ containerRef?: RefObject;
+
+ /**
+ * Center the canvas to the viewport.
+ */
+ centerCanvas?: () => void;
+
+ /**
+ * Fit the canvas to the viewport.
+ */
+ fitCanvas?: () => void;
+
+ /**
+ * Set a zoom factor of the canvas.
+ */
+ setZoom?: (factor: number) => void;
+
+ /**
+ * Zoom in on the canvas.
+ */
+ zoomIn?: () => void;
+
+ /**
+ * Zoom out on the canvas.
+ */
+ zoomOut?: () => void;
+}
+
+/**
+ * Uses native React Context API, meant to be used from hooks only, not by functional components
+ *
+ * @example Usage
+ * import canvasUtilsContext from './src/stores/canvasUtilsContext';
+ * const { containerRef }: CanvasContext = React.useContext(canvasUtilsContext);
+ *
+ * @see https://reactjs.org/docs/context.html
+ * @see https://medium.com/better-programming/react-hooks-usecontext-30eb560999f for useContext hook example (open in anonymous browser #paywall)
+ */
+export const canvasUtilsContext = React.createContext({});
+
+export default canvasUtilsContext;
diff --git a/src/components/edges/BaseEdge.tsx b/src/components/edges/BaseEdge.tsx
new file mode 100644
index 0000000..d45aa47
--- /dev/null
+++ b/src/components/edges/BaseEdge.tsx
@@ -0,0 +1,180 @@
+import classnames from 'classnames';
+import cloneDeep from 'lodash.clonedeep';
+import now from 'lodash.now';
+import React from 'react';
+import {
+ Edge,
+ EdgeData,
+} from 'reaflow';
+import {
+ SetterOrUpdater,
+ useRecoilState,
+ useSetRecoilState,
+} from 'recoil';
+import { blockPickerMenuSelector } from '../../states/blockPickerMenuState';
+import { canvasDatasetSelector } from '../../states/canvasDatasetSelector';
+import { edgesSelector } from '../../states/edgesState';
+import { lastCreatedState } from '../../states/lastCreatedState';
+import { selectedEdgesSelector } from '../../states/selectedEdgesState';
+import { selectedNodesSelector } from '../../states/selectedNodesState';
+import BaseEdgeData from '../../types/BaseEdgeData';
+import BaseEdgeProps from '../../types/BaseEdgeProps';
+import BaseNodeData from '../../types/BaseNodeData';
+import BasePortData from '../../types/BasePortData';
+import BlockPickerMenu, { OnBlockClick } from '../../types/BlockPickerMenu';
+import { CanvasDataset } from '../../types/CanvasDataset';
+import { LastCreated } from '../../types/LastCreated';
+import NodeType from '../../types/NodeType';
+import { translateXYToCanvasPosition } from '../../utils/canvas';
+import {
+ createNodeFromDefaultProps,
+ getDefaultNodePropsWithFallback,
+ upsertNodeThroughPorts,
+} from '../../utils/nodes';
+import EdgeActions from './EdgeActions';
+
+type Props = {} & BaseEdgeProps;
+
+/**
+ * Base edge component.
+ *
+ * This component contains shared business logic common to all edges.
+ * It renders a Reaflow component.
+ *
+ * The Edge renders itself as SVG HTML element wrapper, which contains the HTML element that displays the link itself.
+ *
+ * @see https://reaflow.dev/?path=/story/demos-edges
+ */
+const BaseEdge: React.FunctionComponent = (props) => {
+ const {
+ id,
+ source: sourceNodeId,
+ sourcePort: sourcePortId,
+ target: targetNodeId,
+ targetPort: targetPortId,
+ } = props;
+
+ const [blockPickerMenu, setBlockPickerMenu] = useRecoilState(blockPickerMenuSelector);
+ const [canvasDataset, setCanvasDataset] = useRecoilState(canvasDatasetSelector);
+ const [edges, setEdges] = useRecoilState(edgesSelector);
+ const { nodes } = canvasDataset;
+ const setLastCreatedNode: SetterOrUpdater = useSetRecoilState(lastCreatedState);
+ const { displayedFrom, isDisplayed } = blockPickerMenu;
+ const edge: BaseEdgeData = edges.find((edge: BaseEdgeData) => edge?.id === id) as BaseEdgeData;
+ const [selectedEdges, setSelectedEdges] = useRecoilState(selectedEdgesSelector);
+ const [selectedNodes, setSelectedNodes] = useRecoilState(selectedNodesSelector);
+
+ if (typeof edge === 'undefined') {
+ return null;
+ }
+
+ // Resolve instances of connected nodes and ports
+ const sourceNode: BaseNodeData | undefined = nodes.find((node: BaseNodeData) => node.id === sourceNodeId);
+ const sourcePort: BasePortData | undefined = sourceNode?.ports?.find((port: BasePortData) => port.id === sourcePortId);
+ const targetNode: BaseNodeData | undefined = nodes.find((node: BaseNodeData) => node.id === targetNodeId);
+ const targetPort: BasePortData | undefined = targetNode?.ports?.find((port: BasePortData) => port.id === targetPortId);
+ const isSelected = !!selectedEdges?.find((selectedEdge: string) => selectedEdge === edge.id);
+
+ // console.log('edgeProps', props);
+
+ /**
+ * Invoked when clicking on the "+" of the edge.
+ *
+ * Displays the BlockPickerMenu, which can then be used to select which Block to add to the canvas.
+ * If the BlockPickerMenu was already displayed, hides it if it was opened from the same edge.
+ *
+ * When a block is clicked, the "onBlockClick" function is invoked and creates (upserts) the node
+ * by splitting the edge in two parts and adding the new node in between.
+ *
+ * @param event
+ * @param edge_DO_NOT_USE
+ */
+ const onAddIconClick = (event: React.MouseEvent, edge_DO_NOT_USE: EdgeData): void => {
+ console.log('onAdd edge', edge, event);
+ const onBlockClick: OnBlockClick = (nodeType: NodeType) => {
+ console.log('onBlockClick (from edge add)', nodeType, edge);
+ const newNode: BaseNodeData = createNodeFromDefaultProps(getDefaultNodePropsWithFallback(nodeType));
+ const newDataset: CanvasDataset = upsertNodeThroughPorts(cloneDeep(nodes), cloneDeep(edges), edge, newNode);
+
+ setCanvasDataset(newDataset);
+ setLastCreatedNode({ node: newNode, at: now() });
+ setSelectedNodes([newNode?.id]);
+ setSelectedEdges([]);
+ };
+
+ // Converts the x/y position to a Canvas position and apply some margin for the BlockPickerMenu to display on the right bottom of the cursor
+ const [x, y] = translateXYToCanvasPosition(event.clientX, event.clientY, { left: 15, top: 15 });
+
+ setBlockPickerMenu({
+ displayedFrom: `edge-${edge.id}`,
+ // Toggles on click if the source is the same, otherwise update
+ isDisplayed: displayedFrom === `edge-${edge.id}` ? !isDisplayed : true,
+ onBlockClick,
+ eventTarget: event.target,
+ top: y,
+ left: x,
+ });
+ };
+
+ /**
+ * Invoked when clicking on the "-" of the edge.
+ *
+ * Removes the selected edge.
+ *
+ * @param event
+ * @param edge
+ */
+ const onRemoveIconClick = (event: React.MouseEvent, edge: EdgeData): void => {
+ console.log('onRemoveIconClick', event, edge);
+ setEdges(edges.filter((edge: BaseEdgeData) => edge.id !== id));
+ };
+
+ /**
+ * Selects the edge when clicking on it.
+ *
+ * XXX We're resolving the "edge" ourselves, instead of relying on the 2nd argument (edgeData),
+ * which doesn't contain all the expected properties. It is more reliable to use the current edge, which already known.
+ *
+ * @param event
+ * @param data_DO_NOT_USE
+ */
+ const onEdgeClick = (event: React.MouseEvent, data_DO_NOT_USE: BaseEdgeData) => {
+ console.log('onEdgeClick', event, edge);
+ setSelectedEdges([edge.id]);
+ };
+
+ return (
+
+ )}
+ onClick={onEdgeClick}
+ />
+ // Doesn't support children - See https://github.com/reaviz/reaflow/issues/67
+ // Possible to use a custom children to achieve this, but not great DX because of manual x/y placement
+ /*
+
+ test
+
+ * */
+ );
+};
+
+export default BaseEdge;
diff --git a/src/components/edges/EdgeActions.tsx b/src/components/edges/EdgeActions.tsx
new file mode 100644
index 0000000..0e97ee0
--- /dev/null
+++ b/src/components/edges/EdgeActions.tsx
@@ -0,0 +1,61 @@
+import React, { Fragment } from 'react';
+import {
+ Add,
+ AddProps,
+ Remove,
+ RemoveProps,
+} from 'reaflow';
+import BaseEdgeData from '../../types/BaseEdgeData';
+
+type Props = {
+ hidden: boolean;
+ onRemove?: (
+ event: React.MouseEvent,
+ edge: BaseEdgeData,
+ ) => void;
+ onAdd?: (
+ event: React.MouseEvent,
+ edge: BaseEdgeData,
+ ) => void;
+} & Partial;
+
+/**
+ * Displays the edge's actions (add node, remove current edge).
+ *
+ * Displays the actions only when the edge is being selected.
+ * Displays the actions a bit above the edge, in a line.
+ *
+ * TODO Replace this component by a component that uses HTML - awaiting https://github.com/reaviz/reaflow/pull/76
+ */
+const EdgeActions: React.FunctionComponent = (props) => {
+ const {
+ x,
+ y,
+ onAdd,
+ onRemove,
+ } = props;
+ const aboveLineY = (y || 0) - 20;
+ const removeX = (x || 0) + 20;
+
+ // console.log('EdgeActions props', props);
+
+ return (
+
+
+
+
+ );
+};
+
+export default EdgeActions;
diff --git a/src/components/editor/CanvasContainer.tsx b/src/components/editor/CanvasContainer.tsx
new file mode 100644
index 0000000..6ede80f
--- /dev/null
+++ b/src/components/editor/CanvasContainer.tsx
@@ -0,0 +1,381 @@
+import { Button } from '@chakra-ui/react';
+import { css } from '@emotion/react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { isBrowser } from '@unly/utils';
+import React, {
+ MutableRefObject,
+ useEffect,
+ useState,
+} from 'react';
+import {
+ Canvas,
+ CanvasRef,
+ EdgeProps,
+ NodeProps,
+ UndoRedoEvent,
+ useUndo,
+} from 'reaflow';
+import { useRecoilState } from 'recoil';
+import settings from '../../settings';
+import { blockPickerMenuSelector } from '../../states/blockPickerMenuState';
+import { canvasDatasetSelector } from '../../states/canvasDatasetSelector';
+import { edgesSelector } from '../../states/edgesState';
+import { nodesSelector } from '../../states/nodesState';
+import { selectedEdgesSelector } from '../../states/selectedEdgesState';
+import { selectedNodesSelector } from '../../states/selectedNodesState';
+import BaseNodeData from '../../types/BaseNodeData';
+import { isOlderThan } from '../../utils/date';
+import {
+ createNodeFromDefaultProps,
+ getDefaultNodePropsWithFallback,
+} from '../../utils/nodes';
+import { persistCanvasDatasetInLS } from '../../utils/persistCanvasDataset';
+import canvasUtilsContext from '../context/canvasUtilsContext';
+import BaseEdge from '../edges/BaseEdge';
+import NodeRouter from '../nodes/NodeRouter';
+
+type Props = {
+ canvasRef: MutableRefObject;
+}
+
+/**
+ * Canvas container.
+ *
+ * All nodes and edges are drawn within the element.
+ * Handles undo/redo.
+ * Handles selection of nodes/edges. (one at a time, multi-select isn't supported)
+ *
+ * Positioned in absolute position and takes all the spaces of its parent element (PlaygroundContainer).
+ *
+ * @see https://github.com/reaviz/reaflow
+ * @see https://reaflow.dev/?path=/story/docs-getting-started-basics--page
+ */
+const CanvasContainer: React.FunctionComponent = (props): JSX.Element | null => {
+ if (!isBrowser()) {
+ return null;
+ }
+
+ const {
+ canvasRef,
+ } = props;
+
+ /**
+ * The canvas ref contains useful properties (xy, scroll, etc.) and functions (zoom, centerCanvas, etc.)
+ *
+ * @see https://reaflow.dev/?path=/story/docs-advanced-refs--page
+ */
+ useEffect(() => {
+ console.log('canvasRef', canvasRef);
+ canvasRef?.current?.fitCanvas?.();
+ }, [canvasRef]);
+
+ const [blockPickerMenu, setBlockPickerMenu] = useRecoilState(blockPickerMenuSelector);
+ const [canvasDataset, setCanvasDataset] = useRecoilState(canvasDatasetSelector);
+ const [nodes, setNodes] = useRecoilState(nodesSelector);
+ const [edges, setEdges] = useRecoilState(edgesSelector);
+ const [selectedNodes, setSelectedNodes] = useRecoilState(selectedNodesSelector);
+ const [selectedEdges, setSelectedEdges] = useRecoilState(selectedEdgesSelector);
+ const selections = selectedNodes; // TODO merge selected nodes and edges
+ const [hasClearedUndoHistory, setHasClearedUndoHistory] = useState(false);
+ const [cursorXY, setCursorXY] = useState<[number, number]>([0, 0]);
+
+ /**
+ * When nodes or edges are modified, updates the persisted data in the local storage.
+ *
+ * Persisted data are automatically loaded upon page refresh.
+ */
+ useEffect(() => {
+ persistCanvasDatasetInLS(canvasDataset);
+ }, [canvasDataset]);
+
+ /**
+ * Uses Reaflow Undo/Redo helpers.
+ *
+ * Automatically binds shortcuts (cmd+z/cmd+shift+z).
+ *
+ * TODO Doesn't handle massive undos through shortcut (cmd+z) very well - See https://github.com/reaviz/reaflow/issues/60
+ *
+ * @see https://reaflow.dev/?path=/story/demos-undo-redo
+ */
+ const {
+ undo,
+ redo,
+ canUndo,
+ canRedo,
+ clear,
+ } = useUndo({
+ nodes,
+ edges,
+ onUndoRedo: (state: UndoRedoEvent) => {
+ console.log('Undo / Redo', state);
+
+ setCanvasDataset({
+ nodes: state?.nodes || [],
+ edges: state?.edges || [],
+ });
+ },
+ maxHistory: Infinity,
+ });
+
+ /**
+ * Ensures the start node is always present.
+ *
+ * Will automatically create the start node even if all the nodes are deleted.
+ */
+ useEffect(() => {
+ const startNode: BaseNodeData | undefined = nodes?.find((node: BaseNodeData) => node?.data?.type === 'start');
+
+ if (!startNode) {
+ console.info(`No "start" node found. Creating one automatically.`, nodes);
+ setNodes([
+ ...nodes,
+ createNodeFromDefaultProps(getDefaultNodePropsWithFallback('start')),
+ ]);
+
+ // Clearing the undo/redo history to avoid allowing the editor to "undo" the creation of the "start" node
+ // If the "start" node creation step is "undoed" then it'd be re-created automatically, which would erase the whole history
+ // See https://github.com/reaviz/reaflow/issues/60#issuecomment-780499761
+ // Doing it only once after a reset to avoid infinite loop rendering
+ if (!hasClearedUndoHistory) {
+ console.info('Clearing undo/redo history to start from a clean state.');
+ clear();
+ setHasClearedUndoHistory(true);
+ }
+ }
+ }, [nodes]);
+
+ /**
+ * When clicking on the canvas:
+ * - Unselect all elements (nodes, edges)
+ * - Hide the block menu picker, unless it was opened through targeting the canvas itself
+ * (avoids closing the menu when dropping an edge on the canvas)
+ *
+ * XXX Sometimes, it is triggered even though the click doesn't target the canvas itself specifically.
+ * For instance, it might be triggered when clicking on an SVG displayed within a .
+ */
+ const onCanvasClick = (event: React.MouseEvent): void => {
+ if (event.target === canvasRef?.current?.svgRef?.current) {
+ // Unselecting all selected elements (nodes, edges)
+ if (selectedNodes?.length > 0) {
+ setSelectedNodes([]);
+ }
+ if (selectedEdges?.length > 0) {
+ setSelectedEdges([]);
+ }
+ console.log('onCanvasClick event', event);
+
+ let isBlockPickerMenuTargetingCanvas = false;
+ if (typeof blockPickerMenu?.eventTarget !== 'undefined') {
+ isBlockPickerMenuTargetingCanvas = blockPickerMenu?.eventTarget === canvasRef?.current?.svgRef?.current;
+ }
+
+ // Automatically hide the blockPickerMenu when clicking on the canvas, if it is displayed and was created more than a second ago
+ // Using a delay is a workaround, because when dropping the edge onto the canvas, it counts as a click
+ // If we weren't using a delay, the blockPickerMenu would be displayed and then automatically hidden
+ if (blockPickerMenu?.isDisplayed && (!isBlockPickerMenuTargetingCanvas || isOlderThan(blockPickerMenu?.at, 1000))) {
+ setBlockPickerMenu({
+ isDisplayed: false,
+ });
+ }
+
+ setCursorXY([event?.clientX, event?.clientY]);
+ }
+ };
+
+ /**
+ * Node component. All nodes will render trough this component.
+ *
+ * Uses the NodeRouter component which will render a different node layout, depending on the node "type".
+ */
+ const Node = (nodeProps: NodeProps) => {
+ return (
+
+ );
+ };
+
+ /**
+ * Edge component. All edges will render trough this component.
+ *
+ * All edges render the same way, no matter to which node they're linked to.
+ * (There is no "EdgeRouter", unlike with nodes)
+ */
+ const Edge = (edgeProps: EdgeProps) => {
+ return (
+
+ );
+ };
+
+ /**
+ * Those options will be forwarded to elkLayout under the "options" property.
+ *
+ * @see https://github.com/reaviz/reaflow/blob/master/src/layout/elkLayout.ts Default values applied by Reaflow
+ * @see https://github.com/kieler/elkjs#api
+ * @see https://www.eclipse.org/elk/reference.html
+ * @see https://www.eclipse.org/elk/reference/options.html
+ */
+ const elkLayoutOptions = {
+ 'elk.algorithm': 'layered',
+ };
+
+ return (
+ (global rules, within the
)
+ .reaflow-canvas {
+ // Make all edges display an infinite dash animation
+ .edge {
+ stroke: ${settings.canvas.edges.strokeColor};
+ stroke-dasharray: 5;
+ animation: dashdraw .5s linear infinite;
+ stroke-width: 1;
+
+ &.is-selected {
+ stroke: ${settings.canvas.edges.selected.strokeColor};
+ stroke-dasharray: 0;
+ animation: none;
+ }
+ }
+
+ .port {
+ // Ports are being highlighted during edge dragging, to make it obvious what are the possible destination ports
+ &.is-highlighted {
+ stroke: blue !important;
+ }
+
+ // Make all output ports used by IfNode display differently to distinguish them easily
+ &.port-if {
+ &-true {
+ stroke: green !important;
+ }
+
+ &-false {
+ stroke: red !important;
+ }
+ }
+ }
+
+ @keyframes dashdraw {
+ 0% {
+ stroke-dashoffset: 10;
+ }
+ }
+ }
+ `}
+ >
+
+
+ Undo
+
+
+ Redo
+
+
+
+
+ {
+ if (confirm(`Remove all nodes and edges?`)) {
+ setHasClearedUndoHistory(false); // Reset to allow clearing history even if the option is used several times in the same session
+ setCanvasDataset({
+ nodes: [],
+ edges: [],
+ });
+ }
+ }}
+ >
+ Clear all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ X: {cursorXY?.[0]} | Y: {cursorXY?.[1]}
+
+
+
+ console.log('Layout', layout)}
+ layoutOptions={elkLayoutOptions}
+ />
+
+
+ );
+};
+
+export default CanvasContainer;
diff --git a/src/components/editor/ContainerSeparator.tsx b/src/components/editor/ContainerSeparator.tsx
new file mode 100644
index 0000000..256acdc
--- /dev/null
+++ b/src/components/editor/ContainerSeparator.tsx
@@ -0,0 +1,29 @@
+import { css } from '@emotion/react';
+import React from 'react';
+import settings from '../../settings';
+
+type Props = {}
+
+/**
+ * Unused, kept for later maybe.
+ *
+ * @deprecated
+ */
+const ContainerSeparator: React.FunctionComponent = (props) => {
+ return (
+
+ );
+};
+
+export default ContainerSeparator;
diff --git a/src/components/editor/EditorContainer.tsx b/src/components/editor/EditorContainer.tsx
new file mode 100644
index 0000000..625b728
--- /dev/null
+++ b/src/components/editor/EditorContainer.tsx
@@ -0,0 +1,45 @@
+import { css } from '@emotion/react';
+import React, {
+ MutableRefObject,
+ useRef,
+} from 'react';
+import { CanvasRef } from 'reaflow';
+import settings from '../../settings';
+import PlaygroundContainer from './PlaygroundContainer';
+
+type Props = {};
+
+/**
+ * Displays the playground container.
+ *
+ * Uses a relative position, to make the playground use an absolute position based on this container relative position.
+ * Takes as much space as possible (full page width, full page height minus the height of header/footer components).
+ */
+const EditorContainer: React.FunctionComponent = (props): JSX.Element | null => {
+
+ /**
+ * Used to create a reference to the canvas.
+ *
+ * XXX Unused in this file at the moment, but was necessary when using drag & drop features (which were removed since).
+ * Kept in case it'd be useful later.
+ */
+ const canvasRef: MutableRefObject = useRef(null);
+
+ return (
+
+ );
+};
+
+export default EditorContainer;
diff --git a/src/components/editor/PlaygroundContainer.tsx b/src/components/editor/PlaygroundContainer.tsx
new file mode 100644
index 0000000..48eccb2
--- /dev/null
+++ b/src/components/editor/PlaygroundContainer.tsx
@@ -0,0 +1,52 @@
+import { css } from '@emotion/react';
+import React, { MutableRefObject } from 'react';
+import { CanvasRef } from 'reaflow';
+import BlockPickerMenu from '../blocks/BlockPickerMenu';
+import CanvasContainer from './CanvasContainer';
+
+type Props = {
+ canvasRef: MutableRefObject;
+}
+
+/**
+ * The playground contains the CanvasContainer and the background of the canvas.
+ *
+ * Also contains absolutely-positioned components that have are singletons (one instance at a time) and must appear on top of the canvas.
+ *
+ * @see https://github.com/reaviz/reaflow
+ */
+const PlaygroundContainer: React.FunctionComponent = (props): JSX.Element | null => {
+ const {
+ canvasRef,
+ } = props;
+
+ console.log('Playground render');
+
+ return (
+
+ );
+};
+
+export default PlaygroundContainer;
diff --git a/src/components/hooks/useCanvasUtils.tsx b/src/components/hooks/useCanvasUtils.tsx
new file mode 100644
index 0000000..ff746d4
--- /dev/null
+++ b/src/components/hooks/useCanvasUtils.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import canvasUtilsContext, { CanvasUtilsContext } from '../context/canvasUtilsContext';
+
+export type CanvasUtils = CanvasUtilsContext;
+
+/**
+ * Hook to access Reaflow Canvas utilities.
+ *
+ * Those utilities are provided by Reaflow itself, and made available to all children components within.
+ * Uses canvasUtilsContext internally (provides an identical API).
+ *
+ * This hook should be used by components in favor of canvasUtilsContext directly,
+ * because it grants higher flexibility if you ever need to change the implementation (e.g: use something else than React.Context, like Redux/MobX/Recoil).
+ *
+ * @see https://slides.com/djanoskova/react-context-api-create-a-reusable-snackbar#/11
+ */
+const useCanvasUtils = (): CanvasUtils => {
+ return React.useContext(canvasUtilsContext);
+};
+
+export default useCanvasUtils;
diff --git a/src/components/nodes/BaseNode.tsx b/src/components/nodes/BaseNode.tsx
new file mode 100644
index 0000000..55dd475
--- /dev/null
+++ b/src/components/nodes/BaseNode.tsx
@@ -0,0 +1,488 @@
+import { css } from '@emotion/react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import classnames from 'classnames';
+import cloneDeep from 'lodash.clonedeep';
+import remove from 'lodash.remove';
+import React, {
+ KeyboardEventHandler,
+ MouseEventHandler,
+ useEffect,
+ useState,
+} from 'react';
+import {
+ Node,
+ NodeChildProps,
+ Remove,
+} from 'reaflow';
+import { useRecoilState } from 'recoil';
+import settings from '../../settings';
+import { blockPickerMenuSelector } from '../../states/blockPickerMenuState';
+import { canvasDatasetSelector } from '../../states/canvasDatasetSelector';
+import { lastCreatedState } from '../../states/lastCreatedState';
+import { nodesSelector } from '../../states/nodesState';
+import { selectedNodesSelector } from '../../states/selectedNodesState';
+import BaseNodeComponent from '../../types/BaseNodeComponent';
+import BaseNodeData from '../../types/BaseNodeData';
+import { BaseNodeDefaultProps } from '../../types/BaseNodeDefaultProps';
+import BaseNodeProps, { PatchCurrentNode } from '../../types/BaseNodeProps';
+import BasePortData from '../../types/BasePortData';
+import { CanvasDataset } from '../../types/CanvasDataset';
+import { GetBaseNodeDefaultPropsProps } from '../../types/GetBaseNodeDefaultProps';
+import { SpecializedNodeProps } from '../../types/nodes/SpecializedNodeProps';
+import NodeType from '../../types/NodeType';
+import { isYoungerThan } from '../../utils/date';
+import {
+ cloneNode,
+ isNodeReachable,
+ removeAndUpsertNodesThroughPorts,
+} from '../../utils/nodes';
+import { createPort } from '../../utils/ports';
+import BasePort from '../ports/BasePort';
+
+type Props = BaseNodeProps & {
+ hasCloneAction?: boolean;
+ hasDeleteAction?: boolean;
+};
+
+const fallbackDefaultWidth = 200;
+const fallbackDefaultHeight = 100;
+
+/**
+ * Base node component.
+ *
+ * This component contains shared business logic common to all nodes.
+ * It renders a Reaflow component, which contains a HTML element that allows us to write advanced HTML elements within.
+ *
+ * The Node is rendered as SVG element.
+ * Beware the will appear "on top" of the , and thus the Node will not receive some events because they're caught by the .
+ *
+ * XXX If you want to change the behavior of all nodes while avoid code duplication, here's the place.
+ *
+ * @see https://reaflow.dev/?path=/story/demos-nodes
+ * @see https://github.com/reaviz/reaflow/issues/45 Using `foreignObject` "steals" all `Node` events (onEnter, etc.) - How to forward events when using foreignObject?
+ * @see https://github.com/reaviz/reaflow/issues/50 `useSelection` hook `onKeyDown` event doesn't work with `foreignObject` - Multiple selection doesn't work when using a `foreignObject
+ * @see https://github.com/reaviz/reaflow/issues/44 React select component displays/hides itself randomly (as `foreignObject`)
+ * TODO link doc about foreignObject - awaiting https://github.com/reaviz/reaflow/pull/74
+ */
+const BaseNode: BaseNodeComponent = (props) => {
+ const {
+ children, // Don't forward, overridden in this file
+ node, // Don't forward, not expected
+ hasCloneAction = true, // Don't forward, not expected
+ hasDeleteAction = true, // Don't forward, not expected
+ ...nodeProps // All props that are left will be forwarded to the Node component
+ } = props;
+
+ const [blockPickerMenu, setBlockPickerMenu] = useRecoilState(blockPickerMenuSelector);
+ const [nodes, setNodes] = useRecoilState(nodesSelector);
+ const [canvasDataset, setCanvasDataset] = useRecoilState(canvasDatasetSelector);
+ const { edges } = canvasDataset;
+ const [selectedNodes, setSelectedNodes] = useRecoilState(selectedNodesSelector);
+ const isSelected = !!selectedNodes?.find((selectedNode: string) => selectedNode === node.id);
+ const nodeType: NodeType = node?.data?.type as NodeType;
+ const isReachable = isNodeReachable(node, edges);
+ const [lastCreated] = useRecoilState(lastCreatedState);
+ const lastCreatedNode = lastCreated?.node;
+ const lastCreatedAt = lastCreated?.at;
+ const isLastCreatedNode = lastCreated?.node?.id === node?.id;
+
+ const recentlyCreatedMaxAge = 5000; // TODO convert to settings
+ // Used to highlight the last created node for a few seconds after it's been created, to make it easier to understand where it's located
+ // Particularly useful to the editor when ELK changes the nodes position to avoid losing track of the node that was just created
+ const [isRecentlyCreated, setIsRecentlyCreated] = useState(isYoungerThan(lastCreatedAt, recentlyCreatedMaxAge));
+
+ /**
+ * Stops the highlight of the last created node when the node's age has reached max age.
+ */
+ useEffect(() => {
+ if (isRecentlyCreated) {
+ setTimeout(() => {
+ setIsRecentlyCreated(false);
+ }, recentlyCreatedMaxAge + 1);
+ }
+ }, []);
+
+ /**
+ * Path the properties of the current node.
+ *
+ * Only updates the provided properties, doesn't update other properties.
+ * Also merges the 'data' object, by keeping existing data and only overwriting those that are specified.
+ *
+ * XXX Make sure to call this function once per function call, otherwise only the last patch call would be persisted correctly
+ * (multiple calls within the same function would be overridden by the last patch,
+ * because the "node" used as reference wouldn't be updated right away and would still use the same (outdated) reference)
+ * TLDR; Don't use "patchCurrentNode" multiple times in the same function, it won't work as expected
+ *
+ * @param patch
+ */
+ const patchCurrentNode: PatchCurrentNode = (patch: Partial): void => {
+ const nodeToUpdateIndex = nodes.findIndex((node: BaseNodeData) => node.id === nodeProps.id);
+ const existingNode: BaseNodeData = nodes[nodeToUpdateIndex];
+ const nodeToUpdate = {
+ ...existingNode,
+ ...patch,
+ data: {
+ ...existingNode.data || {},
+ ...patch.data || {},
+ },
+ id: existingNode.id, // Force keep same id to avoid edge cases
+ };
+ console.log('patchCurrentNode before', existingNode, 'after:', nodeToUpdate, 'using patch:', patch);
+
+ const newNodes = cloneDeep(nodes);
+ // @ts-ignore
+ newNodes[nodeToUpdateIndex] = nodeToUpdate;
+
+ setNodes(newNodes);
+ };
+
+ /**
+ * Clones the current node.
+ *
+ * @param event
+ */
+ const onNodeClone = (event: React.MouseEvent) => {
+ const clonedNode: BaseNodeData = cloneNode(node);
+ console.log('clonedNode', clonedNode, nodes);
+
+ setNodes([
+ ...nodes,
+ clonedNode,
+ ]);
+ };
+
+ /**
+ * Removes the current node.
+ *
+ * Upsert its descendant if there were any. (auto-link all descendants to all its ascendants)
+ *
+ * Triggered when clicking on the "x" remove button that appears when a node is selected.
+ *
+ * @param event
+ */
+ const onNodeRemove = (event: React.MouseEvent) => {
+ console.log('onNodeRemove', event, node);
+ const dataset: CanvasDataset = removeAndUpsertNodesThroughPorts(nodes, edges, node);
+ const newSelectedNodes = remove(selectedNodes, node?.id);
+
+ setCanvasDataset(dataset);
+
+ // Updates selected nodes to make sure we don't keep selected nodes that have been deleted
+ setSelectedNodes(newSelectedNodes);
+
+ // Hide the block picker menu.
+ // Forces to reset the function bound to onBlockClick. Necessary when there is one or none node left.
+ setBlockPickerMenu({
+ isDisplayed: false,
+ });
+ };
+
+ /**
+ * Selects the current node when clicking on it.
+ *
+ * XXX We're resolving the "node" ourselves, instead of relying on the 2nd argument (nodeData),
+ * which might return null depending on where in the node the click was performed
+ * (because of the which is displayed on top of the element and might hijack mouse events).
+ *
+ * @param event
+ */
+ const onNodeClick = (event: React.MouseEvent) => {
+ console.log(`node clicked (${node?.data?.type})`, 'node:', node);
+ setSelectedNodes([node.id]);
+ };
+
+ /**
+ * When the mouse enters a node (on hover).
+ *
+ * XXX Tried to detect when entering/leaving a node, but it's hard because of foreignObject which yields a lot of false positive events
+ * I couldn't reliably tell whether we're actually entering/leaving a node
+ * The goal was to to update mouseEnteredState accordingly, to know which node/port are currently being hovered/entered
+ * to bind node/ports in a reliable way when dropping an edge onto a node/port.
+ * But, our current implementation inf "BasePort.onPortDragEnd" (which relies on the DOM) is much more reliable.
+ *
+ * @param event
+ * @param node
+ */
+ const onNodeEnter = (event: React.MouseEvent, node: BaseNodeData) => {
+ // console.log('onNodeEnter', event.target);
+
+ // @ts-ignore
+ // const isNode = event.target?.classList?.contains('node-svg-rect');
+ // if (isNode) {
+ // // console.log('Entering node')
+ // }
+ };
+
+ /**
+ * When the mouse leaves a node (leaves hover area).
+ *
+ * XXX Tried to detect when entering/leaving a node, but it's hard because of foreignObject which yields a lot of false positive events
+ * I couldn't reliably tell whether we're actually entering/leaving a node
+ * The goal was to to update mouseEnteredState accordingly, to know which node/port are currently being hovered/entered
+ * to bind node/ports in a reliable way when dropping an edge onto a node/port.
+ * But, our current implementation inf "BasePort.onPortDragEnd" (which relies on the DOM) is much more reliable.
+ *
+ * @param event
+ * @param node
+ */
+ const onNodeLeave = (event: React.MouseEvent, node: BaseNodeData) => {
+ // console.log('onNodeLeave', event.target);
+ // console.log('containerRef?.current', containerRef?.current)
+
+ // // @ts-ignore
+ // const isChildrenOfNodeContainer = event.target?.closest('.node-container');
+ // // @ts-ignore
+ // const isChildrenOfNodeRect = event.target?.closest('.node-svg-rect');
+ // const isNode = isChildrenOfNodeContainer || isChildrenOfNodeRect;
+ //
+ // if (isNode) {
+ // console.log('Hovering node')
+ // }
+ };
+
+ /**
+ * When a keyboard key is pressed/released.
+ *
+ * @param event
+ * @param node
+ */
+ const onKeyDown = (event: React.KeyboardEvent, node: BaseNodeData) => {
+ console.log('onKeyDown', event, node);
+ };
+
+ // console.log('BaseNode nodeProps', nodeProps);
+
+ return (
+ )}
+ port={( )}
+ >
+ {
+ /**
+ * Renders the foreignObject and common layout used by all nodes.
+ *
+ * XXX CSS styles applied here will be correctly applied to elements created in specialised node components.
+ *
+ * @param nodeProps
+ */
+ (nodeProps: NodeChildProps) => {
+ const {
+ width,
+ height,
+ } = nodeProps;
+ const specializedNodeProps: SpecializedNodeProps = {
+ ...nodeProps,
+ isSelected,
+ isReachable,
+ lastCreated,
+ patchCurrentNode,
+ };
+
+ return (
+
+ // Allows using events specific to the Reaflow component (onClick, onEnter, onLeave, etc.)
+ pointer-events: none;
+
+ .node,
+ .node-header {
+ pointer-events: none;
+ }
+
+ .node-action,
+ .node-content {
+ pointer-events: auto;
+ }
+
+ .node {
+ margin: 15px;
+
+ // XXX Elements within a that are using the CSS "position" attribute won't be shown properly,
+ // unless they're wrapped into a container using a "fixed" position.
+ // Solves the display of React Select element.
+ // See https://github.com/chakra-ui/chakra-ui/issues/3288#issuecomment-776316200
+ position: fixed;
+
+ // Take full size of its parent, minus the margins (left/right)
+ width: calc(100% - 30px); // Depends on above "margin" value
+ height: calc(100% - 30px); // Depends on above "margin" value
+ }
+
+ .is-unreachable-warning {
+ pointer-events: auto;
+ color: orange;
+ float: left;
+ cursor: help;
+ }
+
+ // Applied to all textarea for all nodes
+ .textarea {
+ margin-top: 15px;
+ background-color: #eaeaea;
+ }
+ `}
+ // Use the same onClick/onMouseEnter/onMouseLeave handlers as the one used by the Node component, to yield the same behavior whether clicking on the or on the element
+ onClick={onNodeClick as MouseEventHandler}
+ onMouseEnter={onNodeEnter as MouseEventHandler}
+ onMouseLeave={onNodeLeave as MouseEventHandler}
+ onKeyDown={onKeyDown as KeyboardEventHandler}
+ >
+
+
+ {
+ hasCloneAction && (
+
+
+
+ )
+ }
+
+ {
+ hasDeleteAction && (
+
+
+
+ )
+ }
+
+
+
+ {
+ // Displays a warning icon at the left of the node's title (using css float to avoid break line)
+ !isReachable && (
+
+ alert(`This node is not reachable because there are no edge connected to its entry port.`)}
+ />
+
+ )
+ }
+
+ {
+ // Invoke the children as a function, or render the children as a component, if it's not a function
+ // @ts-ignore
+ typeof children === 'function' ? (children({ ...specializedNodeProps })) : children
+ }
+
+
+
+ );
+ }
+ }
+
+ );
+};
+
+/**
+ * By default, a node has 2 ports, one on the east side (left) and one on the west side (right).
+ *
+ * XXX The height/width properties must be set here because they're used for calculations of the port's placement.
+ * If we change them directly in the component, the ports will be misaligned with the node.
+ */
+BaseNode.getDefaultPorts = (): BasePortData[] => {
+ return [
+ createPort({
+ height: settings.canvas.ports.radius,
+ width: settings.canvas.ports.radius,
+ alignment: 'CENTER',
+ side: 'WEST',
+ }),
+ createPort({
+ height: settings.canvas.ports.radius,
+ width: settings.canvas.ports.radius,
+ alignment: 'CENTER',
+ side: 'EAST',
+ }),
+ ];
+};
+
+/**
+ * Builds the defaults properties of a node.
+ *
+ * Used when creating a new node.
+ *
+ * @param props
+ */
+BaseNode.getDefaultNodeProps = (props: GetBaseNodeDefaultPropsProps): BaseNodeDefaultProps => {
+ const { type, defaultHeight, defaultWidth } = props;
+
+ return {
+ type,
+ defaultWidth: defaultWidth || fallbackDefaultWidth,
+ defaultHeight: defaultHeight || fallbackDefaultHeight,
+ // @ts-ignore
+ ports: BaseNode.getDefaultPorts(),
+ // nodePadding: 10 // TODO try it (left/top/bottom/right)
+ };
+};
+
+export default BaseNode;
diff --git a/src/components/nodes/IfNode.tsx b/src/components/nodes/IfNode.tsx
new file mode 100644
index 0000000..b64d7c6
--- /dev/null
+++ b/src/components/nodes/IfNode.tsx
@@ -0,0 +1,187 @@
+import { css } from '@emotion/react';
+import React, { Fragment } from 'react';
+import settings from '../../settings';
+import BaseNodeComponent from '../../types/BaseNodeComponent';
+import { BaseNodeDefaultProps } from '../../types/BaseNodeDefaultProps';
+import BaseNodeProps from '../../types/BaseNodeProps';
+import BasePortData from '../../types/BasePortData';
+import { IfNodeData } from '../../types/nodes/IfNodeData';
+import { SpecializedNodeProps } from '../../types/nodes/SpecializedNodeProps';
+import NodeType from '../../types/NodeType';
+import { OnSelectedOptionChange } from '../../types/OnSelectedOptionChange';
+import { ReactSelectDefaultOption } from '../../types/ReactSelect';
+import { createPort } from '../../utils/ports';
+import SelectComparisonOperator from '../plugins/SelectComparisonOperator';
+import SelectExpectedValue from '../plugins/SelectExpectedValue';
+import SelectVariable from '../plugins/SelectVariable';
+import BaseNode from './BaseNode';
+
+type Props = {} & BaseNodeProps;
+
+const nodeType: NodeType = 'if';
+const defaultWidth = 300;
+const defaultHeight = 300;
+
+/**
+ * If/Else node.
+ *
+ * Used to split the workflow depending on the result of comparison between 2 variables.
+ *
+ * Displays 3 select inputs to define how the compared variable should be compared to the expected value.
+ * Has one west port and two east ports.
+ * The west port allows unlimited links to other nodes.
+ */
+const IfNode: BaseNodeComponent = (props) => {
+ return (
+
+ {
+ (nodeProps: SpecializedNodeProps) => {
+ const {
+ node,
+ patchCurrentNode,
+ } = nodeProps;
+
+ /**
+ * Updates the current node "comparedVariableName" value.
+ *
+ * @param selectedOption
+ * @param actionMeta
+ */
+ const onSelectedComparedVariableChange: OnSelectedOptionChange = (selectedOption: ReactSelectDefaultOption | undefined, actionMeta) => {
+ const newValue = selectedOption?.value;
+
+ // Do not update when the value isn't different
+ if (newValue !== node?.data?.comparedVariableName) {
+ // Updates the value in the Recoil store
+ patchCurrentNode({
+ data: {
+ comparedVariableName: newValue,
+ },
+ } as IfNodeData);
+ }
+ };
+
+ /**
+ * Updates the current node "comparisonOperator" value.
+ *
+ * @param selectedOption
+ * @param actionMeta
+ */
+ const onSelectedComparedComparisonOperatorChange: OnSelectedOptionChange = (selectedOption: ReactSelectDefaultOption | undefined, actionMeta) => {
+ const newValue = selectedOption?.value;
+
+ // Do not update when the value isn't different
+ if (newValue !== node?.data?.comparisonOperator) {
+ // Updates the value in the Recoil store
+ patchCurrentNode({
+ data: {
+ comparisonOperator: newValue,
+ },
+ } as IfNodeData);
+ }
+ };
+
+ /**
+ * Updates the current node "expectedValue" value.
+ *
+ * @param selectedOption
+ * @param actionMeta
+ */
+ const onSelectedComparisonChange: OnSelectedOptionChange = (selectedOption: ReactSelectDefaultOption | undefined, actionMeta) => {
+ const newValue = selectedOption?.value;
+
+ // Do not update when the value isn't different
+ if (newValue !== node?.data?.expectedValue) {
+ // Updates the value in the Recoil store
+ patchCurrentNode({
+ data: {
+ expectedValue: newValue,
+ },
+ } as IfNodeData);
+ }
+ };
+
+ return (
+
+
+ If
+
+
+
+
+
+
+
+
+
+ Else
+
+
+ );
+ }
+ }
+
+ );
+};
+
+IfNode.getDefaultPorts = (): BasePortData[] => {
+ return [
+ createPort({
+ height: settings.canvas.ports.radius,
+ width: settings.canvas.ports.radius,
+ alignment: 'CENTER',
+ side: 'WEST',
+ }),
+ createPort({
+ height: settings.canvas.ports.radius,
+ width: settings.canvas.ports.radius,
+ alignment: 'CENTER',
+ side: 'EAST',
+ className: 'port-if-true',
+ }),
+ createPort({
+ height: settings.canvas.ports.radius,
+ width: settings.canvas.ports.radius,
+ alignment: 'CENTER',
+ side: 'EAST',
+ className: 'port-if-false',
+ }),
+ ];
+};
+
+IfNode.getDefaultNodeProps = (): BaseNodeDefaultProps => {
+ return {
+ type: nodeType,
+ defaultWidth: defaultWidth,
+ defaultHeight: defaultHeight,
+ // @ts-ignore
+ ports: IfNode.getDefaultPorts(),
+ };
+};
+
+export default IfNode;
diff --git a/src/components/nodes/InformationNode.tsx b/src/components/nodes/InformationNode.tsx
new file mode 100644
index 0000000..ad4bb34
--- /dev/null
+++ b/src/components/nodes/InformationNode.tsx
@@ -0,0 +1,139 @@
+import React, {
+ Fragment,
+ useEffect,
+ useState,
+} from 'react';
+import { DebounceInput } from 'react-debounce-input';
+import { TextareaHeightChangeMeta } from 'react-textarea-autosize/dist/declarations/src';
+import BaseNodeComponent from '../../types/BaseNodeComponent';
+import { BaseNodeDefaultProps } from '../../types/BaseNodeDefaultProps';
+import BaseNodeProps from '../../types/BaseNodeProps';
+import { InformationNodeData } from '../../types/nodes/InformationNodeData';
+import { SpecializedNodeProps } from '../../types/nodes/SpecializedNodeProps';
+import NodeType from '../../types/NodeType';
+import { isYoungerThan } from '../../utils/date';
+import Textarea from '../plugins/Textarea';
+import BaseNode from './BaseNode';
+
+type Props = {} & BaseNodeProps;
+
+const nodeType: NodeType = 'information';
+const defaultWidth = 200;
+const defaultHeight = 100;
+
+/**
+ * Information node.
+ *
+ * Used to display an information (as text).
+ *
+ * Displays a multi lines text input. (textarea)
+ * Has one west port and one east port.
+ * The west port allows unlimited links to other nodes.
+ */
+const InformationNode: BaseNodeComponent = (props) => {
+ return (
+
+ {
+ (nodeProps: SpecializedNodeProps) => {
+ const {
+ node,
+ lastCreated,
+ patchCurrentNode,
+ } = nodeProps;
+ const [informationTextareaAdditionalHeight, setInformationTextareaAdditionalHeight] = useState(0);
+ const lastCreatedNode = lastCreated?.node;
+ const lastCreatedAt = lastCreated?.at;
+
+ // Autofocus works fine when the node is inside the viewport, but when it's created outside it moves the viewport back at the beginning
+ const shouldAutofocus = false && lastCreatedNode?.id === node.id && isYoungerThan(lastCreatedAt, 1000);
+
+ /**
+ * Calculates the node's height dynamically.
+ *
+ * The node's height is dynamic and depends on various parameters (length of text, etc.).
+ */
+ useEffect(() => {
+ const newHeight = defaultHeight + informationTextareaAdditionalHeight;
+
+ // Only update the height if it's different
+ if (node?.height !== newHeight) {
+ patchCurrentNode({
+ height: newHeight,
+ });
+ }
+ }, [informationTextareaAdditionalHeight]);
+
+ /**
+ * When textarea input height changes, we need to increase the height of the whole node accordingly.
+ *
+ * @param height
+ * @param meta
+ */
+ const onTextHeightChange = (height: number, meta: TextareaHeightChangeMeta) => {
+ // Only consider additional height, by ignoring the height of the first row
+ const additionalHeight = height - meta.rowHeight;
+
+ if (informationTextareaAdditionalHeight !== additionalHeight) {
+ setInformationTextareaAdditionalHeight(additionalHeight);
+ }
+ };
+
+ /**
+ * Updates the current node "text" value.
+ *
+ * @param event
+ */
+ const onTextInputValueChange = (event: any) => {
+ const newValue = event.target.value;
+
+ // Updates the value in the Recoil store
+ patchCurrentNode({
+ data: {
+ informationText: newValue,
+ },
+ } as InformationNodeData);
+ };
+
+ return (
+
+
+ Information
+
+
+
+
+
+
+ );
+ }
+ }
+
+ );
+};
+InformationNode.getDefaultNodeProps = (): BaseNodeDefaultProps => {
+ return {
+ type: nodeType,
+ defaultWidth: defaultWidth,
+ defaultHeight: defaultHeight,
+ // @ts-ignore
+ ports: BaseNode.getDefaultPorts(),
+ };
+};
+
+export default InformationNode;
diff --git a/src/components/nodes/NodeRouter.tsx b/src/components/nodes/NodeRouter.tsx
new file mode 100644
index 0000000..3cb0b5e
--- /dev/null
+++ b/src/components/nodes/NodeRouter.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import { NodeProps } from 'reaflow';
+import { useRecoilState } from 'recoil';
+import { nodesSelector } from '../../states/nodesState';
+import BaseNodeData from '../../types/BaseNodeData';
+import { findNodeComponentByType } from '../../utils/nodes';
+
+type Props = {
+ nodeProps: NodeProps;
+}
+
+/**
+ * Node router.
+ *
+ * Acts as a router between node layouts, by rendering a different node layout, depending on the node "data.type" property.
+ */
+const NodeRouter: React.FunctionComponent = (props) => {
+ const {
+ nodeProps,
+ } = props;
+ const nodeType = nodeProps?.properties?.data?.type;
+ const [nodes, setNodes] = useRecoilState(nodesSelector);
+ const node: BaseNodeData = nodes.find((node: BaseNodeData) => node.id === nodeProps.id) as BaseNodeData;
+
+ // If the node is not defined then we don't render the node component because it'll crash
+ // XXX It can happen sometimes when removing nodes that are being displayed,
+ // the node itself still exist in the Canvas, but doesn't exist anymore in the "nodesState".
+ if (typeof node === 'undefined') {
+ return null;
+ }
+
+ if (!nodeType) {
+ try {
+ console.error(`Node with nodeType="${nodeType}" couldn't be rendered. Props: ${JSON.stringify(nodeProps, null, 2)}`);
+ } catch (e) {
+ console.error(`Node with nodeType="${nodeType}" couldn't be rendered. Props cannot be stringified.`);
+ }
+
+ return null;
+ }
+
+ // Will render a specialized node (e.g: StartNode, etc.)
+ const NodeComponent = findNodeComponentByType(nodeType);
+
+ return (
+
+ );
+};
+
+export default NodeRouter;
diff --git a/src/components/nodes/QuestionNode.tsx b/src/components/nodes/QuestionNode.tsx
new file mode 100644
index 0000000..638347c
--- /dev/null
+++ b/src/components/nodes/QuestionNode.tsx
@@ -0,0 +1,275 @@
+import { Button } from '@chakra-ui/react';
+import { css } from '@emotion/react';
+import now from 'lodash.now';
+import sortBy from 'lodash.sortby';
+import React, {
+ Fragment,
+ useEffect,
+ useState,
+} from 'react';
+import { DebounceInput } from 'react-debounce-input';
+import ReactSelect from 'react-select';
+import { OptionTypeBase } from 'react-select/src/types';
+import { TextareaHeightChangeMeta } from 'react-textarea-autosize/dist/declarations/src';
+import { v1 as uuid } from 'uuid';
+import settings from '../../settings';
+import BaseNodeComponent from '../../types/BaseNodeComponent';
+import { BaseNodeDefaultProps } from '../../types/BaseNodeDefaultProps';
+import BaseNodeProps from '../../types/BaseNodeProps';
+import { QuestionChoiceType } from '../../types/nodes/QuestionChoiceType';
+import { QuestionChoiceTypeOption } from '../../types/nodes/QuestionChoiceTypeOption';
+import { QuestionChoiceVariable } from '../../types/nodes/QuestionNodeAdditionalData';
+import { QuestionNodeData } from '../../types/nodes/QuestionNodeData';
+import { SpecializedNodeProps } from '../../types/nodes/SpecializedNodeProps';
+import NodeType from '../../types/NodeType';
+import { isYoungerThan } from '../../utils/date';
+import QuestionChoice from '../plugins/QuestionChoice';
+import Textarea from '../plugins/Textarea';
+import VariableNameInput from '../plugins/VariableNameInput';
+import BaseNode from './BaseNode';
+
+type Props = {} & BaseNodeProps;
+
+const nodeType: NodeType = 'question';
+const defaultWidth = 250;
+const defaultHeight = 250;
+
+/**
+ * Question node.
+ *
+ * Used to display a information (as text) and its choices.
+ *
+ * Displays a multi lines text input. (textarea)
+ * Displays a "question type" select input. (React Select)
+ * - If question type is "text", doesn't display anything more.
+ * - If question type is "single-quick-reply", displays a list of manual choices below, with ability to create new entries.
+ * Displays a "question ref" input at the node's bottom.
+ * Has one west port and one east port.
+ * The west port allows unlimited links to other nodes.
+ */
+const QuestionNode: BaseNodeComponent = (props) => {
+ return (
+
+ {
+ (nodeProps: SpecializedNodeProps) => {
+ const {
+ node,
+ lastCreated,
+ patchCurrentNode,
+ } = nodeProps;
+ const [questionTextareaAdditionalHeight, setQuestionTextareaAdditionalHeight] = useState(0);
+ const choiceTypes: QuestionChoiceTypeOption[] = settings.canvas.nodes.questionNode.choiceTypeOptions;
+ const lastCreatedNode = lastCreated?.node;
+ const lastCreatedAt = lastCreated?.at;
+ const displayChoiceInputs = node?.data?.questionChoiceType === 'single-quick-reply';
+ const additionalHeightChoiceInputs = 200;
+
+ // Autofocus works fine when the node is inside the viewport, but when it's created outside it moves the viewport back at the beginning
+ const shouldAutofocus = false && lastCreatedNode?.id === node.id && isYoungerThan(lastCreatedAt, 1000);
+
+ /**
+ * Calculates the node's height dynamically.
+ *
+ * The node's height is dynamic and depends on various parameters (selected option, length of text, etc.).
+ */
+ useEffect(() => {
+ // TODO increase height depending on how many input choice there are (50px/unit)?
+ const newHeight = defaultHeight + questionTextareaAdditionalHeight + (displayChoiceInputs ? additionalHeightChoiceInputs : 0);
+
+ // Only update the height if it's different
+ if (node?.height !== newHeight) {
+ patchCurrentNode({
+ height: newHeight,
+ });
+ }
+ }, [questionTextareaAdditionalHeight, displayChoiceInputs]);
+
+ /**
+ * When textarea input height changes, we need to increase the height of the whole node accordingly.
+ *
+ * @param height
+ * @param meta
+ */
+ const onTextHeightChange = (height: number, meta: TextareaHeightChangeMeta) => {
+ // Only consider additional height, by ignoring the height of the first row
+ const additionalHeight = height - meta.rowHeight;
+
+ if (questionTextareaAdditionalHeight !== additionalHeight) {
+ setQuestionTextareaAdditionalHeight(additionalHeight);
+ }
+ };
+
+ /**
+ * Updates the current node "text" value.
+ *
+ * @param event
+ */
+ const onTextInputValueChange = (event: any) => {
+ const newValue = event.target.value;
+
+ // Updates the value in the Recoil store
+ patchCurrentNode({
+ data: {
+ questionText: newValue,
+ },
+ } as QuestionNodeData);
+ };
+
+ /**
+ * Find the selected option in the question type select.
+ *
+ * Basically translates the QuestionChoiceType kept in the node "data" into one of the available QuestionChoiceType.
+ *
+ * @param selectedQuestionTypeValue
+ */
+ const findSelectedQuestionTypeOption = (selectedQuestionTypeValue: QuestionChoiceType | undefined): OptionTypeBase | undefined => {
+ return choiceTypes.find((choiceType: QuestionChoiceTypeOption) => choiceType?.value === selectedQuestionTypeValue);
+ };
+
+ /**
+ * Updates the current node "questionChoiceType" value.
+ *
+ * @param selectedChoice
+ * @param action
+ */
+ const onSelectedChoiceTypeChange = (selectedChoice: OptionTypeBase, action: { action: string }): void => {
+ const selectedChoiceValue: QuestionChoiceType = selectedChoice?.value;
+
+ // Don't update if the choice is not different
+ if (selectedChoiceValue !== node?.data?.questionChoiceType) {
+ // Updates the value in the Recoil store
+ patchCurrentNode({
+ data: {
+ questionChoiceType: selectedChoiceValue,
+ },
+ } as QuestionNodeData);
+ }
+ };
+
+ return (
+
+
+ Question
+
+
+
+
+
+
+
+ Choice
+
+
+
+
+
+ {
+ displayChoiceInputs && (
+
+
+ {
+ // Sort the questions from the oldest to youngest, avoids "jumps" of position when editing
+ sortBy(node?.data?.questionChoices, ['createdAt'])?.map((questionChoice: QuestionChoiceVariable) => {
+ return (
+
+ );
+ })
+ }
+
+
+
patchCurrentNode({
+ data: {
+ questionChoices: [
+ ...(node?.data?.questionChoices || []),
+ {
+ id: uuid(),
+ createdAt: now(),
+ },
+ ],
+ },
+ } as QuestionNodeData)}
+ >
+ +
+
+
+ )
+ }
+
+ {/* Displays the "Variable name" input at the bottom of the node, in absolute position */}
+
+ node={node}
+ patchCurrentNode={patchCurrentNode}
+ />
+
+
+
+ );
+ }
+ }
+
+ );
+};
+QuestionNode.getDefaultNodeProps = (): BaseNodeDefaultProps => {
+ return {
+ type: nodeType,
+ defaultWidth: defaultWidth,
+ defaultHeight: defaultHeight,
+ // @ts-ignore
+ ports: BaseNode.getDefaultPorts(),
+ };
+};
+
+export default QuestionNode;
diff --git a/src/components/nodes/StartNode.tsx b/src/components/nodes/StartNode.tsx
new file mode 100644
index 0000000..eab75b4
--- /dev/null
+++ b/src/components/nodes/StartNode.tsx
@@ -0,0 +1,85 @@
+import { css } from '@emotion/react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import React, { Fragment } from 'react';
+import settings from '../../settings';
+import BaseNodeComponent from '../../types/BaseNodeComponent';
+import { BaseNodeDefaultProps } from '../../types/BaseNodeDefaultProps';
+import BaseNodeProps from '../../types/BaseNodeProps';
+import BasePortData from '../../types/BasePortData';
+import { SpecializedNodeProps } from '../../types/nodes/SpecializedNodeProps';
+import { StartNodeData } from '../../types/nodes/StartNodeData';
+import NodeType from '../../types/NodeType';
+import { createPort } from '../../utils/ports';
+import BaseNode from './BaseNode';
+
+type Props = {} & BaseNodeProps;
+
+const nodeType: NodeType = 'start';
+const defaultWidth = 100;
+const defaultHeight = 100;
+
+/**
+ * Start node.
+ *
+ * Used to define the workflow's entry point.
+ * Is unique, there can be only one StartNode, and it's required and must always be present.
+ *
+ * Displays a start icon.
+ * Has only one east port.
+ */
+const StartNode: BaseNodeComponent = (props) => {
+ return (
+
+ {
+ (nodeProps: SpecializedNodeProps) => {
+ return (
+
+
+
+
+
+ );
+ }
+ }
+
+ );
+};
+
+StartNode.getDefaultPorts = (): BasePortData[] => {
+ return [
+ createPort({
+ height: settings.canvas.ports.radius,
+ width: settings.canvas.ports.radius,
+ alignment: 'CENTER',
+ side: 'EAST',
+ }),
+ ];
+};
+
+StartNode.getDefaultNodeProps = (): BaseNodeDefaultProps => {
+ return {
+ type: nodeType,
+ defaultWidth: defaultWidth,
+ defaultHeight: defaultHeight,
+ // @ts-ignore
+ ports: StartNode.getDefaultPorts(),
+ };
+};
+
+export default StartNode;
diff --git a/src/components/plugins/QuestionChoice.tsx b/src/components/plugins/QuestionChoice.tsx
new file mode 100644
index 0000000..7aaa6dd
--- /dev/null
+++ b/src/components/plugins/QuestionChoice.tsx
@@ -0,0 +1,158 @@
+import { css } from '@emotion/react';
+import React, { PropsWithChildren } from 'react';
+import { DebounceInput } from 'react-debounce-input';
+import TextareaAutosize from 'react-textarea-autosize';
+import { PatchCurrentNode } from '../../types/BaseNodeProps';
+import { QuestionChoiceVariable } from '../../types/nodes/QuestionNodeAdditionalData';
+import { QuestionNodeData } from '../../types/nodes/QuestionNodeData';
+
+type Props = {
+ node: NodeData;
+ questionChoiceVariable: QuestionChoiceVariable;
+ patchCurrentNode: PatchCurrentNode;
+};
+
+/**
+ * Display a single "question choice".
+ *
+ * Displays within a QuestionNode component.
+ * Displayed only when the QuestionNode's "questionChoiceType" is set to "single-quick-reply".
+ */
+export const QuestionChoice = (props: PropsWithChildren>): React.ReactElement => {
+ const {
+ node,
+ questionChoiceVariable,
+ patchCurrentNode,
+ } = props;
+ const {
+ id,
+ name,
+ value,
+ } = questionChoiceVariable;
+
+ /**
+ * Filter the current question choice to keep only other question choices.
+ */
+ const filterCurrentQuestionChoice = (): QuestionChoiceVariable[] => {
+ return node?.data?.questionChoices?.filter((variable: QuestionChoiceVariable) => variable?.id !== id) || [];
+ };
+
+ /**
+ * Patches the current question choice, leaving other choices untouched.
+ *
+ * Might reorder the array of questionChoices within the node.
+ *
+ * @param patch
+ */
+ const patchCurrentQuestionChoice = (patch: Partial) => {
+ const patchedQuestionChoices: QuestionChoiceVariable[] = [
+ ...filterCurrentQuestionChoice(),
+ {
+ ...questionChoiceVariable,
+ ...patch,
+ },
+ ];
+
+ // @ts-ignore
+ patchCurrentNode({
+ data: {
+ questionChoices: patchedQuestionChoices,
+ },
+ });
+ };
+
+ /**
+ * Update the question choice variable's name.
+ *
+ * @param event
+ */
+ const updateQuestionChoiceVariableName = (event: any) => {
+ const newName = event?.target?.value;
+
+ patchCurrentQuestionChoice({
+ name: newName,
+ });
+ };
+
+ /**
+ * Update the question choice variable's value.
+ *
+ * @param event
+ */
+ const updateQuestionChoiceVariableValue = (event: any) => {
+ const newValue = event?.target?.value;
+
+ patchCurrentQuestionChoice({
+ value: newValue,
+ });
+ };
+
+ return (
+
+ );
+};
+
+export default QuestionChoice;
diff --git a/src/components/plugins/SelectComparisonOperator.tsx b/src/components/plugins/SelectComparisonOperator.tsx
new file mode 100644
index 0000000..411e939
--- /dev/null
+++ b/src/components/plugins/SelectComparisonOperator.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import ReactSelect, { CommonProps } from 'react-select';
+import {
+ ActionMeta,
+ OptionTypeBase,
+} from 'react-select/src/types';
+import { ReactSelectDefaultOption } from '../../types/ReactSelect';
+
+export type OnSelectedVariableChange = (selectedOption: ReactSelectDefaultOption, actionMeta: ActionMeta) => void;
+
+type Props = {
+ selectedComparisonOperatorName: string | undefined;
+ onSelectedComparisonOperatorChange: OnSelectedVariableChange;
+} & Partial> & React.HTMLProps;
+
+type ComparisonOperator = {
+ name: string,
+ label: string;
+}
+/**
+ * Select displaying a list of all comparison operators available.
+ *
+ * Those are hardcoded and cannot be added manually.
+ */
+export const SelectComparisonOperator: React.FunctionComponent = (props) => {
+ const {
+ selectedComparisonOperatorName,
+ onSelectedComparisonOperatorChange,
+ ...rest
+ } = props;
+
+ const comparisonOperators: ComparisonOperator[] = [
+ {
+ name: 'is',
+ label: 'Is',
+ },
+ {
+ name: 'is-not',
+ label: 'Is not',
+ },
+ ];
+
+ // Converts comparison operator shapes into ReactSelectDefaultOption shapes
+ const comparisonOperatorsAsOptions: ReactSelectDefaultOption[] = comparisonOperators?.map((comparisonOperator: ComparisonOperator) => {
+ return {
+ value: comparisonOperator?.name, // The name of the comparison operator becomes the select "value" field
+ label: comparisonOperator?.label,
+ };
+ });
+
+ return (
+ option?.value === selectedComparisonOperatorName)}
+ options={comparisonOperatorsAsOptions}
+ onChange={onSelectedComparisonOperatorChange as any}
+ {...rest}
+ />
+ );
+};
+
+export default SelectComparisonOperator;
diff --git a/src/components/plugins/SelectExpectedValue.tsx b/src/components/plugins/SelectExpectedValue.tsx
new file mode 100644
index 0000000..8507b42
--- /dev/null
+++ b/src/components/plugins/SelectExpectedValue.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import ReactSelect, { CommonProps } from 'react-select';
+import { useRecoilState } from 'recoil';
+import { variablesSelector } from '../../states/variablesState';
+import { QuestionChoiceVariable } from '../../types/nodes/QuestionNodeAdditionalData';
+import { OnSelectedOptionChange } from '../../types/OnSelectedOptionChange';
+import { ReactSelectDefaultOption } from '../../types/ReactSelect';
+import Variable from '../../types/Variable';
+
+type Props = {
+ selectedComparedVariableName: string | undefined;
+ selectedExpectedValue: string | undefined;
+ onSelectedComparisonChange: OnSelectedOptionChange;
+} & Partial> & React.HTMLProps;
+
+/**
+ * Select displaying a list of all possible choices for the currently selected variable ("selectedComparedVariableName").
+ */
+export const SelectExpectedValue: React.FunctionComponent = (props) => {
+ const {
+ selectedComparedVariableName,
+ selectedExpectedValue,
+ onSelectedComparisonChange,
+ ...rest
+ } = props;
+
+ const [variables] = useRecoilState(variablesSelector);
+ const selectedVariable: Variable | undefined = variables?.find((variable: Variable) => variable?.name === selectedComparedVariableName);
+
+ // Converts Variable shapes into ReactSelectDefaultOption shapes
+ const selectedVariableChoicesAsOptions: ReactSelectDefaultOption[] = (selectedVariable?.choices?.map((choice: QuestionChoiceVariable) => {
+ return {
+ value: choice?.name, // The name of the variable becomes the select "value" field
+ label: choice?.value,
+ };
+ }) || []) as ReactSelectDefaultOption[];
+
+ return (
+ option?.value === selectedExpectedValue)}
+ options={selectedVariableChoicesAsOptions}
+ onChange={onSelectedComparisonChange as any}
+ {...rest}
+ />
+ );
+};
+
+export default SelectExpectedValue;
diff --git a/src/components/plugins/SelectVariable.tsx b/src/components/plugins/SelectVariable.tsx
new file mode 100644
index 0000000..33f94d7
--- /dev/null
+++ b/src/components/plugins/SelectVariable.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import ReactSelect, { CommonProps } from 'react-select';
+import { useRecoilState } from 'recoil';
+import { variablesSelector } from '../../states/variablesState';
+import { OnSelectedOptionChange } from '../../types/OnSelectedOptionChange';
+import { ReactSelectDefaultOption } from '../../types/ReactSelect';
+import Variable from '../../types/Variable';
+
+type Props = {
+ selectedVariableName: string | undefined;
+ onSelectedVariableChange: OnSelectedOptionChange;
+} & Partial> & React.HTMLProps;
+
+/**
+ * Select displaying a list of all variables currently used.
+ *
+ * The list of variables is automatically resolved by lookup on all existing nodes.
+ * This is done automatically by the "variablesSelector".
+ */
+export const SelectVariable: React.FunctionComponent = (props) => {
+ const {
+ selectedVariableName,
+ onSelectedVariableChange,
+ ...rest
+ } = props;
+
+ const [variables] = useRecoilState(variablesSelector);
+
+ // Converts Variable shapes into ReactSelectDefaultOption shapes
+ const variablesAsOptions: ReactSelectDefaultOption[] = variables?.map((variable: Variable) => {
+ return {
+ value: variable?.name, // The name of the variable becomes the select "value" field
+ label: variable?.label,
+ };
+ });
+
+ return (
+ option?.value === selectedVariableName)}
+ options={variablesAsOptions}
+ onChange={onSelectedVariableChange as any}
+ {...rest}
+ />
+ );
+};
+
+export default SelectVariable;
diff --git a/src/components/plugins/Textarea.tsx b/src/components/plugins/Textarea.tsx
new file mode 100644
index 0000000..bcd5816
--- /dev/null
+++ b/src/components/plugins/Textarea.tsx
@@ -0,0 +1,33 @@
+import { css } from '@emotion/react';
+import React from 'react';
+import TextareaAutosize, { TextareaAutosizeProps } from 'react-textarea-autosize';
+
+type Props = {} & TextareaAutosizeProps;
+
+/**
+ * Textarea with autoresize and sane default props.
+ *
+ * Wrapper around the TextareaAutosize component.
+ */
+export const Textarea: React.FunctionComponent = (props) => {
+ const {
+ minRows = 1,
+ maxRows = 5,
+ ...rest
+ } = props;
+
+ return (
+
+ );
+};
+
+export default Textarea;
diff --git a/src/components/plugins/VariableNameInput.tsx b/src/components/plugins/VariableNameInput.tsx
new file mode 100644
index 0000000..7026296
--- /dev/null
+++ b/src/components/plugins/VariableNameInput.tsx
@@ -0,0 +1,94 @@
+import { css } from '@emotion/react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import React, {
+ PropsWithChildren,
+ useState,
+} from 'react';
+import { PatchCurrentNode } from '../../types/BaseNodeProps';
+import NodeDataWithVariableName from '../../types/NodeDataWithVariableName';
+
+type Props = {
+ node: NodeData;
+ patchCurrentNode: PatchCurrentNode;
+};
+
+/**
+ * Input storing the name of the node's "variable name".
+ *
+ * Some nodes ask for a "variable name" input (text, choice, etc.).
+ * The variable name is used in the component, to be displayed as a list of variables.
+ *
+ * Displays at the bottom of the node, in absolute position.
+ * Takes full width of the node component it is contained within.
+ */
+export const VariableNameInput = (props: PropsWithChildren>): React.ReactElement => {
+ const {
+ node,
+ patchCurrentNode,
+ } = props;
+ const {
+ width = 200,
+
+ } = node;
+ const [variableName, setVariableName] = useState(node?.data?.variableName);
+
+ const onChange = (event: any) => {
+ setVariableName(event?.target?.value);
+ };
+
+ const onSubmit = () => {
+ patchCurrentNode({
+ data: {
+ variableName: variableName,
+ },
+ } as NodeData);
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default VariableNameInput;
diff --git a/src/components/ports/BasePort.tsx b/src/components/ports/BasePort.tsx
new file mode 100644
index 0000000..48b6695
--- /dev/null
+++ b/src/components/ports/BasePort.tsx
@@ -0,0 +1,303 @@
+import classnames from 'classnames';
+import cloneDeep from 'lodash.clonedeep';
+import now from 'lodash.now';
+import React from 'react';
+import {
+ Port,
+ PortSide,
+} from 'reaflow';
+import {
+ DragEvent,
+ Position,
+} from 'reaflow/dist/utils/useNodeDrag';
+import {
+ SetterOrUpdater,
+ useRecoilState,
+ useSetRecoilState,
+} from 'recoil';
+import settings from '../../settings';
+import { blockPickerMenuSelector } from '../../states/blockPickerMenuState';
+import { canvasDatasetSelector } from '../../states/canvasDatasetSelector';
+import { draggedEdgeFromPortState } from '../../states/draggedEdgeFromPortState';
+import { edgesSelector } from '../../states/edgesState';
+import { lastCreatedState } from '../../states/lastCreatedState';
+import { selectedEdgesSelector } from '../../states/selectedEdgesState';
+import { selectedNodesSelector } from '../../states/selectedNodesState';
+import BaseEdgeData from '../../types/BaseEdgeData';
+import BaseNodeData from '../../types/BaseNodeData';
+import BasePortData from '../../types/BasePortData';
+import BasePortProps from '../../types/BasePortProps';
+import BlockPickerMenu, { OnBlockClick } from '../../types/BlockPickerMenu';
+import { CanvasDataset } from '../../types/CanvasDataset';
+import { LastCreated } from '../../types/LastCreated';
+import NodeType from '../../types/NodeType';
+import { translateXYToCanvasPosition } from '../../utils/canvas';
+import { createEdge } from '../../utils/edges';
+import {
+ addNodeAndEdgeThroughPorts,
+ createNodeFromDefaultProps,
+ getDefaultNodePropsWithFallback,
+} from '../../utils/nodes';
+import {
+ canConnectToDestinationPort,
+ getDefaultToPort,
+} from '../../utils/ports';
+
+type Props = {
+ fromNodeId: string;
+} & BasePortProps;
+
+/**
+ * Base port component.
+ *
+ * This component contains shared business logic common to all ports.
+ * It renders a Reaflow component.
+ *
+ * The Port is rendered as SVG HTML element wrapper, which contains the HTML element that displays the port itself.
+ *
+ * @see https://reaflow.dev/?path=/story/demos-ports
+ */
+const BasePort: React.FunctionComponent = (props) => {
+ const {
+ id,
+ properties,
+ fromNodeId,
+ onDragStart: onDragStartInternal,
+ onDragEnd: onDragEndInternal,
+ } = props;
+
+ const [blockPickerMenu, setBlockPickerMenu] = useRecoilState(blockPickerMenuSelector);
+ const [canvasDataset, setCanvasDataset] = useRecoilState(canvasDatasetSelector);
+ const [edges, setEdges] = useRecoilState(edgesSelector);
+ const { nodes } = canvasDataset;
+ const [draggedEdgeFromPort, setDraggedEdgeFromPort] = useRecoilState(draggedEdgeFromPortState);
+ const setLastCreatedNode: SetterOrUpdater = useSetRecoilState(lastCreatedState);
+ const node: BaseNodeData = nodes.find((node: BaseNodeData) => node.id === fromNodeId) as BaseNodeData;
+ const port: BasePortData = node?.ports?.find((port: BasePortData) => port.id === id) as BasePortData;
+ const { displayedFrom, isDisplayed } = blockPickerMenu;
+ const [selectedNodes, setSelectedNodes] = useRecoilState(selectedNodesSelector);
+ const [selectedEdges, setSelectedEdges] = useRecoilState(selectedEdgesSelector);
+
+ // Highlight the current port if there is an edge being dragged from another port and if it can connect to the current port
+ const isHighlighted = canConnectToDestinationPort(edges, draggedEdgeFromPort?.fromNode, draggedEdgeFromPort?.fromPort, node, port);
+
+ const style = {
+ fill: 'white',
+ stroke: 'white',
+ };
+
+ /**
+ * Creates a node based on the block that's been clicked within the BlockPickerMenu.
+ *
+ * Automatically creates the edge between the source node and the newly created node, and connects them through their ports.
+ *
+ * @param nodeType
+ * @param blockPickerMenu
+ */
+ const onBlockClick: OnBlockClick = (nodeType: NodeType, blockPickerMenu: BlockPickerMenu | undefined) => {
+ console.log('onBlockClick (from port)', nodeType, draggedEdgeFromPort, blockPickerMenu);
+ const newNode = createNodeFromDefaultProps(getDefaultNodePropsWithFallback(nodeType));
+ let newDataset: CanvasDataset;
+ let createNodeOnSide: PortSide | undefined;
+
+ if (typeof draggedEdgeFromPort === 'undefined') {
+ console.log(`typeof draggedEdgeFromPort === 'undefined'`);
+ // It was a click on a port which opened the BlockPickerMenu, not a drag from a port
+ if (blockPickerMenu?.fromPort?.side) {
+ createNodeOnSide = blockPickerMenu?.fromPort?.side;
+ }
+
+ } else {
+ console.log(`typeof draggedEdgeFromPort !== 'undefined'`);
+ // It was a drag from a port which opened the BlockPickerMenu, not a click on a port
+ if (draggedEdgeFromPort?.fromPort?.side) {
+ createNodeOnSide = draggedEdgeFromPort?.fromPort?.side;
+ }
+ }
+
+ console.log('createNodeOnSide', createNodeOnSide);
+ if (createNodeOnSide === 'EAST') {
+ // The drag started from an EAST port, so we must add the new node on the right of existing node
+ // The from port is either the port where the node was dragged from, or the port that was clicked on
+ const fromPort: BasePortData = (draggedEdgeFromPort?.fromPort || blockPickerMenu?.fromPort) as BasePortData;
+
+ newDataset = addNodeAndEdgeThroughPorts(cloneDeep(nodes), cloneDeep(edges), newNode, node, newNode, fromPort);
+ } else {
+ // The drag started from a WEST port, so we must add the new node on the left of the existing node
+ const fromPort: BasePortData = newNode?.ports?.find((port: BasePortData) => port?.side === 'EAST') as BasePortData;
+ const toPort: BasePortData = draggedEdgeFromPort?.fromPort as BasePortData;
+
+ newDataset = addNodeAndEdgeThroughPorts(cloneDeep(nodes), cloneDeep(edges), newNode, newNode, node, fromPort, toPort);
+ }
+ console.log('addNodeAndEdge fromNode', newNode, 'toNode', node, 'dataset', newDataset);
+ console.log('newDataset', newDataset);
+
+ setCanvasDataset(newDataset);
+ setLastCreatedNode({ node: newNode, at: now() });
+ setSelectedNodes([newNode?.id]);
+ setSelectedEdges([]);
+ };
+
+ /**
+ * Invoked when clicking on a port.
+ *
+ * Displays the BlockPickerMenu, which can then be used to select which Block to add to the canvas.
+ * If the BlockPickerMenu was already displayed by clicking on the same port, then hides it instead.
+ *
+ * @param event
+ * @param port
+ */
+ const onPortClick = (event: React.MouseEvent, port: BasePortData) => {
+ const [x, y] = translateXYToCanvasPosition(event?.clientX, event.clientY, { top: 60, left: 15 });
+
+ console.log('onPortClick', port);
+ setBlockPickerMenu({
+ displayedFrom: `port-${port.id}`,
+ isDisplayed: displayedFrom === `port-${port.id}` ? !isDisplayed : true,
+ onBlockClick,
+ // Depending on the position of the canvas, you might need to deduce from x/y some delta
+ left: x,
+ top: y - settings.layout.nav.height,
+ eventTarget: event.target,
+ fromPort: port,
+ });
+ };
+
+ /**
+ * Invoked when a port is has started being dragged.
+ *
+ * Stores the dragged edge in the shared state, to allow other components to know which edge is being dragged.
+ *
+ * @param event
+ * @param fromPosition
+ * @param fromPort
+ * @param extra
+ */
+ const onPortDragStart = (event: DragEvent, fromPosition: Position, fromPort: BasePortData, extra: any) => {
+ console.log('onDragStart port: ', node, event, fromPosition, fromPort, extra);
+
+ if (typeof onDragStartInternal === 'function') {
+ // Runs internal onDragStart (built-in from Reaflow) which does stuff I'm not aware about
+ onDragStartInternal(event, fromPosition, fromPort, extra);
+ }
+
+ setDraggedEdgeFromPort({
+ fromNode: node,
+ fromPort: fromPort,
+ fromPosition: fromPosition,
+ });
+ };
+
+ /**
+ * Invoked when a port is has been dropped.
+ *
+ * Displays the BlockPickerMenu at the drop location, which can then be used to select which Block to add to the canvas.
+ * Also runs the Reaflow own "onDragEndInternal", which is necessary to avoid leaving ghost edges connected to nothing.
+ *
+ * @param dragEvent
+ * @param initial
+ * @param fromPort
+ * @param extra
+ */
+ const onPortDragEnd = (dragEvent: DragEvent, initial: Position, fromPort: BasePortData, extra: any) => {
+ console.log('onDragEnd port: ', node, dragEvent, dragEvent.event, initial, fromPort, extra);
+ const { xy, distance, event } = dragEvent;
+ const target = event?.target as Element;
+
+ // Look up in the DOM to find the closest element that contains the node's id
+ const foreignObject: Element | null | undefined = target?.closest?.('g')?.previousElementSibling;
+ const foreignObjectId: string | undefined = foreignObject?.id;
+ const toNodeId: string | undefined = foreignObjectId?.replace('node-foreignObject-', '');
+ console.log('closest foreignObject:', foreignObject, 'toNodeId:', toNodeId);
+
+ if (toNodeId) {
+ // The edge has been dropped into a port
+ console.log('found closest node id', toNodeId);
+ const {
+ fromNode,
+ } = draggedEdgeFromPort || {};
+ const toNode: BaseNodeData | undefined = nodes.find((node: BaseEdgeData) => node.id === toNodeId);
+ const toPort: BasePortData = getDefaultToPort(toNode) as BasePortData;
+
+ if (canConnectToDestinationPort(edges, fromNode, fromPort, toNode, toPort)) {
+ const newEdge: BaseEdgeData = createEdge(fromNode, toNode, fromPort, toPort);
+
+ console.log('Linking existing nodes through new edge', newEdge);
+ setEdges([
+ ...edges,
+ newEdge,
+ ]);
+ } else {
+ console.error(`You cannot connect the link to that port.`);
+ alert(`You cannot connect the link to that port.`);
+ }
+
+ // Hides the block picker menu (it might have been opened before)
+ setBlockPickerMenu({
+ isDisplayed: false,
+ });
+ } else {
+ // The edge hasn't been dropped into a port (canvas, etc.) - Doesn't matter, we display the block picker menu
+ // Converts the x/y position to a Canvas position and apply some margin for the BlockPickerMenu to display on the right bottom of the cursor
+ const [x, y] = translateXYToCanvasPosition(...xy, { top: 60, left: 10 });
+
+ // Opens the block picker menu below the clicked element
+ setBlockPickerMenu({
+ displayedFrom: `port-${fromPort.id}`,
+ isDisplayed: true, // Toggle on click XXX change later, should toggle but not easy to test when toggle is on
+ onBlockClick,
+ // Depending on the position of the canvas, you might need to deduce from x/y some delta
+ left: x,
+ top: y - settings.layout.nav.height,
+ eventTarget: target,
+ });
+ }
+
+ if (typeof onDragEndInternal === 'function') {
+ // Runs internal onDragEnd (built-in from Reaflow) which removes the edge if it doesn't connect to anything
+ onDragEndInternal(dragEvent, initial, fromPort, extra);
+ }
+
+ // Reset the edge being dragged
+ setDraggedEdgeFromPort(undefined);
+ };
+
+ /**
+ * Invoked when the mouse is enters a port (hover starts).
+ *
+ * @param event
+ * @param port
+ */
+ const onPortEnter = (event: React.MouseEvent, port: BasePortData) => {
+ // console.log('onPortEnter', event.target)
+ };
+
+ /**
+ * Invoked when the mouse is leaves a port (hover stops).
+ *
+ * @param event
+ * @param port
+ */
+ const onPortLeave = (event: React.MouseEvent, port: BasePortData) => {
+ // console.log('onPortLeave', event.target)
+ };
+
+ return (
+
+ );
+};
+
+export default BasePort;
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
new file mode 100644
index 0000000..49ad0a0
--- /dev/null
+++ b/src/pages/_app.tsx
@@ -0,0 +1,47 @@
+import { ChakraProvider } from '@chakra-ui/react';
+import 'animate.css/animate.min.css'; // Loads animate.css CSS file. See https://github.com/daneden/animate.css
+import {
+ NextComponentType,
+ NextPageContext,
+} from 'next';
+import { Router } from 'next/router';
+import React from 'react';
+import { RecoilRoot } from 'recoil';
+import { RecoilDevtools } from '../components/RecoilDevtools';
+import { RecoilExternalStatePortal } from '../components/RecoilExternalStatePortal';
+import '../utils/fontAwesome';
+
+type Props = {
+ Component: NextComponentType; // Page component, not provided if pageProps.statusCode is 3xx or 4xx
+ err?: Error; // Only defined if there was an error
+ pageProps: any; // Props forwarded to the Page component
+ router?: Router; // Next.js router state
+};
+
+/**
+ * This file is the entry point for all pages, it initialize all pages.
+ *
+ * It can be executed server side or browser side.
+ *
+ * @see https://nextjs.org/docs/advanced-features/custom-app Custom _app
+ * @see https://nextjs.org/docs/basic-features/typescript#custom-app TypeScript for _app
+ */
+const App: React.FunctionComponent = (props): JSX.Element => {
+ const { Component, pageProps } = props;
+
+ return (
+
+
+ {/* Dev tools for Recoil */}
+
+
+ {/* Utility component allowing to use the Recoil state outside of a React component */}
+
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
new file mode 100644
index 0000000..76f2f5d
--- /dev/null
+++ b/src/pages/index.tsx
@@ -0,0 +1,57 @@
+import { isBrowser } from '@unly/utils';
+import DisplayOnBrowserMount from '../components/DisplayOnBrowserMount';
+import EditorContainer from '../components/editor/EditorContainer';
+import Layout from '../components/Layout';
+import { CanvasDataset } from '../types/CanvasDataset';
+import { getCanvasDatasetFromLS } from '../utils/persistCanvasDataset';
+
+export type Props = {
+ canvasDataset: CanvasDataset | null;
+}
+
+/**
+ * You can use your custom business logic here to fetch the canvasDataset from your data storage.
+ * We simplified this demo by storing the canvasDataset in the browser LocalStorage instead.
+ *
+ * @see https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
+ */
+export const getStaticProps = (): { props: Props } => {
+ return {
+ props: {
+ canvasDataset: null,
+ },
+ };
+};
+
+/**
+ * Index/home page.
+ *
+ * A simple page that does nothing more than displaying a layout and the Reaflow canvas (EditorContainer),
+ * after it has initialized the global "initialCanvasDataset" browser variable, which is used by the nodesSelector and edgesSelector Recoil state managers.
+ */
+const IndexPage = (props: any) => {
+ const { canvasDataset: canvasDatasetFromServer } = props; // We don't use canvasDatasetFromServer in this demo, but localstorage instead
+
+ /**
+ * Gets the canvas dataset stored in the browser localstorage and makes it available in the global "window" object.
+ * The window.initialCanvasDataset will be used by the nodes/edges atom during their initialisation.
+ *
+ * XXX Doing it this way (instead of using a setState) ensures the Canvas is initially loaded with the proper dataset.
+ * And it won't have multiple re-renders due to mutating state, which in turn avoids lagginess during init.
+ * Also, it's a viable approach whether using the data from browser localstorage, or a real DB.
+ */
+ if (isBrowser()) {
+ window.initialCanvasDataset = getCanvasDatasetFromLS();
+ }
+
+ return (
+
+ {/* Only renders the EditorContainer on the browser because it's not server-side compatible */}
+
+
+
+
+ );
+};
+
+export default IndexPage;
diff --git a/src/settings.ts b/src/settings.ts
new file mode 100644
index 0000000..21204c4
--- /dev/null
+++ b/src/settings.ts
@@ -0,0 +1,162 @@
+import { CanvasDirection } from 'reaflow';
+import { QuestionChoiceTypeOption } from './types/nodes/QuestionChoiceTypeOption';
+
+/**
+ * @readonly
+ */
+export const settings: Settings = {
+ layout: {
+ nav: {
+ height: 50,
+ },
+ footer: {
+ height: 50,
+ },
+ },
+ blocksContainer: {
+ width: '150px',
+ },
+ canvas: {
+ direction: 'RIGHT',
+ maxWidth: 10000, // 10k should handle about 50 horizontal nodes
+ maxHeight: 2000,
+ nodes: {
+ defaultDebounceFor: 250,
+ selected: {
+ borderColor: 'blue',
+ },
+ questionNode: {
+ choiceTypeOptions: [
+ {
+ value: `text`,
+ label: `Text`,
+ },
+ {
+ value: `single-quick-reply`,
+ label: `Single quick reply`,
+ },
+ ],
+ },
+ },
+ edges: {
+ strokeColor: '#b1b1b7',
+ selected: {
+ strokeColor: 'blue',
+ },
+ },
+ ports: {
+ radius: 15,
+ },
+ },
+};
+
+// Make sure nothing can mutate the settings, as it shouldn't happen.
+Object.freeze(settings);
+
+/**
+ * Global settings for the app.
+ *
+ * Could also be stored as a React Context, but hardcoded settings are kinda similar although more flexible and can be used outside of React components.
+ */
+export type Settings = {
+ /**
+ * Common layout shared by all Next.js pages.
+ */
+ layout: LayoutSettings;
+
+ /**
+ * Left container of blocks.
+ */
+ blocksContainer: BlocksContainerSettings;
+
+ /**
+ * SVG Canvas where nodes and edges are drawn.
+ */
+ canvas: CanvasSettings;
+}
+
+export type LayoutSettings = {
+ nav: {
+ height: number;
+ },
+ footer: {
+ height: number;
+ }
+}
+
+export type BlocksContainerSettings = {
+ width: string;
+}
+
+export type CanvasSettings = {
+ /**
+ * Direction used by the canvas, used by ELKjs.
+ */
+ direction: CanvasDirection;
+
+ /**
+ * Maximal width of the canvas.
+ *
+ * XXX Awaiting https://github.com/reaviz/reaflow/discussions/51 for auto-grow
+ */
+ maxWidth: number;
+
+ /**
+ * Maximal height of the canvas.
+ *
+ * XXX Awaiting https://github.com/reaviz/reaflow/discussions/51 for auto-grow
+ */
+ maxHeight: number;
+
+ /**
+ * Configuration relative to the nodes.
+ */
+ nodes: {
+ /**
+ * Default debounce to use when performing heavy-computing operations.
+ *
+ * Used for input "onChange" events, etc.
+ */
+ defaultDebounceFor: number;
+
+ selected: {
+ /**
+ * Border color when the node is selected.
+ */
+ borderColor: string;
+ }
+
+ /**
+ * Configuration of the "Question" node.
+ */
+ questionNode: {
+ choiceTypeOptions: QuestionChoiceTypeOption[];
+ };
+ },
+
+ /**
+ * Configuration relative to the edges.
+ */
+ edges: {
+ /**
+ * Color of the edge. (stroke, because it's an SVG element)
+ */
+ strokeColor: string;
+
+ selected: {
+ /**
+ * Color when the edge is selected.
+ */
+ strokeColor: string;
+ }
+ }
+
+ /**
+ * Edge's ports.
+ */
+ ports: {
+ radius: number;
+ }
+}
+
+export default settings;
diff --git a/src/states/blockPickerMenuState.ts b/src/states/blockPickerMenuState.ts
new file mode 100644
index 0000000..e6c0e6b
--- /dev/null
+++ b/src/states/blockPickerMenuState.ts
@@ -0,0 +1,55 @@
+import isEqual from 'lodash.isequal';
+import merge from 'lodash.merge';
+import now from 'lodash.now';
+import {
+ atom,
+ selector,
+} from 'recoil';
+import BlockPickerMenu from '../types/BlockPickerMenu';
+
+/**
+ * Used to know the state of the block picker menu, whether it's displayed, where, etc..
+ */
+export const blockPickerMenuState = atom({
+ key: 'blockPickerMenuState',
+ default: {
+ isDisplayed: false,
+ },
+});
+
+/**
+ * Custom selector for the atom.
+ */
+export const blockPickerMenuSelector = selector({
+ key: 'blockPickerMenuSelector',
+ get: ({ get }): BlockPickerMenu => {
+ return get(blockPickerMenuState);
+ },
+
+ /**
+ * Patches the new value with the existing value to keep the state of own properties until they're specifically overridden.
+ *
+ * XXX This way, when only setting "isDisplayed", it'll keep the other properties intact (e.g: fromPort)
+ * which will allow us to know from which port it was initially opened even if it has been closed since.
+ *
+ * Avoids needless updates.
+ *
+ * @param set
+ * @param get
+ * @param reset
+ * @param patch
+ */
+ set: ({ set, get, reset }, patch): void => {
+ const currentValue: BlockPickerMenu = get(blockPickerMenuState);
+ const newValue: Partial = {};
+ merge(newValue, currentValue, patch);
+
+ if (!isEqual(currentValue, newValue)) {
+ newValue.at = now(); // Automatically update the timestamp of creation
+ console.log('blockPickerMenuSelector set', newValue, 'patch:', patch);
+ set(blockPickerMenuState, newValue as BlockPickerMenu);
+ } else {
+ console.log('blockPickerMenuSelector identical to current value (not set)', newValue);
+ }
+ },
+});
diff --git a/src/states/canvasDatasetSelector.ts b/src/states/canvasDatasetSelector.ts
new file mode 100644
index 0000000..3cf2d9d
--- /dev/null
+++ b/src/states/canvasDatasetSelector.ts
@@ -0,0 +1,61 @@
+import {
+ DefaultValue,
+ selector,
+} from 'recoil';
+import { CanvasDataset } from '../types/CanvasDataset';
+import { hasDuplicatedObjects } from '../utils/array';
+import { edgesSelector } from './edgesState';
+import { nodesSelector } from './nodesState';
+
+/**
+ * Custom selector for the atom.
+ *
+ * Applies custom business logic and sanity check when manipulating the atom.
+ */
+export const canvasDatasetSelector = selector({
+ key: 'canvasDatasetSelector',
+
+ /**
+ * Uses the nodes and edges selectors (and not their atoms!) to benefit from their "get" behavior.
+ * (which handles the default value)
+ *
+ * @param get
+ */
+ get: ({ get }): CanvasDataset => {
+ return {
+ nodes: get(nodesSelector),
+ edges: get(edgesSelector),
+ };
+ },
+
+ /**
+ * Ensures we don't update neither the nodes nor the edges if there are any duplicates in any of both arrays.
+ *
+ * Helps keeping the dataset consistent (integrity), by avoiding updating only the nodes or only the edges if sanity checks don't pass.
+ *
+ * XXX It is better to use this selector when updating both edges and nodes at once, to ensure dataset integrity.
+ * (compared to edgesSelector and nodesSelector which would update each of them on their own,
+ * and would break dataset integrity if either of them fail to pass the sanity checks)
+ *
+ * @param set
+ * @param get
+ * @param reset
+ * @param newValue
+ */
+ set: ({ set, get, reset }, newValue: DefaultValue | CanvasDataset): void => {
+ const nodes = (newValue as CanvasDataset)?.nodes || [];
+ const edges = (newValue as CanvasDataset)?.edges || [];
+ const hasDuplicateNodes = hasDuplicatedObjects(nodes, 'id');
+ const hasDuplicateEdges = hasDuplicatedObjects(edges, 'id');
+
+ if (!hasDuplicateNodes && !hasDuplicateEdges) {
+ console.log('canvasDatasetSelector set', newValue);
+ set(nodesSelector, nodes);
+ set(edgesSelector, edges);
+ } else {
+ const message = `Duplicate ids found in "${hasDuplicateNodes ? 'nodes' : ''}" "${hasDuplicateEdges ? 'edges' : ''}", the canvas dataset wasn't updated to avoid to corrupt the dataset.`;
+ console.error(message, newValue);
+ throw new Error(message);
+ }
+ },
+});
diff --git a/src/states/draggedEdgeFromPortState.ts b/src/states/draggedEdgeFromPortState.ts
new file mode 100644
index 0000000..344a3d8
--- /dev/null
+++ b/src/states/draggedEdgeFromPortState.ts
@@ -0,0 +1,18 @@
+import { Position } from 'reaflow/dist/utils/useNodeDrag';
+import { atom } from 'recoil';
+import BaseNodeData from '../types/BaseNodeData';
+import BasePortData from '../types/BasePortData';
+
+export type DraggedEdgeFromPort = {
+ fromNode: BaseNodeData;
+ fromPort: BasePortData;
+ fromPosition: Position;
+}
+
+/**
+ * Used to know from which node/port the current edge is being dragged from.
+ */
+export const draggedEdgeFromPortState = atom({
+ key: 'draggedEdgeFromPortState',
+ default: undefined,
+});
diff --git a/src/states/edgesState.ts b/src/states/edgesState.ts
new file mode 100644
index 0000000..16866b9
--- /dev/null
+++ b/src/states/edgesState.ts
@@ -0,0 +1,53 @@
+import {
+ atom,
+ selector,
+} from 'recoil';
+import BaseEdgeData from '../types/BaseEdgeData';
+import { hasDuplicatedObjects } from '../utils/array';
+
+/**
+ * Used to know what are the edges currently displayed within the Canvas component.
+ */
+export const edgesState = atom({
+ key: 'edgesState',
+ default: undefined,
+});
+
+/**
+ * Custom selector for the atom.
+ *
+ * Applies custom business logic and sanity check when manipulating the atom.
+ */
+export const edgesSelector = selector({
+ key: 'edgesSelector',
+ get: ({ get }): BaseEdgeData[] => {
+ const currentEdges: BaseEdgeData[] | undefined = get(edgesState);
+
+ if (typeof currentEdges === 'undefined') {
+ return window.initialCanvasDataset?.edges || [];
+ } else {
+ return currentEdges;
+ }
+ },
+
+ /**
+ * Ensures we don't update the edges if there are duplicates.
+ *
+ * @param set
+ * @param get
+ * @param reset
+ * @param newValue
+ */
+ set: ({ set, get, reset }, newValue): void => {
+ const hasDuplicateEdges = hasDuplicatedObjects(newValue as BaseEdgeData[], 'id');
+
+ if (!hasDuplicateEdges) {
+ console.log('edgesSelector set', newValue);
+ set(edgesState, newValue);
+ } else {
+ const message = `Duplicate edge ids found, the edges weren't updated to avoid to corrupt the dataset.`;
+ console.error(message, newValue);
+ throw new Error(message);
+ }
+ },
+});
diff --git a/src/states/lastCreatedState.ts b/src/states/lastCreatedState.ts
new file mode 100644
index 0000000..9a45727
--- /dev/null
+++ b/src/states/lastCreatedState.ts
@@ -0,0 +1,10 @@
+import { atom } from 'recoil';
+import { LastCreated } from '../types/LastCreated';
+
+/**
+ * Used to know which node was created last and at what time.
+ */
+export const lastCreatedState = atom({
+ key: 'lastCreatedState',
+ default: undefined,
+});
diff --git a/src/states/mouseEnteredState.ts b/src/states/mouseEnteredState.ts
new file mode 100644
index 0000000..e0ae184
--- /dev/null
+++ b/src/states/mouseEnteredState.ts
@@ -0,0 +1,43 @@
+import {
+ atom,
+ selector,
+} from 'recoil';
+import { MouseEntered } from '../types/MouseEntered';
+
+/**
+ * Used to know where the mouse/cursor is currently entered. (in which node/port)
+ *
+ * Might detects the node/port automatically based on the entered port/node counterpart.
+ *
+ * XXX Behaved erratically (because of complex structure), isn't reliable
+ */
+export const mouseEnteredState = atom({
+ key: 'mouseEnteredState',
+ default: {},
+});
+
+/**
+ * XXX It was an experiment which didn't work out due to other concerns.
+ * Unused for now, might be useful later on.
+ *
+ * @deprecated
+ */
+export const mouseEnteredSelector = selector({
+ key: 'selectedEdgesSelector',
+ get: ({ get }): MouseEntered => {
+ return get(mouseEnteredState);
+ },
+
+ /**
+ *
+ *
+ * @param set
+ * @param get
+ * @param reset
+ * @param newValue
+ */
+ set: ({ set, get, reset }, newValue): void => {
+ console.log('mouseEnteredSelector set', newValue);
+ set(mouseEnteredState, newValue);
+ },
+});
diff --git a/src/states/nodesState.ts b/src/states/nodesState.ts
new file mode 100644
index 0000000..5861de7
--- /dev/null
+++ b/src/states/nodesState.ts
@@ -0,0 +1,53 @@
+import {
+ atom,
+ selector,
+} from 'recoil';
+import BaseNodeData from '../types/BaseNodeData';
+import { hasDuplicatedObjects } from '../utils/array';
+
+/**
+ * Used to know what are the nodes currently displayed within the Canvas component.
+ */
+export const nodesState = atom({
+ key: 'nodesState',
+ default: undefined,
+});
+
+/**
+ * Custom selector for the atom.
+ *
+ * Applies custom business logic and sanity check when manipulating the atom.
+ */
+export const nodesSelector = selector({
+ key: 'nodesSelector',
+ get: ({ get }): BaseNodeData[] => {
+ const currentNodes: BaseNodeData[] | undefined = get(nodesState);
+
+ if (typeof currentNodes === 'undefined') {
+ return window.initialCanvasDataset?.nodes || [];
+ } else {
+ return currentNodes;
+ }
+ },
+
+ /**
+ * Ensures we don't update the nodes if there are duplicates.
+ *
+ * @param set
+ * @param get
+ * @param reset
+ * @param newValue
+ */
+ set: ({ set, get, reset }, newValue): void => {
+ const hasDuplicateNodes = hasDuplicatedObjects(newValue as BaseNodeData[], 'id');
+
+ if (!hasDuplicateNodes) {
+ console.log('nodesSelector set', newValue);
+ set(nodesState, newValue);
+ } else {
+ const message = `Duplicate node ids found, the nodes weren't updated to avoid to corrupt the dataset.`;
+ console.error(message, newValue);
+ throw new Error(message);
+ }
+ },
+});
diff --git a/src/states/selectedEdgesState.ts b/src/states/selectedEdgesState.ts
new file mode 100644
index 0000000..c81c517
--- /dev/null
+++ b/src/states/selectedEdgesState.ts
@@ -0,0 +1,34 @@
+import {
+ atom,
+ selector,
+} from 'recoil';
+
+/**
+ * Used to know which edges (ids) are being selected by the user.
+ *
+ * Can be a single edge, several edges, or none of them.
+ */
+export const selectedEdgesState = atom({
+ key: 'selectedEdgesState',
+ default: [],
+});
+
+export const selectedEdgesSelector = selector({
+ key: 'selectedEdgesSelector',
+ get: ({ get }): string[] => {
+ return get(selectedEdgesState);
+ },
+
+ /**
+ * Ensures we don't update the edges if there are duplicates.
+ *
+ * @param set
+ * @param get
+ * @param reset
+ * @param newValue
+ */
+ set: ({ set, get, reset }, newValue): void => {
+ console.log('selectedEdgesSelector set', newValue);
+ set(selectedEdgesState, newValue);
+ },
+});
diff --git a/src/states/selectedNodesState.ts b/src/states/selectedNodesState.ts
new file mode 100644
index 0000000..b9894d2
--- /dev/null
+++ b/src/states/selectedNodesState.ts
@@ -0,0 +1,34 @@
+import {
+ atom,
+ selector,
+} from 'recoil';
+
+/**
+ * Used to know which nodes (ids) are being selected by the user.
+ *
+ * Can be a single node, several nodes, or none of them.
+ */
+export const selectedNodesState = atom({
+ key: 'selectedNodesState',
+ default: [],
+});
+
+export const selectedNodesSelector = selector({
+ key: 'selectedNodesSelector',
+ get: ({ get }): string[] => {
+ return get(selectedNodesState);
+ },
+
+ /**
+ * Ensures we don't update the nodes if there are duplicates.
+ *
+ * @param set
+ * @param get
+ * @param reset
+ * @param newValue
+ */
+ set: ({ set, get, reset }, newValue): void => {
+ console.log('selectedNodesSelector set', newValue);
+ set(selectedNodesState, newValue);
+ },
+});
diff --git a/src/states/variablesState.ts b/src/states/variablesState.ts
new file mode 100644
index 0000000..07293f6
--- /dev/null
+++ b/src/states/variablesState.ts
@@ -0,0 +1,41 @@
+import capitalize from 'lodash.capitalize';
+import { selector } from 'recoil';
+import BaseNodeData from '../types/BaseNodeData';
+import { QuestionChoiceType } from '../types/nodes/QuestionChoiceType';
+import { QuestionChoiceVariable } from '../types/nodes/QuestionNodeAdditionalData';
+import Variable from '../types/Variable';
+import { isQuestionNodeData } from '../utils/guards';
+import { nodesSelector } from './nodesState';
+
+/**
+ * Builds the list of variables based on the variables used by nodes.
+ *
+ * This makes sure the list of variables that are displayed in "If" selects are always up-to-date with variable names used in Question nodes.
+ * Doesn't allow manual mutation.
+ */
+export const variablesSelector = selector({
+ key: 'variablesSelector',
+ get: ({ get }): Variable[] => {
+ const currentNodes: BaseNodeData[] = get(nodesSelector) as BaseNodeData[];
+ const variableNamesFromNodes: Variable[] = ((currentNodes
+ ?.map((node: BaseNodeData): Variable | undefined => {
+ if (isQuestionNodeData(node)) {
+ return {
+ name: node?.data?.variableName as string,
+ label: capitalize(node?.data?.variableName) as string,
+ type: node?.data?.questionChoiceType as QuestionChoiceType,
+ choices: node?.data?.questionChoices as QuestionChoiceVariable[],
+ };
+ } else {
+ return undefined;
+ }
+ }) as Variable[] | undefined)
+ ?.filter((variable: Variable): boolean => typeof variable !== 'undefined')) as Variable[];
+
+ return variableNamesFromNodes || [];
+ },
+
+ set: ({ set, get, reset }, newValue): void => {
+ throw new Error(`It is forbidden to set the variables manually, because they're resolved dynamically from nodes directly.`);
+ },
+});
diff --git a/src/types/BaseBlockComponent.ts b/src/types/BaseBlockComponent.ts
new file mode 100644
index 0000000..5316a61
--- /dev/null
+++ b/src/types/BaseBlockComponent.ts
@@ -0,0 +1,10 @@
+import { FunctionComponent } from 'react';
+import BaseNodeProps from './BaseNodeProps';
+import { GenericObject } from './GenericObject';
+
+/**
+ * Used by all "Block" components.
+ */
+export type BaseBlockComponent = FunctionComponent & {}
+
+export default BaseBlockComponent;
diff --git a/src/types/BaseEdgeAdditionalData.ts b/src/types/BaseEdgeAdditionalData.ts
new file mode 100644
index 0000000..9363d8b
--- /dev/null
+++ b/src/types/BaseEdgeAdditionalData.ts
@@ -0,0 +1,8 @@
+/**
+ * Additional edge data properties.
+ *
+ * Doesn't do anything particular at the moment, used in case we'd need to extend it later on.
+ */
+export type BaseEdgeAdditionalData = {};
+
+export default BaseEdgeAdditionalData;
diff --git a/src/types/BaseEdgeData.ts b/src/types/BaseEdgeData.ts
new file mode 100644
index 0000000..e99ebe0
--- /dev/null
+++ b/src/types/BaseEdgeData.ts
@@ -0,0 +1,11 @@
+import { EdgeData } from 'reaflow/dist/types';
+import BaseEdgeAdditionalData from './BaseEdgeAdditionalData';
+
+/**
+ * Shape of any edge.
+ *
+ * Extends the Reaflow.EdgeData and add additional data to its "data" property.
+ */
+export type BaseEdgeData = EdgeData>;
+
+export default BaseEdgeData;
diff --git a/src/types/BaseEdgeProps.ts b/src/types/BaseEdgeProps.ts
new file mode 100644
index 0000000..7ffdbc5
--- /dev/null
+++ b/src/types/BaseEdgeProps.ts
@@ -0,0 +1,10 @@
+import { EdgeProps } from 'reaflow';
+
+/**
+ * Props received by any edge component (BaseEdge).
+ *
+ * Doesn't do anything particular at the moment, used in case we'd need to extend it later on.
+ */
+export type BaseEdgeProps = {} & Partial;
+
+export default BaseEdgeProps;
diff --git a/src/types/BaseNodeAdditionalData.ts b/src/types/BaseNodeAdditionalData.ts
new file mode 100644
index 0000000..5697cdf
--- /dev/null
+++ b/src/types/BaseNodeAdditionalData.ts
@@ -0,0 +1,15 @@
+import NodeType from './NodeType';
+
+/**
+ * Additional node data properties that are common to all nodes.
+ */
+export type BaseNodeAdditionalData = {
+ /**
+ * Each node has a type, which affects how it's rendered (layout, size, etc.).
+ *
+ * The component will dynamically detect the type and render the associated React component (StartNode, InformationNode, etc.).
+ */
+ type: Type;
+};
+
+export default BaseNodeAdditionalData;
diff --git a/src/types/BaseNodeComponent.ts b/src/types/BaseNodeComponent.ts
new file mode 100644
index 0000000..7b801f1
--- /dev/null
+++ b/src/types/BaseNodeComponent.ts
@@ -0,0 +1,28 @@
+import { FunctionComponent } from 'react';
+import BaseNodeProps from './BaseNodeProps';
+import BasePortData from './BasePortData';
+import { GetBaseNodeDefaultProps } from './GetBaseNodeDefaultProps';
+
+/**
+ * React Block functional component.
+ *
+ * Used by all node components (BaseNode).
+ */
+export type BaseNodeComponent = FunctionComponent & {
+ /**
+ * Function returning the default props of the node.
+ *
+ * Each node type might implement its own "getDefaultNodeProps".
+ * Used when creating a new node.
+ */
+ getDefaultNodeProps?: GetBaseNodeDefaultProps;
+
+ /**
+ * Function returning the default ports of the node.
+ *
+ * Invoked by getDefaultNodeProps.
+ */
+ getDefaultPorts?: () => BasePortData[];
+}
+
+export default BaseNodeComponent;
diff --git a/src/types/BaseNodeData.ts b/src/types/BaseNodeData.ts
new file mode 100644
index 0000000..855a8d3
--- /dev/null
+++ b/src/types/BaseNodeData.ts
@@ -0,0 +1,11 @@
+import { NodeData } from 'reaflow';
+import BaseNodeAdditionalData from './BaseNodeAdditionalData';
+
+/**
+ * Shape of any node.
+ *
+ * Extends the Reaflow.NodeData and add additional data to its "data" property.
+ */
+export type BaseNodeData = NodeData;
+
+export default BaseNodeData;
diff --git a/src/types/BaseNodeDefaultProps.ts b/src/types/BaseNodeDefaultProps.ts
new file mode 100644
index 0000000..5605933
--- /dev/null
+++ b/src/types/BaseNodeDefaultProps.ts
@@ -0,0 +1,14 @@
+import BasePortData from './BasePortData';
+import NodeType from './NodeType';
+
+/**
+ * Default node properties shared by all nodes, no matter what type they are.
+ *
+ * Can use the same properties as "BaseNodeData".
+ */
+export type BaseNodeDefaultProps = {
+ type: NodeType;
+ defaultWidth: number;
+ defaultHeight: number;
+ ports: BasePortData[];
+}
diff --git a/src/types/BaseNodeProps.ts b/src/types/BaseNodeProps.ts
new file mode 100644
index 0000000..763540d
--- /dev/null
+++ b/src/types/BaseNodeProps.ts
@@ -0,0 +1,16 @@
+import { NodeProps } from 'reaflow';
+import BaseNodeData from './BaseNodeData';
+
+export type PatchCurrentNode = Partial> = (patch: Partial) => void;
+
+/**
+ * Props received by any *Node component (InformationNode, etc.).
+ */
+export type BaseNodeProps = {
+ /**
+ * Current node.
+ */
+ node: NodeData;
+} & Partial;
+
+export default BaseNodeProps;
diff --git a/src/types/BasePortData.ts b/src/types/BasePortData.ts
new file mode 100644
index 0000000..ae5055e
--- /dev/null
+++ b/src/types/BasePortData.ts
@@ -0,0 +1,11 @@
+import { PortData } from 'reaflow';
+
+/**
+ * Shape of any port.
+ * Extends the Reaflow.PortData.
+ *
+ * Doesn't do anything particular at the moment, used in case we'd need to extend it later on.
+ */
+export type BasePortData = PortData & {};
+
+export default BasePortData;
diff --git a/src/types/BasePortProps.ts b/src/types/BasePortProps.ts
new file mode 100644
index 0000000..34e8c80
--- /dev/null
+++ b/src/types/BasePortProps.ts
@@ -0,0 +1,10 @@
+import { PortProps } from 'reaflow';
+
+/**
+ * Props received by any port component (BasePort).
+ *
+ * Doesn't do anything particular at the moment, used in case we'd need to extend it later on.
+ */
+export type BasePortProps = {} & Partial;
+
+export default BasePortProps;
diff --git a/src/types/BlockPickerMenu.ts b/src/types/BlockPickerMenu.ts
new file mode 100644
index 0000000..2a0df04
--- /dev/null
+++ b/src/types/BlockPickerMenu.ts
@@ -0,0 +1,75 @@
+import BasePortData from './BasePortData';
+import NodeType from './NodeType';
+
+/**
+ * Type of the onBlockClick function.
+ *
+ * @param blockType
+ * @param blockPickerMenu Necessary to ensure we're always manipulating an up-to-date "blockPickerMenu" within the "onBlockClick" function
+ */
+export type OnBlockClick = (blockType: NodeType, blockPickerMenu?: BlockPickerMenu) => void;
+
+/**
+ * The block picker menu is a (floating) menu displaying all available blocks.
+ *
+ * It can be floating or positioned to the bottom of the page.
+ * It is used to create new nodes, by clicking on a block.
+ */
+export type BlockPickerMenu = {
+ /**
+ * Element source (node/edge) that initiated the display of the block picker menu.
+ *
+ * Used to know whether to toggle the display of the block picker menu
+ * when clicking upon an element that opens it, or to simply refresh its state.
+ *
+ * `playground` acts as a default value applying when there is no existing node.
+ *
+ * @default playground
+ */
+ displayedFrom?: string | 'playground';
+
+ /**
+ * Function executed when a block is clicked.
+ *
+ * Usually used to create a new node, edges, etc.
+ */
+ onBlockClick?: OnBlockClick;
+
+ /**
+ * Whether the block picker menu is displayed
+ */
+ isDisplayed: boolean;
+
+ /**
+ * Absolute CSS "top" position.
+ *
+ * If not set, the "bottom" CSS position will be set to "0".
+ *
+ * @default initial
+ */
+ top?: number;
+
+ /**
+ * Absolute CSS "left" position.
+ *
+ * @default automatically calculated
+ */
+ left?: number;
+
+ /**
+ * When the menu was opened through a click, contains the event.target.
+ */
+ eventTarget?: EventTarget;
+
+ /**
+ * When the menu was opened through a click on a port, contains the port that was clicked on.
+ */
+ fromPort?: BasePortData;
+
+ /**
+ * Timestamp of creation. (should use lodash.now())
+ */
+ at?: number;
+}
+
+export default BlockPickerMenu;
diff --git a/src/types/CanvasDataset.ts b/src/types/CanvasDataset.ts
new file mode 100644
index 0000000..6b21b61
--- /dev/null
+++ b/src/types/CanvasDataset.ts
@@ -0,0 +1,10 @@
+import BaseEdgeData from './BaseEdgeData';
+import BaseNodeData from './BaseNodeData';
+
+/**
+ * The canvas dataset contains all nodes and edges drawn in the canvas.
+ */
+export type CanvasDataset = {
+ nodes: BaseNodeData[];
+ edges: BaseEdgeData[];
+}
diff --git a/src/types/GenericObject.ts b/src/types/GenericObject.ts
new file mode 100644
index 0000000..9cfb0e4
--- /dev/null
+++ b/src/types/GenericObject.ts
@@ -0,0 +1,9 @@
+/**
+ * Helper to avoid writing `Record` everywhere you would usually use "object".
+ *
+ * @example (data: GenericObject) => void
+ * @example variables: GenericObject
+ *
+ * @see https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-632833366
+ */
+export type GenericObject = Record;
diff --git a/src/types/GetBaseNodeDefaultProps.ts b/src/types/GetBaseNodeDefaultProps.ts
new file mode 100644
index 0000000..9e21ea1
--- /dev/null
+++ b/src/types/GetBaseNodeDefaultProps.ts
@@ -0,0 +1,16 @@
+import { BaseNodeDefaultProps } from './BaseNodeDefaultProps';
+import NodeType from './NodeType';
+
+/**
+ * Arguments of the GetBaseNodeDefaultProps function.
+ */
+export type GetBaseNodeDefaultPropsProps = {
+ type: NodeType;
+ defaultWidth?: number;
+ defaultHeight?: number;
+}
+
+/**
+ * Signature of the getDefaultNodeProps function.
+ */
+export type GetBaseNodeDefaultProps = (props: GetBaseNodeDefaultPropsProps) => BaseNodeDefaultProps;
diff --git a/src/types/LastCreated.ts b/src/types/LastCreated.ts
new file mode 100644
index 0000000..297261a
--- /dev/null
+++ b/src/types/LastCreated.ts
@@ -0,0 +1,16 @@
+import BaseNodeData from './BaseNodeData';
+
+/**
+ * Node that was created at last, and its timestamp of creation.
+ */
+export type LastCreated = {
+ /**
+ * Node that was created last.
+ */
+ node: BaseNodeData;
+
+ /**
+ * Timestamp of creation. (should use lodash.now())
+ */
+ at: number;
+};
diff --git a/src/types/MouseEntered.ts b/src/types/MouseEntered.ts
new file mode 100644
index 0000000..3792f95
--- /dev/null
+++ b/src/types/MouseEntered.ts
@@ -0,0 +1,10 @@
+import BaseNodeData from './BaseNodeData';
+import BasePortData from './BasePortData';
+
+/**
+ * Contains the currently entered node and port.
+ */
+export type MouseEntered = {
+ enteredNode?: BaseNodeData;
+ enteredPort?: BasePortData;
+};
diff --git a/src/types/NodeDataWithVariableName.ts b/src/types/NodeDataWithVariableName.ts
new file mode 100644
index 0000000..6a5f613
--- /dev/null
+++ b/src/types/NodeDataWithVariableName.ts
@@ -0,0 +1,24 @@
+import BaseNodeAdditionalData from './BaseNodeAdditionalData';
+import BaseNodeData from './BaseNodeData';
+import Variable from './Variable';
+
+type WithVariableName = {
+ /**
+ * Variable name used to store the user's response to the question.
+ */
+ variableName?: Variable['name'];
+}
+
+/**
+ * BaseNodeData with variable name property.
+ *
+ * Abstract type used to treat nodes implementing the "variableName" in a particular way.
+ * XXX There might be a better/simpler TS implementation for this, but I haven't found any.
+ *
+ * @see https://stackoverflow.com/questions/66398473/react-typescript-extending-component-with-generic-type-and-reuse-generic-type
+ */
+export type NodeDataWithVariableName =
+ BaseNodeData
+ & WithVariableName;
+
+export default NodeDataWithVariableName;
diff --git a/src/types/NodeType.ts b/src/types/NodeType.ts
new file mode 100644
index 0000000..b875a27
--- /dev/null
+++ b/src/types/NodeType.ts
@@ -0,0 +1,19 @@
+/**
+ * Node type.
+ *
+ * Defines all the allow node types.
+ * The type will affect how the node is rendered (layout, size, etc.)
+ *
+ * Each node type must have an associated Block component and Node component.
+ * - The Block component is used to select the block to be used (from BlockPickerMenu).
+ * - The Node component is used to render the node.
+ *
+ * @example The `information` type has the associated InformationBlock and InformationNode components.
+ */
+export type NodeType =
+ 'start'
+ | 'information'
+ | 'question'
+ | 'if'
+
+export default NodeType;
diff --git a/src/types/OnSelectedOptionChange.ts b/src/types/OnSelectedOptionChange.ts
new file mode 100644
index 0000000..be27390
--- /dev/null
+++ b/src/types/OnSelectedOptionChange.ts
@@ -0,0 +1,10 @@
+import {
+ ActionMeta,
+ OptionTypeBase,
+} from 'react-select/src/types';
+import { ReactSelectDefaultOption } from './ReactSelect';
+
+/**
+ * React select "onChange".
+ */
+export type OnSelectedOptionChange = (selectedOption: ReactSelectDefaultOption, actionMeta: ActionMeta) => void;
diff --git a/src/types/ReactSelect.ts b/src/types/ReactSelect.ts
new file mode 100644
index 0000000..3ea41b6
--- /dev/null
+++ b/src/types/ReactSelect.ts
@@ -0,0 +1,23 @@
+/**
+ * A react-select group
+ *
+ * @see https://react-select.com/advanced#replacing-builtins
+ * @see https://stackoverflow.com/a/52503863/2391795
+ * @see https://www.saltycrane.com/cheat-sheets/typescript/react-select/latest/
+ */
+export type ReactSelectGroup = {
+ label: string;
+ labelShort?: string;
+ options: Array;
+}
+
+/**
+ * A react-select option must have a label and a value field
+ *
+ * But those two keys can be changed from within the component using getOptionLabel and getOptionValue
+ * @see https://www.saltycrane.com/cheat-sheets/typescript/react-select/latest/
+ */
+export type ReactSelectDefaultOption = {
+ label: string;
+ value: string;
+}
diff --git a/src/types/Variable.ts b/src/types/Variable.ts
new file mode 100644
index 0000000..ef98e4e
--- /dev/null
+++ b/src/types/Variable.ts
@@ -0,0 +1,14 @@
+import { QuestionChoiceType } from './nodes/QuestionChoiceType';
+import { QuestionChoiceVariable } from './nodes/QuestionNodeAdditionalData';
+
+/**
+ * Variable used by a node. The variable's name is used as index to store the user's answer.
+ */
+export type Variable = {
+ name: string;
+ label: string;
+ type: QuestionChoiceType;
+ choices: QuestionChoiceVariable[];
+}
+
+export default Variable;
diff --git a/src/types/forms.ts b/src/types/forms.ts
new file mode 100644
index 0000000..9441255
--- /dev/null
+++ b/src/types/forms.ts
@@ -0,0 +1,6 @@
+import React from 'react';
+
+// See https://stackoverflow.com/questions/42081549/typescript-react-event-types
+export type InputChangeEventHandler = React.ChangeEvent;
+export type TextareaChangeEventHandler = React.ChangeEvent;
+export type SelectChangeEventHandler = React.ChangeEvent;
diff --git a/src/types/nodes/IfNodeAdditionalData.ts b/src/types/nodes/IfNodeAdditionalData.ts
new file mode 100644
index 0000000..7381afb
--- /dev/null
+++ b/src/types/nodes/IfNodeAdditionalData.ts
@@ -0,0 +1,10 @@
+import BaseNodeAdditionalData from '../BaseNodeAdditionalData';
+
+/**
+ * Additional "node.data" for the "IfNodeData" type.
+ */
+export type IfNodeAdditionalData = BaseNodeAdditionalData & {
+ comparedVariableName?: string;
+ expectedValue?: string;
+ comparisonOperator?: string;
+};
diff --git a/src/types/nodes/IfNodeData.ts b/src/types/nodes/IfNodeData.ts
new file mode 100644
index 0000000..fcfb549
--- /dev/null
+++ b/src/types/nodes/IfNodeData.ts
@@ -0,0 +1,7 @@
+import BaseNodeData from '../BaseNodeData';
+import { IfNodeAdditionalData } from './IfNodeAdditionalData';
+
+/**
+ * Node "data" for the "IfNodeData" type.
+ */
+export type IfNodeData = BaseNodeData;
diff --git a/src/types/nodes/InformationNodeAdditionalData.ts b/src/types/nodes/InformationNodeAdditionalData.ts
new file mode 100644
index 0000000..7f10842
--- /dev/null
+++ b/src/types/nodes/InformationNodeAdditionalData.ts
@@ -0,0 +1,8 @@
+import BaseNodeAdditionalData from '../BaseNodeAdditionalData';
+
+/**
+ * Additional "node.data" for the "InformationNodeData" type.
+ */
+export type InformationNodeAdditionalData = BaseNodeAdditionalData & {
+ informationText?: string;
+};
diff --git a/src/types/nodes/InformationNodeData.ts b/src/types/nodes/InformationNodeData.ts
new file mode 100644
index 0000000..8f9da21
--- /dev/null
+++ b/src/types/nodes/InformationNodeData.ts
@@ -0,0 +1,7 @@
+import BaseNodeData from '../BaseNodeData';
+import { InformationNodeAdditionalData } from './InformationNodeAdditionalData';
+
+/**
+ * Node "data" for the "InformationNodeData" type.
+ */
+export type InformationNodeData = BaseNodeData;
diff --git a/src/types/nodes/QuestionChoiceType.ts b/src/types/nodes/QuestionChoiceType.ts
new file mode 100644
index 0000000..36fd2e7
--- /dev/null
+++ b/src/types/nodes/QuestionChoiceType.ts
@@ -0,0 +1,8 @@
+/**
+ * Available types for a question's choice.
+ *
+ * Defines how the question choices will inputs will be rendered.
+ * - text: Nothing (would display a text input to the end-user).
+ * - single-quick-reply: Displays a dynamic list of question choices.
+ */
+export type QuestionChoiceType = 'text' | 'single-quick-reply';
diff --git a/src/types/nodes/QuestionChoiceTypeOption.ts b/src/types/nodes/QuestionChoiceTypeOption.ts
new file mode 100644
index 0000000..208b93a
--- /dev/null
+++ b/src/types/nodes/QuestionChoiceTypeOption.ts
@@ -0,0 +1,9 @@
+import { QuestionChoiceType } from './QuestionChoiceType';
+
+/**
+ * React-select options displayed as choices for the question choice type, in component.
+ */
+export type QuestionChoiceTypeOption = {
+ value: QuestionChoiceType;
+ label: string;
+}
diff --git a/src/types/nodes/QuestionNodeAdditionalData.ts b/src/types/nodes/QuestionNodeAdditionalData.ts
new file mode 100644
index 0000000..35652b2
--- /dev/null
+++ b/src/types/nodes/QuestionNodeAdditionalData.ts
@@ -0,0 +1,54 @@
+import BaseNodeAdditionalData from '../BaseNodeAdditionalData';
+import NodeDataWithVariableName from '../NodeDataWithVariableName';
+import { QuestionChoiceType } from './QuestionChoiceType';
+
+/**
+ * Variable used as a question's choice.
+ */
+export type QuestionChoiceVariable = {
+ /**
+ * Id of the variable.
+ * Used for react indexing ("key").
+ */
+ id: string;
+
+ /**
+ * Timestamp of creation. (should use lodash.now())
+ */
+ createdAt: string;
+
+ /**
+ * Name of the variable.
+ */
+ name?: string;
+
+ /**
+ * Value of the variable.
+ * String is the expected type (because text input), but it could also represent a number.
+ *
+ * TODO handle i18n
+ */
+ value?: string;
+}
+
+/**
+ * Additional "node.data" for the "QuestionNodeData" type.
+ */
+export type QuestionNodeAdditionalData = BaseNodeAdditionalData & NodeDataWithVariableName & {
+ /**
+ * Question asked to the user (as text).
+ */
+ questionText?: string;
+
+ /**
+ * Type of the question
+ */
+ questionChoiceType?: QuestionChoiceType;
+
+ /**
+ * Question choices.
+ *
+ * Used when questionChoiceType is 'single-quick-reply'.
+ */
+ questionChoices?: QuestionChoiceVariable[];
+};
diff --git a/src/types/nodes/QuestionNodeData.ts b/src/types/nodes/QuestionNodeData.ts
new file mode 100644
index 0000000..76c5f0f
--- /dev/null
+++ b/src/types/nodes/QuestionNodeData.ts
@@ -0,0 +1,7 @@
+import BaseNodeData from '../BaseNodeData';
+import { QuestionNodeAdditionalData } from './QuestionNodeAdditionalData';
+
+/**
+ * Node "data" for the "QuestionNodeData" type.
+ */
+export type QuestionNodeData = BaseNodeData;
diff --git a/src/types/nodes/SpecializedNodeProps.ts b/src/types/nodes/SpecializedNodeProps.ts
new file mode 100644
index 0000000..9176313
--- /dev/null
+++ b/src/types/nodes/SpecializedNodeProps.ts
@@ -0,0 +1,40 @@
+import { NodeChildProps } from 'reaflow';
+import BaseNodeData from '../BaseNodeData';
+import { PatchCurrentNode } from '../BaseNodeProps';
+import { LastCreated } from '../LastCreated';
+
+/**
+ * A SpecializedNodeProps is a generic type that extends the BaseNodeData, with additional properties common to all specialized nodes.
+ */
+export type SpecializedNodeProps = Omit & {
+ /**
+ * Current node data.
+ */
+ node: NodeData;
+
+ /**
+ * Path the properties of the current node.
+ *
+ * Only updates the provided properties, doesn't update other properties.
+ * Also merges the 'data' object, by keeping existing data and only overwriting those that are specified.
+ *
+ * @param nodeData
+ */
+ patchCurrentNode: PatchCurrentNode>;
+
+ /**
+ * The last created node and its time of creation.
+ * Will be undefined if no node was created yet.
+ */
+ lastCreated?: LastCreated | undefined;
+
+ /**
+ * Whether the node is being selected.
+ */
+ isSelected: boolean;
+
+ /**
+ * Whether the node can be reached.
+ */
+ isReachable: boolean;
+}
diff --git a/src/types/nodes/StartNodeAdditionalData.ts b/src/types/nodes/StartNodeAdditionalData.ts
new file mode 100644
index 0000000..9e4860a
--- /dev/null
+++ b/src/types/nodes/StartNodeAdditionalData.ts
@@ -0,0 +1,6 @@
+import BaseNodeAdditionalData from '../BaseNodeAdditionalData';
+
+/**
+ * Additional "node.data" for the "StartNodeData" type.
+ */
+export type StartNodeAdditionalData = BaseNodeAdditionalData & {};
diff --git a/src/types/nodes/StartNodeData.ts b/src/types/nodes/StartNodeData.ts
new file mode 100644
index 0000000..2768d93
--- /dev/null
+++ b/src/types/nodes/StartNodeData.ts
@@ -0,0 +1,7 @@
+import BaseNodeData from '../BaseNodeData';
+import { StartNodeAdditionalData } from './StartNodeAdditionalData';
+
+/**
+ * Node "data" for the "StartNodeData" type.
+ */
+export type StartNodeData = BaseNodeData;
diff --git a/src/utils/array.ts b/src/utils/array.ts
new file mode 100644
index 0000000..79b4c88
--- /dev/null
+++ b/src/utils/array.ts
@@ -0,0 +1,11 @@
+/**
+ * Detects whether an array has duplicated objects.
+ *
+ * @param array
+ * @param key
+ */
+export const hasDuplicatedObjects = (array: T[], key: keyof T): boolean => {
+ const _array = array.map((element: T) => element[key]);
+
+ return new Set(_array).size !== _array.length;
+};
diff --git a/src/utils/canvas.ts b/src/utils/canvas.ts
new file mode 100644
index 0000000..682baa8
--- /dev/null
+++ b/src/utils/canvas.ts
@@ -0,0 +1,25 @@
+import settings from '../settings';
+
+export type PositionMargin = {
+ top?: number;
+ left?: number;
+}
+
+/**
+ * Translates a x/y position to another x/y canvas position.
+ *
+ * Optionally allows to apply a margin.
+ *
+ * @param x
+ * @param y
+ * @param margin
+ */
+export const translateXYToCanvasPosition = (x: number, y: number, margin?: PositionMargin): [x: number, y: number] => {
+ const xDelta = 0; // No delta, because the canvas takes the full page width
+ const yDelta = settings.layout.nav.height; // Some delta, because the canvas is not at the top of the page, but below the header
+
+ return [
+ x - xDelta + (margin?.left || 0),
+ y - yDelta + (margin?.top || 0),
+ ];
+};
diff --git a/src/utils/date.ts b/src/utils/date.ts
new file mode 100644
index 0000000..bfa7948
--- /dev/null
+++ b/src/utils/date.ts
@@ -0,0 +1,23 @@
+import now from 'lodash.now';
+
+/**
+ * Whether a timestamp is younger than the target.
+ *
+ * @param timestamp
+ * @param age
+ * @param target
+ */
+export const isYoungerThan = (timestamp?: number, age: number = 1000, target: number = now()): boolean => {
+ return ((timestamp || 0) + age > target);
+};
+
+/**
+ * Whether a timestamp is older than the target.
+ *
+ * @param timestamp
+ * @param age
+ * @param target
+ */
+export const isOlderThan = (timestamp?: number, age: number = 1000, target: number = now()): boolean => {
+ return ((timestamp || 0) + age < target);
+};
diff --git a/src/utils/edges.ts b/src/utils/edges.ts
new file mode 100644
index 0000000..884283e
--- /dev/null
+++ b/src/utils/edges.ts
@@ -0,0 +1,33 @@
+import { v1 as uuid } from 'uuid';
+import BaseEdgeData from '../types/BaseEdgeData';
+import BaseNodeData from '../types/BaseNodeData';
+import BasePortData from '../types/BasePortData';
+
+/**
+ * Creates a new edge and returns it.
+ *
+ * Optionally links it to a port.
+ *
+ * @param fromNode
+ * @param toNode
+ * @param fromPort
+ * @param toPort
+ * @param edgeData
+ */
+export const createEdge = (fromNode?: BaseNodeData, toNode?: BaseNodeData, fromPort?: Partial, toPort?: Partial, edgeData?: Partial): BaseEdgeData => {
+ let { id = uuid() } = edgeData || {};
+
+ const newEdge = {
+ text: ' ', // Use a space to increase the distance between nodes, which ease edge's selection
+ ...edgeData,
+ id,
+ from: fromNode?.id,
+ to: toNode?.id,
+ parent: fromNode?.parent,
+ fromPort: fromPort?.id,
+ toPort: toPort?.id,
+ };
+ console.log('createEdge newEdge', newEdge);
+
+ return newEdge;
+};
diff --git a/src/utils/fontAwesome.ts b/src/utils/fontAwesome.ts
new file mode 100644
index 0000000..3edbc04
--- /dev/null
+++ b/src/utils/fontAwesome.ts
@@ -0,0 +1,55 @@
+import {
+ config,
+ library,
+} from '@fortawesome/fontawesome-svg-core';
+import '@fortawesome/fontawesome-svg-core/styles.css';
+import { faGithub } from '@fortawesome/free-brands-svg-icons';
+import {
+ faBullseye,
+ faClone,
+ faCompressArrowsAlt,
+ faExclamationTriangle,
+ faHeart,
+ faHome,
+ faPaperPlane,
+ faPlay,
+ faSearchMinus,
+ faSearchPlus,
+ faTrashAlt,
+} from '@fortawesome/free-solid-svg-icons';
+
+// See https://github.com/FortAwesome/react-fontawesome#integrating-with-other-tools-and-frameworks
+config.autoAddCss = false; // Tell Font Awesome to skip adding the CSS automatically since it's being imported above
+
+/**
+ * Configure the Font-Awesome icons library by pre-registering icon definitions so that we do not have to explicitly pass them to render an icon.
+ * Necessary for proper server-side rendering of icons.
+ *
+ * XXX Since Next.js 10, it is possible to import CSS file outside of the _app.tsx file.
+ * We leverage this new feature to configure our Font-Awesome icons outside of _app to avoid cluttering that file.
+ *
+ * @example
+ *
+ * @see https://fontawesome.com/how-to-use/javascript-api/methods/library-add
+ * @see https://nextjs.org/blog/next-10#importing-css-from-third-party-react-components
+ */
+
+// Import @fortawesome/free-solid-svg-icons
+library.add(
+ faBullseye,
+ faClone,
+ faCompressArrowsAlt,
+ faExclamationTriangle,
+ faHeart,
+ faHome,
+ faPaperPlane,
+ faPlay,
+ faSearchMinus,
+ faSearchPlus,
+ faTrashAlt,
+);
+
+// Import @fortawesome/free-brands-svg-icons
+library.add(
+ faGithub,
+);
diff --git a/src/utils/guards.ts b/src/utils/guards.ts
new file mode 100644
index 0000000..acbb3b3
--- /dev/null
+++ b/src/utils/guards.ts
@@ -0,0 +1,14 @@
+import BaseNodeData from '../types/BaseNodeData';
+import { QuestionNodeData } from '../types/nodes/QuestionNodeData';
+
+/**
+ * Applies a TypeScript guard check to make sure the nodeData is of QuestionNodeData type.
+ *
+ * @param nodeData
+ *
+ * @see https://rangle.io/blog/how-to-use-typescript-type-guards/
+ */
+export const isQuestionNodeData = (nodeData: BaseNodeData): nodeData is QuestionNodeData => {
+ return nodeData?.data?.type === 'question';
+};
+
diff --git a/src/utils/nodes.ts b/src/utils/nodes.ts
new file mode 100644
index 0000000..747bdc0
--- /dev/null
+++ b/src/utils/nodes.ts
@@ -0,0 +1,300 @@
+import cloneDeep from 'lodash.clonedeep';
+import filter from 'lodash.filter';
+import { v1 as uuid } from 'uuid';
+import BaseNode from '../components/nodes/BaseNode';
+import IfNode from '../components/nodes/IfNode';
+import InformationNode from '../components/nodes/InformationNode';
+import QuestionNode from '../components/nodes/QuestionNode';
+import StartNode from '../components/nodes/StartNode';
+import BaseEdgeData from '../types/BaseEdgeData';
+import BaseNodeComponent from '../types/BaseNodeComponent'; // XXX Use v1 for uniqueness - See https://www.sohamkamani.com/blog/2016/10/05/uuid1-vs-uuid4/
+import BaseNodeData from '../types/BaseNodeData';
+import { BaseNodeDefaultProps } from '../types/BaseNodeDefaultProps';
+import BasePortData from '../types/BasePortData';
+import { CanvasDataset } from '../types/CanvasDataset';
+import { GetBaseNodeDefaultProps } from '../types/GetBaseNodeDefaultProps';
+import NodeType from '../types/NodeType';
+import { createEdge } from './edges';
+import {
+ getDefaultFromPort,
+ getDefaultToPort,
+} from './ports';
+
+/**
+ * Creates a new node and returns it.
+ *
+ * @param nodeData
+ */
+export const createNode = (nodeData?: Partial): BaseNodeData => {
+ let { id = uuid() } = nodeData || {};
+
+ const newNode = {
+ ...nodeData,
+ id,
+ };
+ console.log('createNode newNode', newNode);
+
+ return newNode;
+};
+
+/**
+ * Creates a new node from the default props and returns it.
+ *
+ * @param defaultProps
+ */
+export const createNodeFromDefaultProps = (defaultProps: BaseNodeDefaultProps): BaseNodeData => {
+ console.log('createNodeFromDefaultProps', defaultProps);
+ const node = {
+ text: undefined, // XXX Built-in Reaflow "text", unused in our case because we use complex components and we don't need it
+ width: defaultProps.defaultWidth,
+ height: defaultProps.defaultHeight,
+ data: {
+ type: defaultProps.type,
+ defaultWidth: defaultProps.defaultWidth,
+ defaultHeight: defaultProps.defaultHeight,
+ },
+ ports: defaultProps.ports || [],
+ };
+
+ return createNode(node);
+};
+
+/**
+ * Clone an existing node.
+ *
+ * Keeps all properties, except for the id which is regenerated.
+ *
+ * @param node
+ */
+export const cloneNode = (node: BaseNodeData): BaseNodeData => {
+ const newNode = cloneDeep(node);
+ newNode.id = uuid(); // Force generating a new id for the cloned node
+
+ // Generate new ids for ports (or it'll completely break ELK when it tries to link existing edges to ports)
+ newNode?.ports?.map((port: BasePortData) => port.id = uuid());
+
+ return {
+ ...createNode(newNode),
+ };
+};
+
+/**
+ * Filter out a node from an array of nodes.
+ *
+ * @param nodes
+ * @param nodeToFilter
+ */
+export const filterNodeInArray = (nodes: BaseNodeData[], nodeToFilter: BaseNodeData) => {
+ return filter(nodes, (node: BaseNodeData) => {
+ return node?.id !== nodeToFilter.id;
+ });
+};
+
+/**
+ * Returns the Node Component associated to a given node type.
+ *
+ * @param nodeType
+ */
+export const findNodeComponentByType = (nodeType: NodeType): BaseNodeComponent => {
+ switch (nodeType) {
+ case 'start':
+ return StartNode;
+ case 'information':
+ return InformationNode;
+ case 'question':
+ // @ts-ignore
+ return QuestionNode;
+ case 'if':
+ return IfNode;
+ default:
+ throw new Error(`Couldn't find the Node Component to use, using "nodeType=${nodeType}"`);
+ }
+};
+
+/**
+ * Get default props from the Node component related to the nodeType.
+ *
+ * If the Node component doesn't expose "getDefaultNodeProps", fallbacks to the "getDefaultNodeProps" exposed in BaseNode component.
+ *
+ * @param nodeType
+ */
+export const getDefaultNodePropsWithFallback = (nodeType: NodeType): BaseNodeDefaultProps => {
+ const NodeComponent = findNodeComponentByType(nodeType);
+
+ if (typeof NodeComponent.getDefaultNodeProps !== 'undefined') {
+ return NodeComponent.getDefaultNodeProps({ type: nodeType });
+ } else {
+ return (BaseNode.getDefaultNodeProps as GetBaseNodeDefaultProps)({ type: nodeType });
+ }
+};
+
+/**
+ * Detects whether a node is reachable.
+ *
+ * A node that isn't reachable doesn't have any edge coming through its WEST port (left).
+ * Except for "start" node which is the entry point, and thus always reachable.
+ *
+ * @param node
+ * @param edges
+ */
+export const isNodeReachable = (node: BaseNodeData, edges: BaseEdgeData[]) => {
+ if (node?.data?.type === 'start') {
+ return true;
+ }
+
+ const westPort: BasePortData | undefined = node?.ports?.find((port: BasePortData) => port?.side === 'WEST');
+
+ return !!edges?.find((edge: BaseEdgeData) => edge?.toPort === westPort?.id);
+};
+
+/**
+ * Add a node and optional edge, and automatically link their ports.
+ *
+ * Automatically connects the fromNode (left node) using its EAST port (right side) to the newNode (right node) using it's WEST port (left side).
+ *
+ * Similar to reaflow.addNodeAndEdge utility.
+ */
+export function addNodeAndEdgeThroughPorts(
+ nodes: BaseNodeData[],
+ edges: BaseEdgeData[],
+ newNode: BaseNodeData,
+ fromNode?: BaseNodeData,
+ toNode?: BaseNodeData,
+ fromPort?: BasePortData,
+ toPort?: BasePortData,
+): CanvasDataset {
+ // The default destination node is the newly created node
+ toNode = toNode || newNode;
+
+ const newEdge: BaseEdgeData = createEdge(
+ fromNode,
+ toNode,
+ getDefaultFromPort(fromNode, fromPort),
+ getDefaultToPort(toNode, toPort),
+ );
+
+ return {
+ nodes: [...nodes, newNode],
+ edges: [
+ ...edges,
+ ...(fromNode ?
+ [
+ newEdge,
+ ]
+ : []),
+ ],
+ };
+}
+
+/**
+ * Helper function for upserting a node in a edge (split the edge in 2 and put the node in between), and automatically link their ports.
+ *
+ * Automatically connects the left edge to the newNode using it's WEST port (left side).
+ * Automatically connects the right edge to the newNode using it's EAST port (right side).
+ *
+ * Similar to reaflow.upsertNode utility.
+ */
+export function upsertNodeThroughPorts(
+ nodes: BaseNodeData[],
+ edges: BaseEdgeData[],
+ edge: BaseEdgeData,
+ newNode: BaseNodeData,
+): CanvasDataset {
+ const oldEdgeIndex = edges.findIndex(e => e.id === edge.id);
+ const edgeBeforeNewNode = {
+ ...edge,
+ id: `${edge.from}-${newNode.id}`,
+ to: newNode.id,
+ };
+ const edgeAfterNewNode = {
+ ...edge,
+ id: `${newNode.id}-${edge.to}`,
+ from: newNode.id,
+ };
+
+ if (edge.fromPort && edge.toPort) {
+ const fromLeftNodeToWestPort: BasePortData | undefined = newNode?.ports?.find((port: BasePortData) => port?.side === 'WEST');
+ const fromRightNodeToEastPort: BasePortData | undefined = newNode?.ports?.find((port: BasePortData) => port?.side === 'EAST');
+
+ edgeBeforeNewNode.fromPort = edge.fromPort;
+ edgeBeforeNewNode.toPort = fromLeftNodeToWestPort?.id || `${newNode.id}-to`;
+
+ edgeAfterNewNode.fromPort = fromRightNodeToEastPort?.id || `${newNode.id}-from`;
+ edgeAfterNewNode.toPort = edge.toPort;
+ }
+
+ edges.splice(oldEdgeIndex, 1, edgeBeforeNewNode, edgeAfterNewNode);
+
+ return {
+ nodes: [...nodes, newNode],
+ edges: [...edges],
+ };
+}
+
+/**
+ * Removes a node between two edges and merges the two edges into one, and automatically link their ports.
+ *
+ * Similar to reaflow.removeAndUpsertNodes utility.
+ */
+export function removeAndUpsertNodesThroughPorts(
+ nodes: BaseNodeData[],
+ edges: BaseEdgeData[],
+ removeNodes: BaseNodeData | BaseNodeData[],
+ onNodeLinkCheck?: (
+ newNodes: BaseNodeData[],
+ newEdges: BaseEdgeData[],
+ from: BaseNodeData,
+ to: BaseNodeData,
+ port?: BasePortData,
+ ) => undefined | boolean,
+): CanvasDataset {
+ if (!Array.isArray(removeNodes)) {
+ removeNodes = [removeNodes];
+ }
+
+ const nodeIds = removeNodes.map((n) => n.id);
+ const newNodes = nodes.filter((n) => !nodeIds.includes(n.id));
+ const newEdges = edges.filter(
+ (e: BaseEdgeData) => !nodeIds.includes(e?.from as string) && !nodeIds.includes(e?.to as string),
+ );
+
+ for (const nodeId of nodeIds) {
+ const sourceEdges = edges.filter((e) => e.to === nodeId);
+ const targetEdges = edges.filter((e) => e.from === nodeId);
+
+ for (const sourceEdge of sourceEdges) {
+ for (const targetEdge of targetEdges) {
+ const sourceNode = nodes.find((n) => n.id === sourceEdge.from);
+ const targetNode = nodes.find((n) => n.id === targetEdge.to);
+
+ if (sourceNode && targetNode) {
+ const canLink = onNodeLinkCheck?.(
+ newNodes,
+ newEdges,
+ sourceNode,
+ targetNode,
+ );
+
+ if (canLink === undefined || canLink) {
+ const fromPort: BasePortData | undefined = sourceNode?.ports?.find((port: BasePortData) => port?.side === 'EAST');
+ const toPort: BasePortData | undefined = targetNode?.ports?.find((port: BasePortData) => port?.side === 'WEST');
+
+ newEdges.push({
+ id: `${sourceNode.id}-${targetNode.id}`,
+ from: sourceNode.id,
+ to: targetNode.id,
+ parent: sourceNode?.parent,
+ fromPort: fromPort?.id,
+ toPort: toPort?.id,
+ });
+ }
+ }
+ }
+ }
+ }
+
+ return {
+ edges: newEdges,
+ nodes: newNodes,
+ };
+}
diff --git a/src/utils/persistCanvasDataset.ts b/src/utils/persistCanvasDataset.ts
new file mode 100644
index 0000000..017424f
--- /dev/null
+++ b/src/utils/persistCanvasDataset.ts
@@ -0,0 +1,31 @@
+import { CanvasDataset } from '../types/CanvasDataset';
+
+const LS_NS = 'reaflow-graph';
+
+/**
+ * Gets the canvas dataset from the localstorage.
+ */
+export const getCanvasDatasetFromLS = (): Partial => {
+ const data = localStorage.getItem(LS_NS);
+
+ if (!data) {
+ return {};
+ } else {
+ try {
+ return JSON.parse(data);
+ } catch (e) {
+ console.error(e);
+ return {};
+ }
+ }
+};
+
+/**
+ * Persists the canvas dataset in the localstorage.
+ *
+ * @param canvasDataset
+ */
+export const persistCanvasDatasetInLS = (canvasDataset: CanvasDataset): void => {
+ console.log('persistCanvasDatasetInLS', canvasDataset);
+ localStorage.setItem(LS_NS, JSON.stringify(canvasDataset));
+};
diff --git a/src/utils/ports.ts b/src/utils/ports.ts
new file mode 100644
index 0000000..f756006
--- /dev/null
+++ b/src/utils/ports.ts
@@ -0,0 +1,73 @@
+import { v1 as uuid } from 'uuid';
+import BaseEdgeData from '../types/BaseEdgeData';
+import BaseNodeData from '../types/BaseNodeData'; // XXX Use v1 for uniqueness - See https://www.sohamkamani.com/blog/2016/10/05/uuid1-vs-uuid4/
+import BasePortData from '../types/BasePortData';
+
+/**
+ * Creates a new port and returns it.
+ *
+ * @param portData
+ */
+export const createPort = (portData?: Partial): BasePortData => {
+ let { id = uuid() } = portData || {};
+
+ const newPort = {
+ ...portData,
+ id,
+ };
+ console.log('newPort', newPort);
+
+ return newPort as BasePortData;
+};
+
+/**
+ * Resolve default fromPort, based on the existing ports attached to the node.
+ *
+ * @param fromNode
+ * @param fromPort
+ */
+export const getDefaultFromPort = (fromNode?: BaseNodeData, fromPort?: Partial): Partial | undefined => {
+ fromPort = fromPort || fromNode?.ports?.find((port: BasePortData) => port?.side === 'EAST');
+
+ return fromPort;
+};
+
+/**
+ * Resolve default toPort, based on the existing ports attached to the node.
+ *
+ * @param toNode
+ * @param toPort
+ */
+export const getDefaultToPort = (toNode?: BaseNodeData, toPort?: Partial): Partial | undefined => {
+ toPort = toPort || toNode?.ports?.find((port: BasePortData) => port?.side === 'WEST');
+
+ return toPort;
+};
+
+/**
+ * Whether a port can connect to another port.
+ *
+ * Should highlight when:
+ * - The destination node is different. (don't allow recursive link to itself)
+ * - The port source is on EAST side. (don't allow to go from right to left)
+ * - The destination port's side is not the same as the source port's side. (forces left to right workflow)
+ * - There isn't already a connection to the destination port. (avoids duplicated edges)
+ *
+ * @param edges
+ * @param fromNode
+ * @param fromPort
+ * @param toNode
+ * @param toPort
+ */
+export const canConnectToDestinationPort = (edges: BaseEdgeData[], fromNode?: BaseNodeData, fromPort?: BasePortData, toNode?: BaseNodeData, toPort?: BasePortData): boolean => {
+ if (!fromNode || !fromPort || !toNode || !toPort) {
+ return false;
+ }
+
+ const areSourceAndDestinationPortsDifferent = fromNode?.id !== toNode?.id;
+ const arePortsOnDifferentSides = fromPort?.side !== toPort?.side;
+ const isSourcePortFromEastSide = fromPort?.side === 'EAST';
+ const isLinked = !!edges?.find((edge: BaseEdgeData) => edge.from === fromNode?.id && edge?.to === toNode?.id);
+
+ return areSourceAndDestinationPortsDifferent && arePortsOnDifferentSides && isSourcePortFromEastSide && !isLinked;
+};
diff --git a/tsconfig.json b/tsconfig.json
index c65399c..90eaa0f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,12 +11,21 @@
"moduleResolution": "node",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
- "target": "esnext"
+ "target": "esnext",
+
+ // Added by Emotion 11, necessary to use the "css" props using the "Babel preset", without using JSX pragma.
+ // Using the Babel preset is better (DX), because we don't have to use "/** @jsx jsx */" in every file, as Babel does it for us.
+ // See https://emotion.sh/docs/typescript#css-prop TS configuration to support the "css" prop
+ // See https://emotion.sh/docs/emotion-11#typescript Migration guide about Emotion 11 for TS
+ // See https://emotion.sh/docs/css-prop#babel-preset Babel preset vs JSX pragma configuration
+ // See https://github.com/emotion-js/emotion/issues/1606#issuecomment-757930872 Issue explanation when migrating from Emotion 10 to 11
+ // See https://github.com/UnlyEd/next-right-now/pull/247 Emotion v10 > v11 migration pull request
+ "jsxImportSource": "@emotion/react"
},
"exclude": ["node_modules"],
"include": ["**/*.ts", "**/*.tsx"]
diff --git a/utils/sample-data.ts b/utils/sample-data.ts
deleted file mode 100644
index 1dd38ec..0000000
--- a/utils/sample-data.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { User } from '../interfaces'
-
-/** Dummy user data. */
-export const sampleUserData: User[] = [
- { id: 101, name: 'Alice' },
- { id: 102, name: 'Bob' },
- { id: 103, name: 'Caroline' },
- { id: 104, name: 'Dave' },
-]
diff --git a/yarn.lock b/yarn.lock
index a4908c9..c9b7a38 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -60,12 +60,31 @@
dependencies:
"@babel/highlight" "^7.10.4"
+"@babel/code-frame@^7.0.0":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
+ integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
+ dependencies:
+ "@babel/highlight" "^7.12.13"
+
+"@babel/helper-module-imports@^7.7.0":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0"
+ integrity sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==
+ dependencies:
+ "@babel/types" "^7.12.13"
+
+"@babel/helper-plugin-utils@^7.12.13":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz#174254d0f2424d8aefb4dd48057511247b0a9eeb"
+ integrity sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==
+
"@babel/helper-validator-identifier@^7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
-"@babel/highlight@^7.10.4":
+"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c"
integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==
@@ -74,6 +93,13 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/plugin-syntax-jsx@^7.12.1":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz#044fb81ebad6698fe62c478875575bcbb9b70f15"
+ integrity sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.12.13"
+
"@babel/runtime@7.12.5":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
@@ -81,6 +107,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d"
+ integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/types@7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c"
@@ -90,6 +123,675 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
+"@babel/types@^7.12.13":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.13.tgz#8be1aa8f2c876da11a9cf650c0ecf656913ad611"
+ integrity sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.12.11"
+ lodash "^4.17.19"
+ to-fast-properties "^2.0.0"
+
+"@chakra-ui/accordion@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/accordion/-/accordion-1.1.0.tgz#db758a696153efef9663d5e60d00d2db2ea27568"
+ integrity sha512-7aEaP4J2ycOb51fQ3G/TaV5x5mcXT5ojq/yz2wV75TgUHLE7zVIvIClx0LxzHhZBS0b3BjMZ5f4jddUtRPX5dg==
+ dependencies:
+ "@chakra-ui/descendant" "1.0.6"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/icon" "1.1.0"
+ "@chakra-ui/transition" "1.0.7"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/alert@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/alert/-/alert-1.1.0.tgz#9139ee06c2b7d878ea41a500b9074a486b4dc27c"
+ integrity sha512-tnji4AjFCBq8giZ2IrbIy3NVodBbw6tmJUmu31/9WvzM8XgChHhN/gpWuS5zoiSxvkxHcCG0d5exNLNJuB4h7Q==
+ dependencies:
+ "@chakra-ui/icon" "1.1.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/avatar@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/avatar/-/avatar-1.1.0.tgz#98d5a36f8cde706789636afd61e10f2150d2e602"
+ integrity sha512-LtxK9z7iIPhDAlr1/rYp1uaGPDw8Yv/bpI+uWdbnhl3ifEW5CguGfmiBUHG3XrlnBYwU/I95jfX04IlHZnDYTg==
+ dependencies:
+ "@chakra-ui/image" "1.0.6"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/breadcrumb@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/breadcrumb/-/breadcrumb-1.1.0.tgz#7690e4ee3819089702450ceb5f975f19b5de9353"
+ integrity sha512-3T7uyOyKD8Jxb1DHVliBFigR57yjP/+2NS6NcH2+GnIPyFXsodTqGBJWA2/ZYSdTZshS5kdgI/PB+yzeXm3qOg==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/button@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/button/-/button-1.1.0.tgz#04d8e850a76bb46d81c726da102cd30e6fc6f8f6"
+ integrity sha512-Lgox+98W595mSCYzc6UT3x0SJwJ3q8rIKbr60vQ4iGKNkA/PEIypidmkq9zwzNeHeoem5OQpW7t/de0EVtBg+A==
+ dependencies:
+ "@chakra-ui/spinner" "1.1.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/checkbox@1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/checkbox/-/checkbox-1.2.0.tgz#f64165134d1a85b51e5b4dacdc61bc4467c0a1cc"
+ integrity sha512-WE3JK6XKZxALiX4liDtPRbBnwfBZAEOo9PllrcfBRpjIY67+5R5pMXMUz2/fnyJ058UaNVyBvwQL2v7Z6OJSWg==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+ "@chakra-ui/visually-hidden" "1.0.3"
+
+"@chakra-ui/clickable@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/clickable/-/clickable-1.0.3.tgz#fdeade4954b9b4cc71dee2eb965308dd0922e862"
+ integrity sha512-61TKhyu8x/q5tOO5YEMcPqcoE/gGYvhZUyRkHkGtJzn1v50JvBiMNWJV94/KLZ5QEvL/QwJqLH123A+Lj484ag==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/close-button@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/close-button/-/close-button-1.1.0.tgz#75a0b9508253fc3e221c4ea399e12402850fcddb"
+ integrity sha512-8TN68/FpAnrIp4zDdCrK4p2RLreAbuoowrGGpNodCu9XkD9sxpB+tOg0hfKSbEJiXMW5Yz6sBMdvxL0D7ZD0Sg==
+ dependencies:
+ "@chakra-ui/icon" "1.1.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/color-mode@1.0.6":
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/color-mode/-/color-mode-1.0.6.tgz#8921b4b1b58a4e94dad74f94fdcc3f83180876f5"
+ integrity sha512-nxSHz/XrV3BL06bOdKnSWL5ydPWPebQ44AKCqx4N+JQ3bCfrsAhSpDT8DN5jEfawtnJCnEFqFRayYRgkbctN2Q==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/control-box@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/control-box/-/control-box-1.0.3.tgz#338782765b9ed584ebb99b6fdca5ded16d5ba9e8"
+ integrity sha512-ihhDS5DirjZqH7u621luufkq/j9FLXwdX70zJhXlEIqLPOTGRkETASN6l0A+lYvqz4fM1eIag1qeAFaPz72ejw==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/counter@1.0.6":
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/counter/-/counter-1.0.6.tgz#446538699f39abc17e3da71df3483e75495a186e"
+ integrity sha512-Knb0khasT1lDsH+R7d1kvvhrioYRgmxZ9pSyd5nh9ycqJOJgJRzH4fUIDyEtwheBPeGdugC1kqRCoASnJLUgHQ==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/css-reset@1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/css-reset/-/css-reset-1.0.0.tgz#8395921b35ef27bee0579a4d730c5ab7f7b39734"
+ integrity sha512-UaPsImGHvCgFO3ayp6Ugafu2/3/EG8wlW/8Y9Ihfk1UFv8cpV+3BfWKmuZ7IcmxcBL9dkP6E8p3/M1T0FB92hg==
+
+"@chakra-ui/descendant@1.0.6":
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/descendant/-/descendant-1.0.6.tgz#c21d66de97b3d8e1970a35e53acfd263ae6494e6"
+ integrity sha512-3DPEzHHSETVIJkbB7kt0dkXtzlCTFXw1wozdUAtaEJ0/tP6H58qQV577gglTRj9q6+CIFaB8rc9DeF+9I0SsLQ==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+
+"@chakra-ui/editable@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/editable/-/editable-1.1.0.tgz#5ee810af506aac09e21a6b3b2ea4714d4cc0e7f6"
+ integrity sha512-5eDT4+nRDJZvPzQNiFjoAZ//pcr8DCxmQ0uwN+DygYuSW7Eg34xER5sTYAM82OmiBfYzjeLpCX5LNH6/H6CucQ==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/focus-lock@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/focus-lock/-/focus-lock-1.0.3.tgz#22f1b8277bceaf1df43da1f5a0cc433368e475af"
+ integrity sha512-WxkQGAoqIutaDu10zl/22XGICRd5+cZpRzPeF9w8Hv031fHADDiEb5T7LreFvPCGu7bHy/LI0V/6RrMA1ACHnA==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+ react-focus-lock "2.4.1"
+
+"@chakra-ui/form-control@1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/form-control/-/form-control-1.2.0.tgz#6a81abb1f11f0bc3c3e68f05d4fa48e2a968d85a"
+ integrity sha512-kBYHHHZ4xJY5fezoYDCYHCcNmcntsC3Lkdis+7JanR5Dnv2478zDGyQ0brFOuWbRHS9VfNJEMnAMYUXaYklcjQ==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/icon" "1.1.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/hooks@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/hooks/-/hooks-1.1.3.tgz#684cf49db8db90b4b262ac737710f28d2a80ad18"
+ integrity sha512-ycyvojiJboNwgTF2+IBUO8N4ctv8m3jfsho+hfpBtFUrJxB8C+3aZXbldRM77G/6fMTFfSrQM/P5ppgSIWT5gg==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+ compute-scroll-into-view "1.0.14"
+ copy-to-clipboard "3.3.1"
+
+"@chakra-ui/icon@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/icon/-/icon-1.1.0.tgz#8816535a1e0e833112bd1c3067479969dc40e652"
+ integrity sha512-OvO0auLIvL39nTf8IW2wnu0x/9whezRqazUHVBJnSsgkpatChhz3RfqzErGmWaOnTyw7omTt6PFfD1YgY40cIQ==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/icons@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/icons/-/icons-1.0.4.tgz#f1c6cb4a7f65e9b1fa933a57ca682cc9a61c91d9"
+ integrity sha512-bFbyXGz+41Sf2/hcyOZbba1MXfYSrwdr2ph1r/qtTWrl0FPFpg5yPC9zPP6wtdf3+MtumbK9SxMyYneZ8KKJRA==
+ dependencies:
+ "@chakra-ui/icon" "1.1.0"
+ "@types/react" "^17.0.0"
+
+"@chakra-ui/image@1.0.6":
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/image/-/image-1.0.6.tgz#99fbdc21a589d2599e7066e78f27efe51a6e57d4"
+ integrity sha512-7kFv51T9fxCyCDStj/GuyqrJMUOyNYsYiLR5FL98MlMBBb+3R+0HagXa/RM/QPvEG890lpXRSJePrm4GwODElg==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/input@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/input/-/input-1.1.0.tgz#0e73f8ee41a52d8bf446cd4c3479faac78755554"
+ integrity sha512-T7o5zVOyC7pe9102PYQ2vkYhX0h+47u9HRW5hjntABpOBQiM3eqcXLyxeymZgaUiLsnZuog+9pdp2Nae6VvojA==
+ dependencies:
+ "@chakra-ui/form-control" "1.2.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/layout@1.3.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/layout/-/layout-1.3.0.tgz#b8c893c60a4acfe3e8b5ad135455059c36eddc10"
+ integrity sha512-4IrMvVPrW8ua32uRzp5UUmHfYa/+K4E4zP6191pr1TRJifX4Dii/FGDPlK0GFFsXxennyiLFbrdp6EkbjX0xXQ==
+ dependencies:
+ "@chakra-ui/icon" "1.1.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/live-region@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/live-region/-/live-region-1.0.3.tgz#80f99783043606fbe71258e278805dbef62391ed"
+ integrity sha512-dYZm1o7JV1YGhgY90sLm8dSjWH/j5M14M4swXdSPx85Fc9gdG3QShzobJnCK21dl1CrERNksG8J1GsHQTqBFOw==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/media-query@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/media-query/-/media-query-1.0.4.tgz#689728e811559d6cac40bb5a739afd3e87b2dee1"
+ integrity sha512-ZhHcWubwaIgkUi6Tl5KeepOfZ83xXp0d5Sz4pXWyAP/9jfDmxk8s8qrMXMh+19EOBfm2AvfeCIj9+23/ZpXJUg==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/menu@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/menu/-/menu-1.1.0.tgz#229f44aa492f3e8eb0a7f42616cd79959d39c8f8"
+ integrity sha512-N1d0kNA6BzOHWu8IaqHcXyEchzclU2tuoh10TaVZfZt3WECQo10IXtLE9e6xZ+x6BLjFleVXnupNVPHX26tI9g==
+ dependencies:
+ "@chakra-ui/clickable" "1.0.3"
+ "@chakra-ui/descendant" "1.0.6"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/popper" "1.1.2"
+ "@chakra-ui/transition" "1.0.7"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/modal@1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/modal/-/modal-1.5.0.tgz#f3786f4c043b97d48b2c69975d0d0c3dde6f371e"
+ integrity sha512-CBtMon/PpMv9eUT3sVuSImd3vW1yCiatMaU4Ve4v1s1TWMAIQuvBBUzhv1kycFiVG+zFo5+XhJLF3G381lRTpQ==
+ dependencies:
+ "@chakra-ui/close-button" "1.1.0"
+ "@chakra-ui/focus-lock" "1.0.3"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/portal" "1.1.0"
+ "@chakra-ui/transition" "1.0.7"
+ "@chakra-ui/utils" "1.1.0"
+ aria-hidden "^1.1.1"
+ react-remove-scroll "2.4.0"
+
+"@chakra-ui/number-input@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/number-input/-/number-input-1.1.0.tgz#27b3929432601d5b1958efa3ebac8959611e7171"
+ integrity sha512-uLuEjP7DraVKWPqJcG3iThg8yJhOEjbhxF3U7jnqKjgAZlKMHjPqYNyR+FPbBW1VCPYOpxqQXlpzAJxLuc/pcQ==
+ dependencies:
+ "@chakra-ui/counter" "1.0.6"
+ "@chakra-ui/form-control" "1.2.0"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/icon" "1.1.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/pin-input@1.3.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/pin-input/-/pin-input-1.3.0.tgz#52a184396aeeeb3c4e474745ecd28df05cd7379d"
+ integrity sha512-UGhr1C02q7bZWMkc0PxCDcMQkomlLtxygB20w0tqD8GKZCcqFaRHI7EpkUFsBIpVcadhsA/ZVAmp0r2+eYr4mQ==
+ dependencies:
+ "@chakra-ui/descendant" "1.0.6"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/popover@1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/popover/-/popover-1.2.0.tgz#c8ec2b397c5192859bb3c608e48017ffd5a1b9f0"
+ integrity sha512-e8Zhy0YqQKXhS2wYFpVH6EZ7qhibb99oOPQzejcnUJ0mXTp3QaFlVDQRW//hivhckNbJH6d6N8pYkaIxYulOmQ==
+ dependencies:
+ "@chakra-ui/close-button" "1.1.0"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/popper" "1.1.2"
+ "@chakra-ui/portal" "1.1.0"
+ "@chakra-ui/transition" "1.0.7"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/popper@1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/popper/-/popper-1.1.2.tgz#a46a3e56b6ddaa00197d93ce0b4a540c9d8a23da"
+ integrity sha512-8ZPMXUjNdVgqntpWc8sh8zNflko16tiJCLRcblm7MP7oxqqYFKO+2mnq+xRh/Afl8qSdbDh1F2S3mkYQP1AdQw==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+ "@popperjs/core" "2.4.4"
+ dequal "2.0.2"
+
+"@chakra-ui/portal@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/portal/-/portal-1.1.0.tgz#1fa653a95c7a139556ad22296b89875e73f5db9d"
+ integrity sha512-Zy4+zRXZr7ArmifWAVxS5VmPlzafmI8EjUuRZPNUzzRhCJ0aiGiIO/9VGcMQapN3Z6083C8ajdw3dGZEwIK0Mw==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/progress@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/progress/-/progress-1.1.0.tgz#cabfde1186a5afbe6a1207d9c5a24b9bfd86155c"
+ integrity sha512-GXy3OSLO1jnbrp2VcPkT2+2XFPyYfxuNSleQDe6fLcIHuJIY5T8AxfW9u8/bWsEdunLoUdo2YKrovb6XtwNP8Q==
+ dependencies:
+ "@chakra-ui/theme-tools" "1.0.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/radio@1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/radio/-/radio-1.2.0.tgz#30ee08f6bc9786802d82d1bf6f99cac62ddf1c7f"
+ integrity sha512-q3oa6EAezunT6pe3oooAB2CsPQONXbueffkaiO7GZgNM+msowrlrIO8CWBDSXfJUaKQjrdhJD/HNgfJfWC+6zA==
+ dependencies:
+ "@chakra-ui/form-control" "1.2.0"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+ "@chakra-ui/visually-hidden" "1.0.3"
+
+"@chakra-ui/react@1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/react/-/react-1.2.1.tgz#4a799bc68b341f6cb1cae984736d19831263a70e"
+ integrity sha512-f92nEpuddcXtgoFhNwmwcAZYMlLStnF4A+Pyu70bXkONKspA1zJnmT8RXYtzIksDQ00fwymqD3SjwVqo85JA3A==
+ dependencies:
+ "@chakra-ui/accordion" "1.1.0"
+ "@chakra-ui/alert" "1.1.0"
+ "@chakra-ui/avatar" "1.1.0"
+ "@chakra-ui/breadcrumb" "1.1.0"
+ "@chakra-ui/button" "1.1.0"
+ "@chakra-ui/checkbox" "1.2.0"
+ "@chakra-ui/close-button" "1.1.0"
+ "@chakra-ui/control-box" "1.0.3"
+ "@chakra-ui/counter" "1.0.6"
+ "@chakra-ui/css-reset" "1.0.0"
+ "@chakra-ui/editable" "1.1.0"
+ "@chakra-ui/form-control" "1.2.0"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/icon" "1.1.0"
+ "@chakra-ui/image" "1.0.6"
+ "@chakra-ui/input" "1.1.0"
+ "@chakra-ui/layout" "1.3.0"
+ "@chakra-ui/live-region" "1.0.3"
+ "@chakra-ui/media-query" "1.0.4"
+ "@chakra-ui/menu" "1.1.0"
+ "@chakra-ui/modal" "1.5.0"
+ "@chakra-ui/number-input" "1.1.0"
+ "@chakra-ui/pin-input" "1.3.0"
+ "@chakra-ui/popover" "1.2.0"
+ "@chakra-ui/popper" "1.1.2"
+ "@chakra-ui/portal" "1.1.0"
+ "@chakra-ui/progress" "1.1.0"
+ "@chakra-ui/radio" "1.2.0"
+ "@chakra-ui/select" "1.1.0"
+ "@chakra-ui/skeleton" "1.1.1"
+ "@chakra-ui/slider" "1.1.0"
+ "@chakra-ui/spinner" "1.1.0"
+ "@chakra-ui/stat" "1.1.0"
+ "@chakra-ui/switch" "1.1.0"
+ "@chakra-ui/system" "1.2.1"
+ "@chakra-ui/table" "1.1.0"
+ "@chakra-ui/tabs" "1.1.0"
+ "@chakra-ui/tag" "1.1.0"
+ "@chakra-ui/textarea" "1.1.0"
+ "@chakra-ui/theme" "1.5.0"
+ "@chakra-ui/toast" "1.1.8"
+ "@chakra-ui/tooltip" "1.1.0"
+ "@chakra-ui/transition" "1.0.7"
+ "@chakra-ui/utils" "1.1.0"
+ "@chakra-ui/visually-hidden" "1.0.3"
+
+"@chakra-ui/select@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/select/-/select-1.1.0.tgz#e3965ef202624d6a018423a12ee30b08743a826b"
+ integrity sha512-cHgqgvEZ1ejAPiEiMptpTrgFphtiGlEIOio9zGzP5UKvtaUn0ReBnXxNvfuORmvueqlJ8C38D51Yh8dPRg9RiA==
+ dependencies:
+ "@chakra-ui/form-control" "1.2.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/skeleton@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/skeleton/-/skeleton-1.1.1.tgz#4e0c30f48ea75b8b8a316a546349828b5c600f79"
+ integrity sha512-rRmgnIUCSSEIxMbLXSyBrrXy9t/2IdcKGqHaxiAk+rcd07dHFVGEZR+ftBBJGNmxyGJyV1xXP6RGysIH1V9/zQ==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/media-query" "1.0.4"
+ "@chakra-ui/system" "1.2.1"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/slider@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/slider/-/slider-1.1.0.tgz#0fe2e4d36b1f8c605d224e31fdacb158d24cff9b"
+ integrity sha512-LAFjD9OH1J5oomuQZcHgGHduxJV/mVApXbfXRvBw5CcgD+woa8M8oqYjTNXdRJTj/ja02jX7yDvEjqYgh9UKlQ==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/spinner@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/spinner/-/spinner-1.1.0.tgz#c792df2e8e31d2ef566c0c7bddc5f4b0deb869b5"
+ integrity sha512-pA31As8tG2uFS6GNXXGWqfZvEJjhDneXxVVJH1uOCj3a3BDlhzrn5nft7Ni6u3hlhA7vd4g8kW+rlNpvtLwTHQ==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+ "@chakra-ui/visually-hidden" "1.0.3"
+
+"@chakra-ui/stat@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/stat/-/stat-1.1.0.tgz#a23419b022beace75af578144337a4a98b1d32ae"
+ integrity sha512-GeRrPIpQdX1y1X9p4Vip2/h29FBVAgRbye/6Kw8fYKPtLKri8UjgHeUZOToCEwMfKhw7Ivu9EmRvUAQLO+eZEA==
+ dependencies:
+ "@chakra-ui/icon" "1.1.0"
+ "@chakra-ui/utils" "1.1.0"
+ "@chakra-ui/visually-hidden" "1.0.3"
+
+"@chakra-ui/styled-system@1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/styled-system/-/styled-system-1.6.0.tgz#7e4e1dd9a9083c8d4bab016a0c4dc1a40b136358"
+ integrity sha512-ZzrBkMMQ7cpSPZiSAJCQRsJTiUjKHKXomryHzRiWzO77577l2D7Wn2vY4RHHdRiUrOsWdQ4gg3Pm3LL1iTH07g==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+ css-get-unit "1.0.1"
+ csstype "^3.0.6"
+
+"@chakra-ui/switch@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/switch/-/switch-1.1.0.tgz#f0a3e4a019816f866b8404ff4fbef65156d4ae46"
+ integrity sha512-I8iQSw8RkfqCZPHq83gEyHdsfifHRKHYXlSKxtsTM09VnHgIKA62LYEuOu0FCfElNsjDyicrct4D59RrqjO6Hg==
+ dependencies:
+ "@chakra-ui/checkbox" "1.2.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/system@1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/system/-/system-1.2.1.tgz#9dfc01b16c4e86eb9c0b56e87e77d7185b882ab9"
+ integrity sha512-s992HSyUHwJ2rLFEXMwqHgYPzDXLAz+C6S3KPfhLh9hCij9WCLj+501QndzbqO1/jl7eJpxk1jztRLJkPGKUlQ==
+ dependencies:
+ "@chakra-ui/color-mode" "1.0.6"
+ "@chakra-ui/styled-system" "1.6.0"
+ "@chakra-ui/utils" "1.1.0"
+ react-fast-compare "3.2.0"
+
+"@chakra-ui/table@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/table/-/table-1.1.0.tgz#90ac26cf7e1d85ab10e1e07a1cfde6e33f9f24c1"
+ integrity sha512-JHJ7+a83ZdUB1BwnA3A8dqAP1e4WMNSmEOPlEoDspYy1VNuA9rxKWLLPBSaxyNo9gtiOQ7XOV3nwuByJsMUJdA==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/tabs@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/tabs/-/tabs-1.1.0.tgz#f38c1fea3220cfe3f4ca17d1f823f62d855fb5ad"
+ integrity sha512-wSAzyffKaj242GQISTlrbu+dXh8358sh8ZRhGGkBjvTXE4NbxuuFe21+KIt1mArFuxHKb6SPyziiD+QxN5klRA==
+ dependencies:
+ "@chakra-ui/clickable" "1.0.3"
+ "@chakra-ui/descendant" "1.0.6"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/tag@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/tag/-/tag-1.1.0.tgz#3f3b25e86d4da12df5909d430252301733a9ea45"
+ integrity sha512-7BnQG/qYwqAoKOIMgKc7aivHEbmKgqia2fxc9e68+Zd8v4P/BrEf1Ur2/K+VbOA9KO3HZvBKHH337lgeOPD6LQ==
+ dependencies:
+ "@chakra-ui/icon" "1.1.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/textarea@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/textarea/-/textarea-1.1.0.tgz#e3fed6255421790d7a6fd8b0aff854ec9129e28a"
+ integrity sha512-hQtxGkcNrpEv+uP1JcL67gMDT8FkiSrxBXQH9jDVEj3dRi0IBuGKyuBefh4jq5eyN8VPQa5dBIAKDuVZ0Q4j1w==
+ dependencies:
+ "@chakra-ui/form-control" "1.2.0"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/theme-tools@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/theme-tools/-/theme-tools-1.0.3.tgz#a741566e48f6526afa653c0aec578b4bf477efa6"
+ integrity sha512-ajI/vJkaoPykNXYqlw9xA0eHN1dI4Tnezd2F0a6/WjAXVjZNV/0IUnh437o3yHqP0WSwp4uMfXseZUaNmIj75A==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+ "@types/tinycolor2" "1.4.2"
+ tinycolor2 "1.4.2"
+
+"@chakra-ui/theme@1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/theme/-/theme-1.5.0.tgz#95554908b6c8031065be4f6bba079904a5c11433"
+ integrity sha512-imaFsQGNAB3nKJRvi8fGJiVnXkNtwtV66kCeMU4iLvHyIzT5UqrJ6TMrZpDEPwijDULvDEbxfOUQ5OFVbhN2ww==
+ dependencies:
+ "@chakra-ui/theme-tools" "1.0.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/toast@1.1.8":
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/toast/-/toast-1.1.8.tgz#fcca52eb21135811eaeb37263f42fa0d11d06aa2"
+ integrity sha512-2mNK8hUpXIKCBkSKtkhMnUgMb3nOvdH6TTSeDWFOavuPhgRR5HLlqbo3KsxDdwcctA0gOWXbAxzSq7A0kEWwgA==
+ dependencies:
+ "@chakra-ui/alert" "1.1.0"
+ "@chakra-ui/close-button" "1.1.0"
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/theme" "1.5.0"
+ "@chakra-ui/transition" "1.0.7"
+ "@chakra-ui/utils" "1.1.0"
+ "@reach/alert" "0.11.0"
+
+"@chakra-ui/tooltip@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/tooltip/-/tooltip-1.1.0.tgz#123d206a88854f388ed63a6e0ff8cbb418f9002e"
+ integrity sha512-6tplYBJsURpZuzNagCIHdDjZiEpuGD3KUpSzK2hDjUgzh74ECBsdphYpcLCOsDB2F+l7d+8VFcYu67p/ytqwfQ==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/popper" "1.1.2"
+ "@chakra-ui/portal" "1.1.0"
+ "@chakra-ui/utils" "1.1.0"
+ "@chakra-ui/visually-hidden" "1.0.3"
+
+"@chakra-ui/transition@1.0.7":
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/transition/-/transition-1.0.7.tgz#c6ed7e62c4b72062743347db6ff4be6d664008b3"
+ integrity sha512-nADY7JpXB/YxnnIelVdo0mNZDO8oUYINh16FecDWfrK4NHKL5EYZTyViNf+N7D4qk6XcMZkjbQwnCau3h6YwVQ==
+ dependencies:
+ "@chakra-ui/hooks" "1.1.3"
+ "@chakra-ui/utils" "1.1.0"
+
+"@chakra-ui/utils@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/utils/-/utils-1.1.0.tgz#e006d17ffc1b09c2daa08f94fbc58766c4329e76"
+ integrity sha512-QiX6HqQpv+oE4lRaPhDYRhrYI2ijAsNDpKhZeOeUh6hBXWIayzMB62iN0QANeMqynPz413TU5RR2c+WGwyax6g==
+ dependencies:
+ "@types/lodash.mergewith" "4.6.6"
+ "@types/object-assign" "4.0.30"
+ css-box-model "1.2.1"
+ lodash.mergewith "4.6.2"
+ object-assign "4.1.1"
+
+"@chakra-ui/visually-hidden@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@chakra-ui/visually-hidden/-/visually-hidden-1.0.3.tgz#b38987f18d72073577e278387f6a9eeb25f10ff8"
+ integrity sha512-OTmqXgw/jXXhRhzyhA21AgDLdgcAZhtzg9ZHU6SpLprZxi2L+nf0VQ8y0vD4YpS5VHf+ZMy5W2GIXbuJRhjePg==
+ dependencies:
+ "@chakra-ui/utils" "1.1.0"
+
+"@emotion/babel-plugin@11.1.2", "@emotion/babel-plugin@^11.0.0":
+ version "11.1.2"
+ resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.1.2.tgz#68fe1aa3130099161036858c64ee92056c6730b7"
+ integrity sha512-Nz1k7b11dWw8Nw4Z1R99A9mlB6C6rRsCtZnwNUOj4NsoZdrO2f2A/83ST7htJORD5zpOiLKY59aJN23092949w==
+ dependencies:
+ "@babel/helper-module-imports" "^7.7.0"
+ "@babel/plugin-syntax-jsx" "^7.12.1"
+ "@babel/runtime" "^7.7.2"
+ "@emotion/hash" "^0.8.0"
+ "@emotion/memoize" "^0.7.5"
+ "@emotion/serialize" "^1.0.0"
+ babel-plugin-macros "^2.6.1"
+ convert-source-map "^1.5.0"
+ escape-string-regexp "^4.0.0"
+ find-root "^1.1.0"
+ source-map "^0.5.7"
+ stylis "^4.0.3"
+
+"@emotion/cache@^11.0.0", "@emotion/cache@^11.1.3":
+ version "11.1.3"
+ resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.1.3.tgz#c7683a9484bcd38d5562f2b9947873cf66829afd"
+ integrity sha512-n4OWinUPJVaP6fXxWZD9OUeQ0lY7DvtmtSuqtRWT0Ofo/sBLCVSgb4/Oa0Q5eFxcwablRKjUXqXtNZVyEwCAuA==
+ dependencies:
+ "@emotion/memoize" "^0.7.4"
+ "@emotion/sheet" "^1.0.0"
+ "@emotion/utils" "^1.0.0"
+ "@emotion/weak-memoize" "^0.2.5"
+ stylis "^4.0.3"
+
+"@emotion/css@^11.0.0":
+ version "11.1.3"
+ resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.1.3.tgz#9ed44478b19e5d281ccbbd46d74d123d59be793f"
+ integrity sha512-RSQP59qtCNTf5NWD6xM08xsQdCZmVYnX/panPYvB6LQAPKQB6GL49Njf0EMbS3CyDtrlWsBcmqBtysFvfWT3rA==
+ dependencies:
+ "@emotion/babel-plugin" "^11.0.0"
+ "@emotion/cache" "^11.1.3"
+ "@emotion/serialize" "^1.0.0"
+ "@emotion/sheet" "^1.0.0"
+ "@emotion/utils" "^1.0.0"
+
+"@emotion/hash@^0.8.0":
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
+ integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
+
+"@emotion/is-prop-valid@^0.8.2":
+ version "0.8.8"
+ resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
+ integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
+ dependencies:
+ "@emotion/memoize" "0.7.4"
+
+"@emotion/is-prop-valid@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.0.0.tgz#1dbe82e52a12c065d416a702e2d106e552cde5be"
+ integrity sha512-G5X0t7eR9pkhUvAY32QS3lToP9JyNF8It5CcmMvbWjmC9/Yq7IhevaKqxl+me2BKR93iTPiL/h3En1ZX/1G3PQ==
+ dependencies:
+ "@emotion/memoize" "^0.7.4"
+
+"@emotion/memoize@0.7.4":
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
+ integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
+
+"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5":
+ version "0.7.5"
+ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50"
+ integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
+
+"@emotion/react@11.1.4", "@emotion/react@^11.1.1":
+ version "11.1.4"
+ resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.1.4.tgz#ddee4247627ff7dd7d0c6ae52f1cfd6b420357d2"
+ integrity sha512-9gkhrW8UjV4IGRnEe4/aGPkUxoGS23aD9Vu6JCGfEDyBYL+nGkkRBoMFGAzCT9qFdyUvQp4UUtErbKWxq/JS4A==
+ dependencies:
+ "@babel/runtime" "^7.7.2"
+ "@emotion/cache" "^11.1.3"
+ "@emotion/serialize" "^1.0.0"
+ "@emotion/sheet" "^1.0.1"
+ "@emotion/utils" "^1.0.0"
+ "@emotion/weak-memoize" "^0.2.5"
+ hoist-non-react-statics "^3.3.1"
+
+"@emotion/serialize@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.0.tgz#1a61f4f037cf39995c97fc80ebe99abc7b191ca9"
+ integrity sha512-zt1gm4rhdo5Sry8QpCOpopIUIKU+mUSpV9WNmFILUraatm5dttNEaYzUWWSboSMUE6PtN2j1cAsuvcugfdI3mw==
+ dependencies:
+ "@emotion/hash" "^0.8.0"
+ "@emotion/memoize" "^0.7.4"
+ "@emotion/unitless" "^0.7.5"
+ "@emotion/utils" "^1.0.0"
+ csstype "^3.0.2"
+
+"@emotion/sheet@^1.0.0", "@emotion/sheet@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.1.tgz#245f54abb02dfd82326e28689f34c27aa9b2a698"
+ integrity sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g==
+
+"@emotion/styled@11.0.0":
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.0.0.tgz#698196c2822746360a8644a73a5d842b2d1a78a5"
+ integrity sha512-498laccxJlBiJqrr2r/fx9q+Pr55D0URP2UyOkoSGLjevb8LLAFWueqthsQ5XijE66iGo7y3rzzEYdA7CHmZEQ==
+ dependencies:
+ "@babel/runtime" "^7.7.2"
+ "@emotion/babel-plugin" "^11.0.0"
+ "@emotion/is-prop-valid" "^1.0.0"
+ "@emotion/serialize" "^1.0.0"
+ "@emotion/utils" "^1.0.0"
+
+"@emotion/unitless@^0.7.5":
+ version "0.7.5"
+ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
+ integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
+
+"@emotion/utils@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af"
+ integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==
+
+"@emotion/weak-memoize@^0.2.5":
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
+ integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
+
+"@fortawesome/fontawesome-common-types@^0.2.34":
+ version "0.2.34"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.34.tgz#0a8c348bb23b7b760030f5b1d912e582be4ec915"
+ integrity sha512-XcIn3iYbTEzGIxD0/dY5+4f019jIcEIWBiHc3KrmK/ROahwxmZ/s+tdj97p/5K0klz4zZUiMfUlYP0ajhSJjmA==
+
+"@fortawesome/fontawesome-svg-core@1.2.34":
+ version "1.2.34"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.34.tgz#1d1a7c92537cbc2b8a83eef6b6d824b4b5b46b26"
+ integrity sha512-0KNN0nc5eIzaJxlv43QcDmTkDY1CqeN6J7OCGSs+fwGPdtv0yOQqRjieopBCmw+yd7uD3N2HeNL3Zm5isDleLg==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "^0.2.34"
+
+"@fortawesome/free-brands-svg-icons@5.15.2":
+ version "5.15.2"
+ resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.2.tgz#d74e2540b5552b915d6bef719f17e361b70a8d65"
+ integrity sha512-YPlVjE1cEO+OJ9I9ay3TQ3I88+XkxMTYwnnddqAboxLhPNGncsHV0DjWOVLCyuAY66yPfyndWwVn4v7vuqsO1g==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "^0.2.34"
+
+"@fortawesome/free-solid-svg-icons@5.15.2":
+ version "5.15.2"
+ resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.2.tgz#25bb035de57cf85aee8072965732368ccc8e8943"
+ integrity sha512-ZfCU+QjaFsdNZmOGmfqEWhzI3JOe37x5dF4kz9GeXvKn/sTxhqMtZ7mh3lBf76SvcYY5/GKFuyG7p1r4iWMQqw==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "^0.2.34"
+
+"@fortawesome/react-fontawesome@0.1.14":
+ version "0.1.14"
+ resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz#bf28875c3935b69ce2dc620e1060b217a47f64ca"
+ integrity sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==
+ dependencies:
+ prop-types "^15.7.2"
+
"@hapi/accept@5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.1.tgz#068553e867f0f63225a506ed74e899441af53e10"
@@ -154,31 +856,229 @@
resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.14.0.tgz#c67fc20a4d891447ca1a855d7d70fa79a3533001"
integrity sha512-sDOAZcYwynHFTbLo6n8kIbLiVF3a3BLkrmehJUyEbT9F+Smbi47kLGS2gG2g0fjBLR/Lr1InPD7kXL7FaTqEkw==
-"@types/node@^12.12.21":
- version "12.19.15"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.15.tgz#0de7e978fb43db62da369db18ea088a63673c182"
- integrity sha512-lowukE3GUI+VSYSu6VcBXl14d61Rp5hA1D+61r16qnwC0lYNSqdxcvRh0pswejorHfS+HgwBasM8jLXz0/aOsw==
+"@popperjs/core@2.4.4":
+ version "2.4.4"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.4.tgz#11d5db19bd178936ec89cd84519c4de439574398"
+ integrity sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==
+
+"@reach/alert@0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@reach/alert/-/alert-0.11.0.tgz#34bea3e9e286fa54a86adaa1a3730faff9b2bda3"
+ integrity sha512-7Rw+lrrIOhgNTVmk8YZsqoF+fyOiA+kJx23p9/FZq+d0MK28e6puUA1zpeWASdU1LDzS+vDJM5hUmj98NMM/nw==
+ dependencies:
+ "@reach/utils" "0.11.0"
+ "@reach/visually-hidden" "0.11.0"
+ prop-types "^15.7.2"
+ tslib "^2.0.0"
+
+"@reach/utils@0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.11.0.tgz#09ad5e1f42b498253df8a6a4c99ccd0ab2d85464"
+ integrity sha512-A7Ofr1Biq4vUeTBYhbZ/YiLq1B/lEObbEoR2UiuQqCO1r093N95hZNcKqfFwpkRScjD87uob3wSYYGxvq9y/+w==
+ dependencies:
+ "@types/warning" "^3.0.0"
+ tslib "^2.0.0"
+ warning "^4.0.3"
+
+"@reach/visually-hidden@0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.11.0.tgz#a7cd68ea2a197238dfcffbef8723db9117d31ebb"
+ integrity sha512-O67fK7jz01TYu/V57RiDsxKY29ReHdQkpq+OV0ijmXsv7g5r3Nys51Ry+IqPrJst4Ve5xxFbiJsTt/bGwxorrQ==
+ dependencies:
+ tslib "^2.0.0"
+
+"@types/base16@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@types/base16/-/base16-1.0.2.tgz#eb3a07db52309bfefb9ba010dfdb3c0784971f65"
+ integrity sha512-oYO/U4VD1DavwrKuCSQWdLG+5K22SLPem2OQaHmFcQuwHoVeGC+JGVRji2MUqZUAIQZHEonOeVfAX09hYiLsdg==
+
+"@types/classnames@2.2.11":
+ version "2.2.11"
+ resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.11.tgz#2521cc86f69d15c5b90664e4829d84566052c1cf"
+ integrity sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==
+
+"@types/lodash.capitalize@4.2.6":
+ version "4.2.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.capitalize/-/lodash.capitalize-4.2.6.tgz#261a1c0151872c2eab068b78d9bcd33305a76f92"
+ integrity sha512-PgarsttqczpGoJz1fxTTofVkFrQQAVH85bbF6rvXOAEd0PV9/KJtYClNGEi1xSSpyzngMySQhWXu0ILsSsB42g==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.clonedeep@4.5.6":
+ version "4.5.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b"
+ integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.curry@^4.1.6":
+ version "4.1.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.curry/-/lodash.curry-4.1.6.tgz#f26c490c80c92d7cbaa2300d542e89781d44b1ff"
+ integrity sha512-x3ctCcmOYqRrihNNnQJW6fe/yZFCgnrIa6p80AiPQRO8Jis29bBdy1dEw1FwngoF/mCZa3Bx+33fUZvOEE635Q==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.debounce@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60"
+ integrity sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.filter@4.6.6":
+ version "4.6.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.filter/-/lodash.filter-4.6.6.tgz#34d93d3d40b2585384a12093eb4bdf2a26cbb829"
+ integrity sha512-K9oEglaInmu7pnQnZYdciNePpKe0W7O9yssnCza9mLjpq5N5Ju8RIMwTvp9tovb8V5yemxFTJqHzG+tIkAl1xw==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.includes@4.3.6":
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.includes/-/lodash.includes-4.3.6.tgz#47a806f9dd6ad5ebe35cfd46096db348420c3fb0"
+ integrity sha512-YzujoN0s409VTzdsQc7JYa7jHGV9YuYhGWIhzuEaZNSCKFuJD636tghbjq7R9xdjjbTnmiDeV/DnCurVSxVVxQ==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.isequal@4.5.5":
+ version "4.5.5"
+ resolved "https://registry.yarnpkg.com/@types/lodash.isequal/-/lodash.isequal-4.5.5.tgz#4fed1b1b00bef79e305de0352d797e9bb816c8ff"
+ integrity sha512-4IKbinG7MGP131wRfceK6W4E/Qt3qssEFLF30LnJbjYiSfHGGRU/Io8YxXrZX109ir+iDETC8hw8QsDijukUVg==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.merge@4.6.6":
+ version "4.6.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.merge/-/lodash.merge-4.6.6.tgz#b84b403c1d31bc42d51772d1cd5557fa008cd3d6"
+ integrity sha512-IB90krzMf7YpfgP3u/EvZEdXVvm4e3gJbUvh5ieuI+o+XqiNEt6fCzqNRaiLlPVScLI59RxIGZMQ3+Ko/DJ8vQ==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.mergewith@4.6.6":
+ version "4.6.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz#c4698f5b214a433ff35cb2c75ee6ec7f99d79f10"
+ integrity sha512-RY/8IaVENjG19rxTZu9Nukqh0W2UrYgmBj5sdns4hWRZaV8PqR7wIKHFKzvOTjo4zVRV7sVI+yFhAJql12Kfqg==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.now@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.now/-/lodash.now-4.0.6.tgz#b6895297d0d9b027b9717e608dc8c3ea781eda5a"
+ integrity sha512-7BMqeFp2RMmVUC5weEl+4y4ru8b/2HNtXx9mMAbOAzEpj+N+K73RY34H6PnbrKh/JQrWOMYfC2frOn0Yc1mDcA==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.remove@4.7.6":
+ version "4.7.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.remove/-/lodash.remove-4.7.6.tgz#c9ab9a4ab1b8f6af60345224418255c9061b5589"
+ integrity sha512-RPZHKzbDu5vqdySn4LwYcg31tdGA0eoRiggzUgHGofRtm2VDp/junkHSmfkExZzmrSFfoqpXawE+ojUuN78YLw==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.size@4.2.6":
+ version "4.2.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.size/-/lodash.size-4.2.6.tgz#3503299c541e5f967be274e2d97ee831de29d3af"
+ integrity sha512-IY1r3hxyqxg0cclYIpygQDV61hFsyTR1904FxTj9kDAAIuOsftE88vNHUv3WLOApsWZMSlyiMJtH6928NHfN+Q==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.some@4.6.6":
+ version "4.6.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.some/-/lodash.some-4.6.6.tgz#6b79f350f7031aee8d93edde3e5b5e8147d9b74b"
+ integrity sha512-7MNJlK+WU2udp6QhxWDp3e05cjYq1v34JMpVVucal7XcZSlC1mZS3Gvdo/z/rL9CZgXXhoGvXerkfT+B8AfZEg==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash.sortby@4.7.6":
+ version "4.7.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.sortby/-/lodash.sortby-4.7.6.tgz#eed689835f274b553db4ae16a4a23f58b79618a1"
+ integrity sha512-EnvAOmKvEg7gdYpYrS6+fVFPw5dL9rBnJi3vcKI7wqWQcLJVF/KRXK9dH29HjGNVvFUj0s9prRP3J8jEGnGKDw==
+ dependencies:
+ "@types/lodash" "*"
-"@types/prop-types@*":
+"@types/lodash@*":
+ version "4.14.168"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008"
+ integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==
+
+"@types/node@14.14.22":
+ version "14.14.22"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18"
+ integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==
+
+"@types/object-assign@4.0.30":
+ version "4.0.30"
+ resolved "https://registry.yarnpkg.com/@types/object-assign/-/object-assign-4.0.30.tgz#8949371d5a99f4381ee0f1df0a9b7a187e07e652"
+ integrity sha1-iUk3HVqZ9Dge4PHfCpt6GH4H5lI=
+
+"@types/parse-json@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
+ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
+
+"@types/prop-types@*", "@types/prop-types@^15.7.3":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
-"@types/react-dom@^16.9.4":
- version "16.9.10"
- resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f"
- integrity sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw==
+"@types/react-dom@*", "@types/react-dom@17.0.0":
+ version "17.0.0"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.0.tgz#b3b691eb956c4b3401777ee67b900cb28415d95a"
+ integrity sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react-select@4.0.12":
+ version "4.0.12"
+ resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-4.0.12.tgz#5dde0f1bbd000f7366d2dea225443a8ae0efda99"
+ integrity sha512-7AACEwGNafPDZ+l8oOp83Y6KAgBPKk8KlEZAvPYmfgvsm85g2wKZ6NAcBNXmFcHFG+tc1q7cDoNWayvEhbM+cA==
dependencies:
- "@types/react" "^16"
+ "@emotion/serialize" "^1.0.0"
+ "@types/react" "*"
+ "@types/react-dom" "*"
+ "@types/react-transition-group" "*"
-"@types/react@^16", "@types/react@^16.9.16":
- version "16.14.3"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.3.tgz#f5210f5deecf35d8794845549c93c2c3ad63aa9c"
- integrity sha512-zPrXn03hmPYqh9DznqSFQsoRtrQ4aHgnZDO+hMGvsE/PORvDTdJCHQ6XvJV31ic+0LzF73huPFXUb++W6Kri0Q==
+"@types/react-transition-group@*":
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
+ integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*", "@types/react@17.0.1", "@types/react@^17.0.0":
+ version "17.0.1"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.1.tgz#eb1f1407dea8da3bc741879c1192aa703ab9975b"
+ integrity sha512-w8t9f53B2ei4jeOqf/gxtc2Sswnc3LBK5s0DyJcg5xd10tMHXts2N31cKjWfH9IC/JvEPa/YF1U4YeP1t4R6HQ==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
+"@types/tinycolor2@1.4.2":
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf"
+ integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==
+
+"@types/uuid@8.3.0":
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
+ integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
+
+"@types/warning@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
+ integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=
+
+"@unly/utils@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@unly/utils/-/utils-1.0.3.tgz#6fd8e361dfb977f0cfdc9ce83d7f0719dd6ae3de"
+ integrity sha512-QTRknIDX56FvzGcIpBum5D/oRSlX3dkZ+l1op1jsFlYCTd925OGUb991V7zsFv3ePcqFfvfqfR5cNVv+w4JAOw==
+
+"@welldone-software/why-did-you-render@6.0.5":
+ version "6.0.5"
+ resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-6.0.5.tgz#a8df3509ed612770bb21b5fa0ad61634acc61f38"
+ integrity sha512-8fWib+bKoAmnJHZPU8/qpEXKG8piB3oaoKj78fXNAdYd3x8ryde1pC7D5tKff5Mart2iOJUvYNxxcYlbtT9tow==
+ dependencies:
+ lodash "^4"
+
abort-controller@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@@ -193,6 +1093,11 @@ agent-base@6:
dependencies:
debug "4"
+animate.css@4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/animate.css/-/animate.css-4.1.1.tgz#614ec5a81131d7e4dc362a58143f7406abd68075"
+ integrity sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==
+
anser@1.4.9:
version "1.4.9"
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760"
@@ -248,6 +1153,13 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"
+aria-hidden@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.1.2.tgz#5354315a29bffdaced3993fccd826817dc8c5272"
+ integrity sha512-WAMH9q3vRimVqP+B0q2eDvx7IPDoY17A2fWwj5atTA/zTYJCNcS6HJ5YErZ5FO3PUHhrV0y0yR1NA0dRNm913A==
+ dependencies:
+ tslib "^1.0.0"
+
array-flatten@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541"
@@ -276,11 +1188,25 @@ ast-types@0.13.2:
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48"
integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA==
+babel-plugin-macros@^2.6.1:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
+ integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
+ dependencies:
+ "@babel/runtime" "^7.7.2"
+ cosmiconfig "^6.0.0"
+ resolve "^1.12.0"
+
babel-plugin-syntax-jsx@6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=
+base16@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
+ integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=
+
base64-js@^1.0.2, base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -444,6 +1370,16 @@ bytes@3.1.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+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=
+
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001173, caniuse-lite@^1.0.30001179:
version "1.0.30001183"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001183.tgz#7a57ba9d6584119bb5f2bc76d3cc47ba9356b3e2"
@@ -494,7 +1430,7 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
inherits "^2.0.1"
safe-buffer "^5.0.1"
-classnames@2.2.6:
+classnames@2.2.6, classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@@ -536,7 +1472,7 @@ color-string@^1.5.4:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
-color@^3.1.3:
+color@^3.1.2, color@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
@@ -559,6 +1495,11 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
+compute-scroll-into-view@1.0.14:
+ version "1.0.14"
+ resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz#80e3ebb25d6aa89f42e533956cb4b16a04cfe759"
+ integrity sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ==
+
console-browserify@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
@@ -574,18 +1515,36 @@ constants-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
-convert-source-map@1.7.0:
+convert-source-map@1.7.0, convert-source-map@^1.5.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
dependencies:
safe-buffer "~5.1.1"
+copy-to-clipboard@3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
+ integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
+ dependencies:
+ toggle-selection "^1.0.6"
+
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+cosmiconfig@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
+ integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
+ dependencies:
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.1.0"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.7.2"
+
create-ecdh@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
@@ -641,6 +1600,18 @@ crypto-browserify@3.12.0, crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"
+css-box-model@1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
+ integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
+ dependencies:
+ tiny-invariant "^1.0.6"
+
+css-get-unit@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/css-get-unit/-/css-get-unit-1.0.1.tgz#e490b9e56b2cd20f903a22ccafb448382edf7976"
+ integrity sha1-5JC55Wss0g+QOiLMr7RIOC7feXY=
+
css.escape@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
@@ -678,11 +1649,23 @@ cssnano-simple@1.2.2:
cssnano-preset-simple "1.2.2"
postcss "^7.0.32"
-csstype@^3.0.2:
+csstype@^3.0.2, csstype@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef"
integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==
+"d3-path@1 - 2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-2.0.0.tgz#55d86ac131a0548adae241eebfb56b4582dd09d8"
+ integrity sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==
+
+d3-shape@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-2.0.0.tgz#2331b62fa784a2a1daac47a7233cfd69301381fd"
+ integrity sha512-djpGlA779ua+rImicYyyjnOjeubyhql1Jyn1HK0bTyawuH76UQRWXd+pftr67H6Fa8hSwetkgb/0id3agKWykw==
+ dependencies:
+ d3-path "1 - 2"
+
data-uri-to-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636"
@@ -709,11 +1692,28 @@ decompress-response@^6.0.0:
dependencies:
mimic-response "^3.1.0"
+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-diff@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-1.0.2.tgz#afd3d1f749115be965e89c63edc7abb1506b9c26"
+ integrity sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==
+
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+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"
+
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@@ -724,6 +1724,11 @@ depd@~1.1.2:
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
+dequal@2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
+ integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==
+
des.js@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
@@ -737,6 +1742,11 @@ detect-libc@^1.0.3:
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
+detect-node-es@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.0.0.tgz#c0318b9e539a5256ca780dd9575c9345af05b8ed"
+ integrity sha512-S4AHriUkTX9FoFvL4G8hXDcx6t3gp2HpfCza3Q0v6S78gul2hKWifLQbeW+ZF89+hSm2ZIc/uF3J97ZgytgTRg==
+
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -746,6 +1756,14 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
+dom-helpers@^5.0.1:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b"
+ integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==
+ dependencies:
+ "@babel/runtime" "^7.8.7"
+ csstype "^3.0.2"
+
dom-serializer@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.1.0.tgz#5f7c828f1bfc44887dc2a315ab5c45691d544b58"
@@ -811,6 +1829,16 @@ electron-to-chromium@^1.3.634:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.652.tgz#9465d884d609acffd131ba71096de7bfabd63670"
integrity sha512-85J5D0Ksxjq2MIHfgwOURRej72UMlexbaa7t+oKTJan3Pa/RBE8vJ4/JzwaQjLCElPvd0XeLWi7+xYTVrq96aA==
+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.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/ellipsize/-/ellipsize-0.1.0.tgz#9d43682d44b91ad16ebd84268ac103170a6553f8"
+ integrity sha1-nUNoLUS5GtFuvYQmisEDFwplU/g=
+
elliptic@^6.5.3:
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
@@ -841,6 +1869,13 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+error-ex@^1.3.1:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
+ integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
+ dependencies:
+ is-arrayish "^0.2.1"
+
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -851,6 +1886,11 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@@ -879,11 +1919,21 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
+exenv@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
+ integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
+
expand-template@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
+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=
+
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@@ -900,6 +1950,11 @@ find-cache-dir@3.3.1:
make-dir "^3.0.2"
pkg-dir "^4.1.0"
+find-root@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
+ integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
+
find-up@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@@ -908,6 +1963,42 @@ find-up@^4.0.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
+focus-lock@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.7.0.tgz#b2bfb0ca7beacc8710a1ff74275fe0dc60a1d88a"
+ integrity sha512-LI7v2mH02R55SekHYdv9pRHR9RajVNyIJ2N5IEkWbg7FT5ZmJ9Hw4mWxHeEUcd+dJo0QmzztHvDvWcc7prVFsw==
+
+framer-motion@3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-3.2.1.tgz#66eeb883a0b5c425dd7767ecacdeac451c184cdb"
+ integrity sha512-5AWrh4JElgFAXWLqk0u8lVcdkigyuofyEy2LSsjuCxKbAb1hHqRn3PPdrV0KgPrysTHq95QO1bHFTLA7/Q8g+Q==
+ dependencies:
+ framesync "^5.0.0"
+ hey-listen "^1.0.8"
+ popmotion "^9.1.0"
+ style-value-types "^4.0.1"
+ tslib "^1.10.0"
+ optionalDependencies:
+ "@emotion/is-prop-valid" "^0.8.2"
+
+framer-motion@^3.0.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-3.3.0.tgz#e355824369f03c8cd07a87ef59b7a8348c33fafd"
+ integrity sha512-bjUrwXfMJZ6D+HSMDiXbMGKmlWGnUux8HotWgORTZkdPTgKAndlRXjeC2ikCgNVo2ifmRvEla5ckP9JaZc7JKA==
+ dependencies:
+ framesync "^5.0.0"
+ hey-listen "^1.0.8"
+ popmotion "^9.1.0"
+ style-value-types "^4.0.1"
+ tslib "^1.10.0"
+ optionalDependencies:
+ "@emotion/is-prop-valid" "^0.8.2"
+
+framesync@5.0.0, framesync@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.0.0.tgz#7de8caedf53ac441118e79680f1beb7391c328b6"
+ integrity sha512-wd8t+JsQGisluSv1twiEeDv0aNGpavGb9q7xgIk9fGbcIWkNXF/KVtrjnOrCwBWJuiXxlJfNkcvGudsI32FxYA==
+
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
@@ -918,6 +2009,11 @@ fsevents@~2.3.1:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f"
integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
gauge@~2.7.3:
version "2.7.4"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@@ -932,6 +2028,11 @@ gauge@~2.7.3:
strip-ansi "^3.0.1"
wide-align "^1.1.0"
+get-nonce@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
+ integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
+
github-from-package@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
@@ -969,6 +2070,13 @@ has-unicode@^2.0.0:
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
+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"
+
hash-base@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
@@ -991,6 +2099,11 @@ he@1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+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==
+
hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -1000,6 +2113,13 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
+hoist-non-react-statics@^3.3.1:
+ 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==
+ dependencies:
+ react-is "^16.7.0"
+
htmlparser2@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-5.0.1.tgz#7daa6fc3e35d6107ac95a4fc08781f091664f6e7"
@@ -1046,6 +2166,14 @@ ieee754@^1.1.13, ieee754@^1.1.4:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+import-fresh@^3.1.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+ integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
inherits@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
@@ -1066,6 +2194,18 @@ ini@~1.3.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+invariant@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
+ integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
+ dependencies:
+ loose-envify "^1.0.0"
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+ integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
+
is-arrayish@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
@@ -1078,6 +2218,13 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
+is-core-module@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
+ integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==
+ dependencies:
+ has "^1.0.3"
+
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -1132,6 +2279,11 @@ jest-worker@24.9.0:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+json-parse-even-better-errors@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+ integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
@@ -1139,6 +2291,30 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
+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==
+
line-column@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2"
@@ -1147,6 +2323,11 @@ line-column@^1.0.2:
isarray "^1.0.0"
isobject "^2.0.0"
+lines-and-columns@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
+ integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
+
loader-utils@1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
@@ -1163,17 +2344,82 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
-lodash.sortby@^4.7.0:
+lodash.capitalize@4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
+ integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk=
+
+lodash.clonedeep@4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+ integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
+
+lodash.curry@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
+ integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=
+
+lodash.debounce@4.0.8, lodash.debounce@^4, lodash.debounce@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+ integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
+
+lodash.filter@4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
+ integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=
+
+lodash.includes@4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
+ integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
+
+lodash.isequal@4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+ integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
+
+lodash.merge@4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+lodash.mergewith@4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
+ integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
+
+lodash.now@4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/lodash.now/-/lodash.now-4.0.2.tgz#5b5b0461aabc10ce7753505fd9d9b7278c8c6cc7"
+ integrity sha1-W1sEYaq8EM53U1Bf2dm3J4yMbMc=
+
+lodash.remove@4.7.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/lodash.remove/-/lodash.remove-4.7.0.tgz#f31d31e7c39a0690d5074ec0d3627162334ee626"
+ integrity sha1-8x0x58OaBpDVB07A02JxYjNO5iY=
+
+lodash.size@4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86"
+ integrity sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y=
+
+lodash.some@4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
+ integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=
+
+lodash.sortby@4.7.0, lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
-lodash@^4.17.13:
+lodash@^4, lodash@^4.17.13, lodash@^4.17.19:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
-loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -1203,6 +2449,11 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
+memoize-one@^5.0.0:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
+ integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
+
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -1246,6 +2497,11 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
+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==
+
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@@ -1268,7 +2524,7 @@ native-url@0.3.4:
dependencies:
querystring "^0.2.0"
-next@latest:
+next@10.0.6:
version "10.0.6"
resolved "https://registry.yarnpkg.com/next/-/next-10.0.6.tgz#1d33723d714c85f282b9bf6ff59dcae40f9252cb"
integrity sha512-uM5Yv4Ha9iL6Lbg7Ez36GyJ0YTdRLzXLA9b1REH3rX2Wytw0Ls5qPuFGk4BHSQpQhYx6Z61iA2qPkGl33W4iBg==
@@ -1401,7 +2657,7 @@ number-is-nan@^1.0.0:
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
-object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@4.1.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -1418,6 +2674,11 @@ os-browserify@^0.3.0:
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
+p-cancelable@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e"
+ integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==
+
p-limit@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
@@ -1449,6 +2710,13 @@ pako@~1.0.5:
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
parse-asn1@^5.0.0, parse-asn1@^5.1.5:
version "5.1.6"
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
@@ -1460,6 +2728,21 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5:
pbkdf2 "^3.0.3"
safe-buffer "^5.1.1"
+parse-json@^5.0.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
+ integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ error-ex "^1.3.1"
+ json-parse-even-better-errors "^2.3.0"
+ lines-and-columns "^1.1.6"
+
+parse-key@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/parse-key/-/parse-key-0.2.1.tgz#7bcf76595536e36075664be4d687e4bdd910208f"
+ integrity sha1-e892WVU242B1Zkvk1ofkvdkQII8=
+
path-browserify@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
@@ -1475,6 +2758,16 @@ path-exists@^4.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+path-parse@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
+ integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+
+path-type@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+ integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
pbkdf2@^3.0.3:
version "3.1.1"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94"
@@ -1510,6 +2803,21 @@ pnp-webpack-plugin@1.6.4:
dependencies:
ts-pnp "^1.1.6"
+popmotion@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.1.0.tgz#4360d06bd18ce8baa8f9284ecec7d55344af6325"
+ integrity sha512-+J7pzzBy5kk2qsP8ilowKs/CH+HoZa3kOGEBNCleCvsPXEF3nKHdfAR3SboMyPvdpIrofaT7ZIy/xWgz446Azw==
+ dependencies:
+ framesync "5.0.0"
+ hey-listen "^1.0.8"
+ style-value-types "^4.0.1"
+ tslib "^1.10.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-safe-parser@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96"
@@ -1576,7 +2884,7 @@ process@0.11.10, process@^0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
-prop-types@15.7.2, prop-types@^15.6.2:
+prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -1665,34 +2973,188 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-dom@^16.12.0:
- version "16.14.0"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
- integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==
+rdk@5.0.6, rdk@^5.0.6:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/rdk/-/rdk-5.0.6.tgz#6724786f7220a0937ab5ff3b6a78dc5b743561e4"
+ integrity sha512-aPKfLq2c0ms680wgnbdXLCqF8HHp7ZUaRBZlUR45OuUCMyMpHz667ptlDyW13T3YiGa6E0cWhw+ZfVHCvJBcbQ==
+ dependencies:
+ classnames "^2.2.6"
+ popper.js "^1.16.1"
+ react-scrolllock "^5.0.1"
+
+react-base16-styling@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.8.0.tgz#6251b814b4e09efab3284ae1ecdd490f2c4111eb"
+ integrity sha512-ElvciPaL4xpWh7ISX7ugkNS/dvoh7DpVMp4t93ngnEsS2LkMd8Gu+cDDOLis2rj4889CNK662UdjOfv3wvZg9w==
+ dependencies:
+ "@types/base16" "^1.0.2"
+ "@types/lodash.curry" "^4.1.6"
+ base16 "^1.0.0"
+ color "^3.1.2"
+ csstype "^3.0.2"
+ lodash.curry "^4.1.1"
+
+react-clientside-effect@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.3.tgz#95c95f520addfb71743608b990bfe01eb002012b"
+ integrity sha512-96HOmjJjjemxZD4qMdaMWFl3d/3Dqm/MAXnThoP8+jQihevYs8VzooqYWlVEPmkp9tVIa06i67R7FF1qsuzUwQ==
+ dependencies:
+ "@babel/runtime" "^7.0.0"
+
+react-cool-dimensions@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/react-cool-dimensions/-/react-cool-dimensions-1.2.0.tgz#d3cc89e18ae561986838621ef3ba21f330daa8da"
+ integrity sha512-sdpBLlEyQT2/jQn2V05udrxtfvG6JJlyAg38KElLQ7plvX645diU47trI1lWIU8XY/+oUBBCyCj2nziYaK62hA==
+
+react-debounce-input@3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/react-debounce-input/-/react-debounce-input-3.2.3.tgz#9e8c69771a621c81e8fe36b45ade49a95059cd87"
+ integrity sha512-7Bfjm9sxrtgB+IsSrdXoo4CVqKg7CbWC68dNhr8q7ZmY6C0AqtR524//SenHQWT+eeSG9DmSLWNWCUFSyaaWSQ==
+ dependencies:
+ lodash.debounce "^4"
+ prop-types "^15.7.2"
+
+react-dock@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/react-dock/-/react-dock-0.3.0.tgz#5d9d51cac78ee5819854c3ad180dabe50c420820"
+ integrity sha512-A0Dfy6enwb6gruxsMXAkZM6hVyJETTcuB4u/CUVPAj4ZH0/ULQacLHmIdUHfYtZYmPCfeEgOa7KyTueSPrIjFg==
+ dependencies:
+ "@types/prop-types" "^15.7.3"
+ lodash.debounce "^4.0.8"
+ prop-types "^15.7.2"
+
+react-dom@17.0.1:
+ version "17.0.1"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
+ integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
+ scheduler "^0.20.1"
+
+react-fast-compare@3.2.0, 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-focus-lock@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.4.1.tgz#e842cc93da736b5c5d331799012544295cbcee4f"
+ integrity sha512-c5ZP56KSpj9EAxzScTqQO7bQQNPltf/W1ZEBDqNDOV1XOIwvAyHX0O7db9ekiAtxyKgnqZjQlLppVg94fUeL9w==
+ dependencies:
+ "@babel/runtime" "^7.0.0"
+ focus-lock "^0.7.0"
prop-types "^15.6.2"
- scheduler "^0.19.1"
+ react-clientside-effect "^1.2.2"
+ use-callback-ref "^1.2.1"
+ use-sidecar "^1.0.1"
+
+react-input-autosize@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
+ integrity sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==
+ dependencies:
+ prop-types "^15.5.8"
-react-is@16.13.1, react-is@^16.8.1:
+react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+react-json-tree@^0.13.0:
+ version "0.13.0"
+ resolved "https://registry.yarnpkg.com/react-json-tree/-/react-json-tree-0.13.0.tgz#49b1e68e4ed7f4f918142a4b0d9af6c533c86a7d"
+ integrity sha512-FPJUQzYWi7pvBUAnMd9ENOnAUT2mXLhe01VUbPfKH9Q4gk4FQ0fpS1e1WZ3o7g6zfYJpYJeBTo1WwlMHlMlZOw==
+ dependencies:
+ "@types/prop-types" "^15.7.3"
+ prop-types "^15.7.2"
+ react-base16-styling "^0.8.0"
+
react-refresh@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
-react@^16.12.0:
- version "16.14.0"
- resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
- integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
+react-remove-scroll-bar@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.1.1.tgz#5876428dfd546f2f63a4d277aea2197925505c1e"
+ integrity sha512-IZbfQPSozIr8ylHE9MFcQeb2TTzj4abfE7OBXjmtUeXQ5h6ColGKDNo5h7OmzrJRilAx3YIKBf3jb0yrb31BJQ==
+ dependencies:
+ react-style-singleton "^2.1.0"
+ tslib "^1.0.0"
+
+react-remove-scroll@2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.0.tgz#190c16eb508c5927595935499e8f5dd9ab0e75cf"
+ integrity sha512-BZIO3GaEs0Or1OhA5C//n1ibUP1HdjJmqUVUsOCMxwoIpaCocbB9TFKwHOkBa/nyYy3slirqXeiPYGwdSDiseA==
+ dependencies:
+ react-remove-scroll-bar "^2.1.0"
+ react-style-singleton "^2.1.0"
+ tslib "^1.0.0"
+ use-callback-ref "^1.2.3"
+ use-sidecar "^1.0.1"
+
+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:
+ exenv "^1.2.2"
+
+react-select@4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.0.2.tgz#4dcca9f38d6a41e01f2dc7673e244a325e3b4e0e"
+ integrity sha512-BiihrRpRIBBvNqofNZIBpo08Kw8DBHb/kgpIDW4bxgkttk50Sxf0alEIKobns3U7UJXk/CA4rsFUueQEg9Pm5A==
+ dependencies:
+ "@babel/runtime" "^7.4.4"
+ "@emotion/cache" "^11.0.0"
+ "@emotion/css" "^11.0.0"
+ "@emotion/react" "^11.1.1"
+ memoize-one "^5.0.0"
+ prop-types "^15.6.0"
+ react-input-autosize "^3.0.0"
+ react-transition-group "^4.3.0"
+
+react-style-singleton@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66"
+ integrity sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==
+ dependencies:
+ get-nonce "^1.0.0"
+ invariant "^2.2.4"
+ tslib "^1.0.0"
+
+react-textarea-autosize@8.3.0:
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.0.tgz#e6e2fd186d9f61bb80ac6e2dcb4c55504f93c2fa"
+ integrity sha512-3GLWFAan2pbwBeoeNDoqGmSbrShORtgWfaWX0RJDivsUrpShh01saRM5RU/i4Zmf+whpBVEY5cA90Eq8Ub1N3w==
+ dependencies:
+ "@babel/runtime" "^7.10.2"
+ use-composed-ref "^1.0.0"
+ use-latest "^1.0.0"
+
+react-transition-group@^4.3.0:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
+ integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ dom-helpers "^5.0.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+
+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@17.0.1:
+ version "17.0.1"
+ resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
+ integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
- prop-types "^15.6.2"
readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.3, readable-stream@^2.3.6:
version "2.3.7"
@@ -1723,11 +3185,93 @@ readdirp@~3.5.0:
dependencies:
picomatch "^2.2.1"
+reaflow@3.0.13:
+ version "3.0.13"
+ resolved "https://registry.yarnpkg.com/reaflow/-/reaflow-3.0.13.tgz#7e1344cb011aeaf654502f40f80e8f648752b4e2"
+ integrity sha512-EEPq/4atEpL1RVJxTaLaN8Y3zhYHo2rpLnMbAX7DlI88emJJPqJpTwsH4q5jHspfo2tnatqEe8vmvxXGHLL0mQ==
+ dependencies:
+ calculate-size "^1.1.1"
+ classnames "^2.2.6"
+ d3-shape "^2.0.0"
+ elkjs "^0.7.1"
+ ellipsize "^0.1.0"
+ framer-motion "^3.0.0"
+ kld-affine "^2.1.1"
+ kld-intersections "^0.7.0"
+ p-cancelable "^2.0.0"
+ rdk "^5.0.6"
+ react-cool-dimensions "^1.2.0"
+ react-fast-compare "^3.2.0"
+ react-use-gesture "^8.0.1"
+ reakeys "^1.1.0"
+ undoo "^0.5.0"
+
+reakeys@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/reakeys/-/reakeys-1.1.0.tgz#cdacdcc1ee478992307b0952e7fc413157296fef"
+ integrity sha512-vNrGkUkIKs+mE4JVtvSTXwi3/c+nFxRoU8A+XLqfbBiXkYOIjnh4v8IEb1c+npzw1peH7ikKLvvR6jLFs97pwg==
+ dependencies:
+ mousetrap "^1.6.5"
+
+recoil-devtools-dock@^0.1.6:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/recoil-devtools-dock/-/recoil-devtools-dock-0.1.6.tgz#88037b16e44515c4f63e1d8fcb046b470cab0af1"
+ integrity sha512-ADCT7QE5H/LF2tUtz339y+pzIsEfXVh1yltBijQKnFskp6wS/JhZHRaWW6jDEXJQl6awuB+PMuS9fe46ZYN6aQ==
+ dependencies:
+ parse-key "^0.2.1"
+ react-dock "^0.3.0"
+ recoil "^0.1.2"
+
+recoil-devtools-log-monitor@^0.2.7:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/recoil-devtools-log-monitor/-/recoil-devtools-log-monitor-0.2.7.tgz#1cf604f1393ec868212805ba67c98b857caa3310"
+ integrity sha512-tfTzAPXj5UX955tLaA2a02AU8BsrSDiBGaeq+x597aRaNDEzwu7VXuOnFIF0dYHd/aqlqaHoF9svwTbm0Etscw==
+ dependencies:
+ base16 "^1.0.0"
+ lodash.debounce "^4.0.8"
+ react-json-tree "^0.13.0"
+ recoil "^0.1.2"
+ recoil-devtools-themes "^0.1.2"
+
+recoil-devtools-logger@^0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/recoil-devtools-logger/-/recoil-devtools-logger-0.1.5.tgz#40379e865d155ab2c42fc212b2785f0b1accf06c"
+ integrity sha512-TrNMGyHhyA3I/32JzDve3qyxEO1fo0f6GPKJuisl0FOTDRzWib96ErV/R2Fy9MyXz0GeX6TpmXnprcvSBgs1Lw==
+ dependencies:
+ deep-diff "^1.0.2"
+ recoil "^0.1.2"
+
+recoil-devtools-themes@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/recoil-devtools-themes/-/recoil-devtools-themes-0.1.2.tgz#5b6a2000153d5183417071c3a4beedd2b466b6fc"
+ integrity sha512-cawFXiUMNCw/gLImG93i4WjURBZxtuJa4bR7Y6KNvDRY2SWl2hGt+AA+r7kcs3MtdwbarR595m5yiLfUlRIyHQ==
+ dependencies:
+ "@types/base16" "^1.0.2"
+ base16 "^1.0.0"
+
+recoil@0.1.2, recoil@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.1.2.tgz#844c612f535826dbe54c977761a67ded16f6d063"
+ integrity sha512-hIRrHlkmW4yITlBFprVYjVPhzPKYrJKoaDrrJtAtbkMeXfXaa/XE5OlyR10n+rNfnKWNToCKb3Z4fo86IGjkzg==
+
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve@^1.12.0:
+ version "1.19.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
+ integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
+ dependencies:
+ is-core-module "^2.1.0"
+ path-parse "^1.0.6"
+
ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@@ -1751,10 +3295,10 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-scheduler@^0.19.1:
- version "0.19.1"
- resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
- integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==
+scheduler@^0.20.1:
+ version "0.20.1"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c"
+ integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
@@ -1875,6 +3419,11 @@ source-map@0.8.0-beta.0:
dependencies:
whatwg-url "^7.0.0"
+source-map@^0.5.7:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+ integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
+
source-map@^0.6.0, source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
@@ -1981,6 +3530,14 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
+style-value-types@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.0.1.tgz#23f05dd03e8a850654defc22cf03ebac572aaa00"
+ integrity sha512-aOV/HHyynIyTmU27qfs0oAHhFde6BFIvV4+nMerE2MAPZMwYOeQk1/F3S6djxF2u4HdbiieCPs3ZzWsbNUoc9A==
+ dependencies:
+ hey-listen "^1.0.8"
+ tslib "^1.10.0"
+
styled-jsx@3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-3.3.2.tgz#2474601a26670a6049fb4d3f94bd91695b3ce018"
@@ -2005,6 +3562,11 @@ stylis@3.5.4:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe"
integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==
+stylis@^4.0.3:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.7.tgz#412a90c28079417f3d27c028035095e4232d2904"
+ integrity sha512-OFFeUXFgwnGOKvEXaSv0D0KQ5ADP0n6g3SVONx6I/85JzNZ3u50FRwB3lVIk1QO2HNdI75tbVzc4Z66Gdp9voA==
+
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -2063,6 +3625,16 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
+tiny-invariant@^1.0.6:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
+ integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
+
+tinycolor2@1.4.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
+ integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
+
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@@ -2080,6 +3652,11 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
+toggle-selection@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
+ integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
+
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
@@ -2092,11 +3669,26 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
+ts-essentials@^2.0.3:
+ version "2.0.12"
+ resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745"
+ integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==
+
ts-pnp@^1.1.6:
version "1.2.0"
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
+tslib@^1.0.0, tslib@^1.10.0, tslib@^1.9.3:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+ integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
+tslib@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
+ integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
+
tty-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@@ -2114,10 +3706,18 @@ type-fest@^0.7.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
-typescript@4.0:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.5.tgz#ae9dddfd1069f1cb5beb3ef3b2170dd7c1332389"
- integrity sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==
+typescript@4.1.3:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
+ integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
+
+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"
unpipe@1.0.0:
version "1.0.0"
@@ -2132,6 +3732,38 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
+use-callback-ref@^1.2.1, use-callback-ref@^1.2.3:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
+ integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
+
+use-composed-ref@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc"
+ integrity sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg==
+ dependencies:
+ ts-essentials "^2.0.3"
+
+use-isomorphic-layout-effect@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz#7bb6589170cd2987a152042f9084f9effb75c225"
+ integrity sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==
+
+use-latest@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.0.tgz#a44f6572b8288e0972ec411bdd0840ada366f232"
+ integrity sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==
+ dependencies:
+ use-isomorphic-layout-effect "^1.0.0"
+
+use-sidecar@^1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.4.tgz#38398c3723727f9f924bed2343dfa3db6aaaee46"
+ integrity sha512-A5ggIS3/qTdxCAlcy05anO2/oqXOfpmxnpRE1Jm+fHHtCvUvNSZDGqgOSAXPriBVAcw2fMFFkh5v5KqrFFhCMA==
+ dependencies:
+ detect-node-es "^1.0.0"
+ tslib "^1.9.3"
+
use-subscription@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1"
@@ -2158,11 +3790,23 @@ util@^0.11.0:
dependencies:
inherits "2.0.3"
+uuid@8.3.2:
+ version "8.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
vm-browserify@1.1.2, vm-browserify@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
+warning@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
+ integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+ dependencies:
+ loose-envify "^1.0.0"
+
watchpack@2.0.0-beta.13:
version "2.0.0-beta.13"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.0.0-beta.13.tgz#9d9b0c094b8402139333e04eb6194643c8384f55"
@@ -2212,6 +3856,11 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+yaml@^1.7.2:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
+ integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
+
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"