1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

continue typescript & chakra v1 migrations [skip ci]

This commit is contained in:
checktheroads
2020-12-22 02:00:36 -07:00
parent 2b7039943b
commit d263fb9203
22 changed files with 547 additions and 17 deletions

View File

@@ -17,7 +17,7 @@ export const CopyButton = (props: TCopyButton) => {
size="sm"
variant="ghost"
onClick={onCopy}
colorScheme="primary"
colorScheme="secondary"
{...rest}>
<Icon as={hasCopied ? Check : Copy} boxSize="16px" />
</Button>

View File

@@ -1,5 +1,6 @@
export * from './colorMode';
export * from './copy';
export * from './path';
export * from './requery';
export * from './reset';
export * from './submit';

View File

@@ -0,0 +1,17 @@
import dynamic from 'next/dynamic';
import { Button, Icon, Tooltip } from '@chakra-ui/react';
import type { TPathButton } from './types';
const PathIcon = dynamic<MeronexIcon>(() => import('@meronex/icons/gr').then(i => i.GrNetwork));
export const PathButton = (props: TPathButton) => {
const { onOpen } = props;
return (
<Tooltip hasArrow label="View AS Path" placement="top">
<Button as="a" mx={1} size="sm" variant="ghost" onClick={onOpen} colorScheme="secondary">
<Icon as={PathIcon} boxSize="16px" />
</Button>
</Tooltip>
);
};

View File

@@ -23,3 +23,7 @@ export interface TRSubmitButton {
onChange(e: OnChangeArgs): void;
children: React.ReactNode;
}
export interface TPathButton {
onOpen(): void;
}

View File

@@ -15,6 +15,7 @@ export * from './lookingGlass';
export * from './markdown';
export * from './meta';
export * from './output';
export * from './path';
export * from './results';
export * from './select';
export * from './table';

View File

@@ -0,0 +1,60 @@
import { useMemo } from 'react';
import { Box, Flex, Icon, Badge, VStack } from '@chakra-ui/react';
import { BeatLoader } from 'react-spinners';
import ReactFlow from 'react-flow-renderer';
import { Background, Controls } from 'react-flow-renderer';
import { Handle, Position } from 'react-flow-renderer';
import { useConfig, useColorValue, useColorToken } from '~/context';
import { useASNDetail } from '~/hooks';
import { buildElements } from './util';
import type { TChart, TNode, TNodeData } from './types';
export const Chart = (props: TChart) => {
const { data } = props;
const { primary_asn, org_name } = useConfig();
// const elements = useMemo(() => [...buildElements({ asn: primary_asn, name: org_name }, data)], [
// data,
// ]);
const elements = [...buildElements({ asn: primary_asn, name: org_name }, data)];
const dots = useColorToken('blackAlpha.500', 'whiteAlpha.400');
return (
<Box boxSize="100%">
<ReactFlow elements={elements} nodeTypes={{ TestNode }} defaultPosition={[500, 450]}>
<Background color={dots} />
<Controls />
</ReactFlow>
</Box>
);
};
const TestNode = (props: TNode<TNodeData>) => {
const { data } = props;
const { asn, name, hasChildren, hasParents } = data;
const color = useColorValue('black', 'white');
const bg = useColorValue('white', 'blackAlpha.800');
const { data: asnData, isError, isLoading } = useASNDetail(asn);
return (
<>
{hasChildren && <Handle type="source" position={Position.Top} />}
<Box py={3} px={4} bg={bg} minW={40} minH={12} color={color} boxShadow="md" borderRadius="md">
<VStack spacing={4}>
<Flex fontSize="lg">
{isLoading ? (
<BeatLoader color={color} />
) : !isError && asnData?.data?.description_short ? (
asnData.data.description_short
) : (
name
)}
</Flex>
<Badge fontFamily="mono" fontWeight="normal" fontSize="sm" colorScheme="primary">
{asn}
</Badge>
</VStack>
</Box>
{hasParents && <Handle type="target" position={Position.Bottom} />}
</>
);
};

View File

@@ -0,0 +1 @@
export * from './path';

View File

@@ -0,0 +1,41 @@
import {
Modal,
ModalBody,
ModalHeader,
ModalOverlay,
ModalContent,
useDisclosure,
Skeleton,
ModalCloseButton,
} from '@chakra-ui/react';
import { PathButton } from '~/components';
import { useColorValue } from '~/context';
import { useLGState } from '~/hooks';
import { Chart } from './chart';
import type { TPath } from './types';
export const Path = (props: TPath) => {
const { device } = props;
const { getResponse } = useLGState();
const { isOpen, onClose, onOpen } = useDisclosure();
const { displayTarget } = useLGState();
const response = getResponse(device);
const output = response?.output as TStructuredResponse;
const bg = useColorValue('white', 'blackFaded.800');
return (
<>
<PathButton onOpen={onOpen} />
<Modal isOpen={isOpen} onClose={onClose} size="full" isCentered>
<ModalOverlay />
<ModalContent bg={bg} maxW="80%" maxH="60%">
<ModalHeader>{`Path to ${displayTarget.value}`}</ModalHeader>
<ModalCloseButton />
<ModalBody>
{response !== null ? <Chart data={output} /> : <Skeleton w="500px" h="300px" />}
</ModalBody>
</ModalContent>
</Modal>
</>
);
};

View File

@@ -0,0 +1,25 @@
import type { NodeProps } from 'react-flow-renderer';
export interface TChart {
data: TStructuredResponse;
}
export interface TPath {
device: string;
}
export interface TNode<D extends any> extends Omit<NodeProps, 'data'> {
data: D;
}
export interface TNodeData {
asn: string;
name: string;
hasChildren: boolean;
hasParents?: boolean;
}
export interface BasePath {
asn: string;
name: string;
}

View File

@@ -0,0 +1,68 @@
import { arrangeIntoTree } from '~/util';
import type { FlowElement, Elements } from 'react-flow-renderer';
import type { PathPart } from '~/types';
import type { BasePath } from './types';
function treeToElement(part: PathPart, len: number, index: number): FlowElement[] {
const x = index * 250;
const y = -(len * 10);
let elements = [
{
id: String(part.base),
type: 'TestNode',
position: { x, y },
data: {
asn: part.base,
name: `AS${part.base}`,
hasChildren: part.children.length !== 0,
hasParents: true,
},
},
] as Elements;
for (const child of part.children) {
let xc = index;
if (part.children.length !== 0) {
elements.push({
id: `e${part.base}-${child.base}`,
source: String(part.base),
target: String(child.base),
});
} else {
xc = x;
}
elements.push(...treeToElement(child, part.children.length * 12 + len, xc));
}
return elements;
}
export function* buildElements(base: BasePath, data: TStructuredResponse): Generator<FlowElement> {
const { routes } = data;
// Eliminate empty AS paths & deduplicate non-empty AS paths. Length should be same as count minus empty paths.
const asPaths = routes.filter(r => r.as_path.length !== 0).map(r => [...new Set(r.as_path)]);
const asTree = arrangeIntoTree(asPaths);
const numHops = asPaths.flat().length;
const childPaths = asTree.map((a, i) => {
return treeToElement(a, asTree.length, i);
});
// Add the first hop at the base.
yield {
id: base.asn,
type: 'TestNode',
position: { x: 150, y: numHops * 10 },
data: { asn: base.asn, name: base.name, hasChildren: true, hasParents: false },
};
for (const [i, path] of childPaths.entries()) {
// path = Each unique path from origin
const first = path[0];
yield { id: `e${base.asn}-${first.id}`, source: base.asn, target: first.id };
// Add link from base to each first hop.
yield { id: `e${base.asn}-${first.id}`, source: base.asn, target: first.id };
for (const hop of path) {
yield hop;
}
}
}

View File

@@ -12,9 +12,9 @@ import {
import { motion } from 'framer-motion';
import { BsLightningFill } from '@meronex/icons/bs';
import { startCase } from 'lodash';
import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If } from '~/components';
import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If, Path } from '~/components';
import { useColorValue, useConfig, useMobile } from '~/context';
import { useStrf, useLGQuery, useTableToString } from '~/hooks';
import { useStrf, useLGQuery, useLGState, useTableToString } from '~/hooks';
import { isStructuredOutput, isStringOutput } from '~/types';
import { FormattedError } from './error';
import { ResultHeader } from './header';
@@ -49,12 +49,15 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
} = props;
const { web, cache, messages } = useConfig();
const isMobile = useMobile();
const color = useColorValue('black', 'white');
const scrollbar = useColorValue('blackAlpha.300', 'whiteAlpha.300');
const scrollbarHover = useColorValue('blackAlpha.400', 'whiteAlpha.400');
const scrollbarBg = useColorValue('blackAlpha.50', 'whiteAlpha.50');
const { responses } = useLGState();
const { data, error, isError, isLoading, refetch } = useLGQuery({
queryLocation,
queryTarget,
@@ -62,6 +65,10 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
queryVrf,
});
if (typeof data !== 'undefined') {
responses.merge({ [device.name]: data });
}
const cacheLabel = useStrf(web.text.cache_icon, { time: data?.timestamp }, [data?.timestamp]);
const [isOpen, setOpen] = useState(false);
@@ -175,6 +182,9 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
/>
</AccordionButton>
<ButtonGroup px={[1, 1, 3, 3]} py={2}>
{isStructuredOutput(data) && data.level === 'success' && tableComponent && (
<Path device={device.name} />
)}
<CopyButton copyValue={copyValue} isDisabled={isLoading} />
<RequeryButton requery={refetch} isDisabled={isLoading} />
</ButtonGroup>
@@ -199,7 +209,7 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
}}>
<Box>
<Flex direction="column" flex="1 0 auto" maxW={error ? '100%' : undefined}>
{!isError && typeof data !== 'undefined' && (
{!isError && typeof data !== 'undefined' ? (
<>
{isStructuredOutput(data) && data.level === 'success' && tableComponent ? (
<BGPTable>{data.output}</BGPTable>
@@ -209,8 +219,16 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
<Alert rounded="lg" my={2} py={4} status={errorLevel}>
<FormattedError message={data.output} keywords={errorKeywords} />
</Alert>
) : null}
) : (
<Alert rounded="lg" my={2} py={4} status={errorLevel}>
<FormattedError message={errorMsg} keywords={errorKeywords} />
</Alert>
)}
</>
) : (
<Alert rounded="lg" my={2} py={4} status={errorLevel}>
<FormattedError message={errorMsg} keywords={errorKeywords} />
</Alert>
)}
</Flex>
</Box>

