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:
@@ -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>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './colorMode';
|
||||
export * from './copy';
|
||||
export * from './path';
|
||||
export * from './requery';
|
||||
export * from './reset';
|
||||
export * from './submit';
|
||||
|
||||
17
hyperglass/ui/components/buttons/path.tsx
Normal file
17
hyperglass/ui/components/buttons/path.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -23,3 +23,7 @@ export interface TRSubmitButton {
|
||||
onChange(e: OnChangeArgs): void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface TPathButton {
|
||||
onOpen(): void;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
60
hyperglass/ui/components/path/chart.tsx
Normal file
60
hyperglass/ui/components/path/chart.tsx
Normal 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} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
1
hyperglass/ui/components/path/index.ts
Normal file
1
hyperglass/ui/components/path/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './path';
|
||||
41
hyperglass/ui/components/path/path.tsx
Normal file
41
hyperglass/ui/components/path/path.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
25
hyperglass/ui/components/path/types.ts
Normal file
25
hyperglass/ui/components/path/types.ts
Normal 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;
|
||||
}
|
||||
68
hyperglass/ui/components/path/util.ts
Normal file
68
hyperglass/ui/components/path/util.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
11
hyperglass/ui/hooks/useASNDetail.ts
Normal file
11
hyperglass/ui/hooks/useASNDetail.ts
Normal 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);
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
2
hyperglass/ui/package.json
vendored
2
hyperglass/ui/package.json
vendored
@@ -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",
|
||||
|
||||
26
hyperglass/ui/types/bgpview.ts
Normal file
26
hyperglass/ui/types/bgpview.ts
Normal 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;
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
4
hyperglass/ui/types/util.ts
Normal file
4
hyperglass/ui/types/util.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface PathPart {
|
||||
base: number;
|
||||
children: PathPart[];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
182
hyperglass/ui/yarn.lock
vendored
182
hyperglass/ui/yarn.lock
vendored
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user