View File

@@ -31,7 +31,7 @@ export const useTheme = (): ITheme => useChakraTheme();
export const useMobile = (): boolean =>
useBreakpointValue({ base: true, md: true, lg: false, xl: false }) ?? true;
export const useColorToken = (light: string, dark: string): ValueOf<ITheme['colors']> =>
export const useColorToken = (light: string, dark: string): string =>
useColorModeValue(useToken('colors', light), useToken('colors', dark));
export {

View File

@@ -1,9 +1,10 @@
export * from './useASNDetail';
export * from './useBooleanValue';
export * from './useDevice';
export * from './useGreeting';
export * from './useLGQuery';
export * from './useLGState';
export * from './useOpposingColor';
export * from './useSessionStorage';
export * from './useStrf';
export * from './useTableToString';
export * from './useLGState';

View File

@@ -0,0 +1,11 @@
import { useQuery } from 'react-query';
import type { TASNDetails } from '~/types';
async function query(asn: string): Promise<TASNDetails> {
const res = await fetch(`https://api.bgpview.io/asn/${asn}`, { mode: 'cors' });
return await res.json();
}
export function useASNDetail(asn: string) {
return useQuery(asn, query);
}

View File

@@ -4,29 +4,32 @@ import type { State } from '@hookstate/core';
import type { Families, TDeviceVrf, TQueryTypes, TFormData } from '~/types';
type TLGState = {
isSubmitting: boolean;
queryVrf: string;
families: Families;
queryTarget: string;
btnLoading: boolean;
formData: TFormData;
isSubmitting: boolean;
displayTarget: string;
queryType: TQueryTypes;
queryLocation: string[];
availVrfs: TDeviceVrf[];
resolvedIsOpen: boolean;
fqdnTarget: string | null;
formData: TFormData;
responses: { [d: string]: TQueryResponse };
};
type TLGStateHandlers = {
resolvedOpen(): void;
resolvedClose(): void;
resetForm(): void;
getResponse(d: string): TQueryResponse | null;
};
const LGState = createState<TLGState>({
isSubmitting: false,
formData: { query_location: [], query_target: '', query_type: '', query_vrf: '' },
resolvedIsOpen: false,
isSubmitting: false,
displayTarget: '',
queryLocation: [],
btnLoading: false,
@@ -34,9 +37,9 @@ const LGState = createState<TLGState>({
queryTarget: '',
queryType: '',
availVrfs: [],
responses: {},
queryVrf: '',
families: [],
formData: { query_location: [], query_target: '', query_type: '', query_vrf: '' },
});
export function useLGState(): State<TLGState> & TLGStateHandlers {
@@ -59,8 +62,16 @@ export function useLGState(): State<TLGState> & TLGStateHandlers {
resolvedIsOpen: false,
btnLoading: false,
formData: { query_location: [], query_target: '', query_type: '', query_vrf: '' },
responses: {},
});
}
function getResponse(device: string): TQueryResponse | null {
if (device in state.responses) {
return state.responses[device].value;
} else {
return null;
}
}
return { resetForm, resolvedOpen, resolvedClose, ...state };
return { resetForm, resolvedOpen, resolvedClose, getResponse, ...state };
}

View File

@@ -35,10 +35,12 @@
"react-countdown": "^2.2.1",
"react-dom": "^17.0.1",
"react-fast-compare": "^3.2.0",
"react-flow-renderer": "^8.2.3",
"react-hook-form": "^6.13.1",
"react-markdown": "^4.3.1",
"react-query": "^2.26.4",
"react-select": "^3.1.1",
"react-spinners": "^0.9.0",
"react-string-replace": "^0.4.4",
"react-table": "^7.6.2",
"string-format": "^2.0.0",

View File

@@ -0,0 +1,26 @@
interface TASNRIRAllocation {
rir_name: string | null;
country_code: string | null;
date_allocated: string | null;
}
interface TASNData {
asn: number;
name: string | null;
description_short: string | null;
description_full: string[];
country_code: string;
website: string | null;
email_contacts: string[];
abuse_contacts: string[];
looking_glass: string | null;
traffic_estimation: string | null;
traffic_ratio: string | null;
owner_address: string[];
rir_allocation: TASNRIRAllocation;
date_updated: string | null;
}
export interface TASNDetails {
status: string;
status_message: string;
data: TASNData;
}

View File

@@ -16,9 +16,9 @@ export function isString(a: any): a is string {
}
export function isStructuredOutput(data: any): data is TStringTableData {
return typeof data.output !== 'string';
return typeof data !== 'undefined' && 'output' in data && typeof data.output !== 'string';
}
export function isStringOutput(data: any): data is TQueryResponseString {
return typeof data.output === 'string';
return typeof data !== 'undefined' && 'output' in data && typeof data.output === 'string';
}

View File

@@ -1,3 +1,4 @@
export * from './bgpview';
export * from './common';
export * from './config';
export * from './data';
@@ -5,3 +6,4 @@ export * from './dns-over-https';
export * from './guards';
export * from './table';
export * from './theme';
export * from './util';

View File

@@ -0,0 +1,4 @@
export interface PathPart {
base: number;
children: PathPart[];
}

View File

@@ -12,3 +12,64 @@ export function flatten<T extends unknown>(arr: any[][]): T[] {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
export function chunkArray<A extends any>(array: A[], size: number): A[][] {
let result = [] as A[][];
for (let i = 0; i < array.length; i += size) {
let chunk = array.slice(i, i + size);
result.push(chunk);
}
return result;
}
interface PathPart {
base: number;
children: PathPart[];
}
/**
* Arrange an array of arrays into a tree of nodes.
*
* Blatantly stolen from:
* @see https://gist.github.com/stephanbogner/4b590f992ead470658a5ebf09167b03d
*/
export function arrangeIntoTree<P extends any>(paths: P[][]): PathPart[] {
let tree = [] as PathPart[];
for (let i = 0; i < paths.length; i++) {
let path = paths[i];
let currentLevel = tree;
for (let j = 0; j < path.length; j++) {
let part = path[j];
const existingPath = findWhere(currentLevel, 'base', part);
if (existingPath !== false) {
currentLevel = existingPath.children;
} else {
const newPart = {
base: part,
children: [],
} as PathPart;
currentLevel.push(newPart);
currentLevel = newPart.children;
}
}
}
return tree;
function findWhere<V extends any>(array: any[], idx: string, value: V): PathPart | false {
let t = 0;
while (t < array.length && array[t][idx] !== value) {
t++;
}
if (t < array.length) {
return array[t];
} else {
return false;
}
}
}

View File

@@ -160,7 +160,7 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@7.12.5", "@babel/runtime@^7.0.0", "@babel/runtime@^7.10.5":
"@babel/runtime@7.12.5", "@babel/runtime@^7.0.0", "@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
@@ -760,6 +760,18 @@
"@emotion/weak-memoize" "^0.2.5"
stylis "^4.0.3"
"@emotion/core@^10.0.15":
version "10.1.1"
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3"
integrity sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==
dependencies:
"@babel/runtime" "^7.5.5"
"@emotion/cache" "^10.0.27"
"@emotion/css" "^10.0.27"
"@emotion/serialize" "^0.11.15"
"@emotion/sheet" "0.9.4"
"@emotion/utils" "0.11.3"
"@emotion/core@^10.0.9":
version "10.0.35"
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.0.35.tgz#513fcf2e22cd4dfe9d3894ed138c9d7a859af9b3"
@@ -2096,7 +2108,12 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@2.2.6:
classcat@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/classcat/-/classcat-4.1.0.tgz#e8fd8623e5625187b58adf49bb669a13b6c520f4"
integrity sha512-RA8O5oCi1I1CF6rR4cRBROh8MtZzM4w7xKLm0jd+S6UN2G4FIto+9DVOeFc46JEZFN5PVe/EZWLQO1VU/AUH4A==
classnames@2.2.6, classnames@^2.2.5:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@@ -2508,6 +2525,68 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
"d3-color@1 - 2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e"
integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==
"d3-dispatch@1 - 2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-2.0.0.tgz#8a18e16f76dd3fcaef42163c97b926aa9b55e7cf"
integrity sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==
d3-drag@2:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-2.0.0.tgz#9eaf046ce9ed1c25c88661911c1d5a4d8eb7ea6d"
integrity sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==
dependencies:
d3-dispatch "1 - 2"
d3-selection "2"
"d3-ease@1 - 2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-2.0.0.tgz#fd1762bfca00dae4bacea504b1d628ff290ac563"
integrity sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==
"d3-interpolate@1 - 2":
version "2.0.1"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==
dependencies:
d3-color "1 - 2"
d3-selection@2, d3-selection@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-2.0.0.tgz#94a11638ea2141b7565f883780dabc7ef6a61066"
integrity sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==
"d3-timer@1 - 2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-2.0.0.tgz#055edb1d170cfe31ab2da8968deee940b56623e6"
integrity sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==
d3-transition@2:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-2.0.0.tgz#366ef70c22ef88d1e34105f507516991a291c94c"
integrity sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog==
dependencies:
d3-color "1 - 2"
d3-dispatch "1 - 2"
d3-ease "1 - 2"
d3-interpolate "1 - 2"
d3-timer "1 - 2"
d3-zoom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-2.0.0.tgz#f04d0afd05518becce879d04709c47ecd93fba54"
integrity sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==
dependencies:
d3-dispatch "1 - 2"
d3-drag "2"
d3-interpolate "1 - 2"
d3-selection "2"
d3-transition "2"
d@1, d@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
@@ -2759,6 +2838,20 @@ duplexify@^3.4.2, duplexify@^3.6.0:
readable-stream "^2.0.0"
stream-shift "^1.0.0"
easy-peasy@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/easy-peasy/-/easy-peasy-4.0.1.tgz#8b3ab1ebb43509a62dc2c37b4269a9141e33d918"
integrity sha512-aTvB48M2ej6dM/wllUm1F7CTWGnYOYh82SHBkvJtOZhJ/9L8Gmg/nIVqDPwJeojOWZe+gbLtpyi8DhN6fPNBYg==
dependencies:
immer "7.0.9"
is-plain-object "^5.0.0"
memoizerific "^1.11.3"
redux "^4.0.5"
redux-thunk "^2.3.0"
symbol-observable "^2.0.3"
ts-toolbelt "^8.0.7"
use-memo-one "^1.1.1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -3247,7 +3340,7 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
fast-deep-equal@^3.1.1:
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
@@ -3783,6 +3876,11 @@ ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
immer@7.0.9:
version "7.0.9"
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e"
integrity sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A==
import-fresh@^3.0.0, import-fresh@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
@@ -4059,6 +4157,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
is-plain-object@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
is-regex@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9"
@@ -4376,6 +4479,11 @@ map-cache@^0.2.2:
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
map-or-similar@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08"
integrity sha1-beJlMXSt+12e3DPGnT6Sobdvrwg=
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
@@ -4414,6 +4522,13 @@ memoize-one@^5.0.0:
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
memoizerific@^1.11.3:
version "1.11.3"
resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a"
integrity sha1-fIekZGREwy11Q4VwkF8tvRsagFo=
dependencies:
map-or-similar "^1.5.0"
memory-fs@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
@@ -5475,11 +5590,32 @@ react-dom@^17.0.1:
object-assign "^4.1.1"
scheduler "^0.20.1"
react-draggable@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.3.tgz#0727f2cae5813e36b0e4962bf11b2f9ef2b406f3"
integrity sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==
dependencies:
classnames "^2.2.5"
prop-types "^15.6.0"
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-flow-renderer@^8.2.3:
version "8.2.3"
resolved "https://registry.yarnpkg.com/react-flow-renderer/-/react-flow-renderer-8.2.3.tgz#00df643d66c4414037eb5ebbdeb7aaa819860649"
integrity sha512-Kez92PpeU1xmXNMa1pFiWYvLaLaFSmBX+9CUTSmXMO3eqpTD48nMJsax4lu/qv/muxJyBjuftSXb9hQM7iHJVQ==
dependencies:
"@babel/runtime" "^7.12.5"
classcat "^4.1.0"
d3-selection "^2.0.0"
d3-zoom "^2.0.0"
easy-peasy "^4.0.1"
fast-deep-equal "^3.1.3"
react-draggable "^4.4.3"
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"
@@ -5568,6 +5704,13 @@ react-select@^3.1.1:
react-input-autosize "^2.2.2"
react-transition-group "^4.3.0"
react-spinners@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.9.0.tgz#b22c38acbfce580cd6f1b04a4649e812370b1fb8"
integrity sha512-+x6eD8tn/aYLdxZjNW7fSR1uoAXLb9qq6TFYZR1dFweJvckcf/HfP8Pa/cy5HOvB/cvI4JgrYXTjh2Me3S6Now==
dependencies:
"@emotion/core" "^10.0.15"
react-string-replace@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/react-string-replace/-/react-string-replace-0.4.4.tgz#24006fbe0db573d5be583133df38b1a735cb4225"
@@ -5645,6 +5788,19 @@ readdirp@~3.5.0:
dependencies:
picomatch "^2.2.1"
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
dependencies:
loose-envify "^1.4.0"
symbol-observable "^1.2.0"
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
@@ -6436,6 +6592,16 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
symbol-observable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
symbol-observable@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a"
integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==
table@^5.2.3:
version "5.4.6"
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
@@ -6651,6 +6817,11 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
ts-toolbelt@^8.0.7:
version "8.0.7"
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-8.0.7.tgz#4dad2928831a811ee17dbdab6eb1919fc0a295bf"
integrity sha512-KICHyKxc5Nu34kyoODrEe2+zvuQQaubTJz7pnC5RQ19TH/Jged1xv+h8LBrouaSD310m75oAljYs59LNHkLDkQ==
tsconfig-paths@^3.9.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"
@@ -6880,6 +7051,11 @@ use-callback-ref@^1.2.1, use-callback-ref@^1.2.3:
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.4.tgz#d86d1577bfd0b955b6e04aaf5971025f406bea3c"
integrity sha512-rXpsyvOnqdScyied4Uglsp14qzag1JIemLeTWGKbwpotWht57hbP78aNT+Q4wdFKQfQibbUX4fb6Qb4y11aVOQ==
use-memo-one@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c"
integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==
use-sidecar@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.3.tgz#17a4e567d4830c0c0ee100040e85a7fe68611e0f"