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

restructure components & clean up unused code [skip ci]

This commit is contained in:
checktheroads
2020-12-29 15:58:36 -07:00
parent 634606c8d0
commit fc018955c0
29 changed files with 81 additions and 477 deletions

View File

@@ -1,9 +1,10 @@
import dynamic from 'next/dynamic';
import { Button, Flex, Link, HStack, useToken } from '@chakra-ui/react';
import { If, ColorModeToggle } from '~/components';
import { Button, Flex, Link, Icon, HStack, useToken } from '@chakra-ui/react';
import { If } from '~/components';
import { useConfig, useMobile, useColorValue, useBreakpointValue } from '~/context';
import { useStrf } from '~/hooks';
import { FooterButton } from './button';
import { ColorModeToggle } from './colorMode';
const CodeIcon = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiCode));
const ExtIcon = dynamic<MeronexIcon>(() => import('@meronex/icons/go').then(i => i.GoLinkExternal));
@@ -52,7 +53,11 @@ export const Footer = () => {
</If>
{!isMobile && <Flex p={0} flex="0 0 auto" maxWidth="100%" mr="auto" />}
<If c={web.credit.enable}>
<FooterButton side="right" content={content.credit} title={<CodeIcon />} />
<FooterButton
side="right"
content={content.credit}
title={<Icon as={CodeIcon} boxSize={size} />}
/>
</If>
<ColorModeToggle size={size} />
</HStack>

View File

@@ -1,4 +1,4 @@
import type { MenuListProps } from '@chakra-ui/react';
import type { ButtonProps, MenuListProps } from '@chakra-ui/react';
type TFooterSide = 'left' | 'right';
@@ -9,3 +9,7 @@ export interface TFooterButton extends Omit<MenuListProps, 'title'> {
}
export type TFooterItems = 'help' | 'credit' | 'terms';
export interface TColorModeToggle extends ButtonProps {
size?: string;
}

View File

@@ -1,251 +0,0 @@
import * as React from 'react';
import { forwardRef, useState, useEffect } from 'react';
import { Box, Flex } from '@chakra-ui/core';
import { useForm } from 'react-hook-form';
import { intersectionWith, isEqual } from 'lodash';
import * as yup from 'yup';
import format from 'string-format';
import {
FormField,
HelpModal,
QueryLocation,
QueryType,
QueryTarget,
CommunitySelect,
QueryVrf,
ResolvedTarget,
SubmitButton,
} from 'app/components';
import { useConfig } from 'app/context';
format.extend(String.prototype, {});
const formSchema = config =>
yup.object().shape({
query_location: yup
.array()
.of(yup.string())
.required(
config.messages.no_input.format({
field: config.web.text.query_location,
}),
),
query_type: yup
.string()
.required(config.messages.no_input.format({ field: config.web.text.query_type })),
query_vrf: yup.string(),
query_target: yup
.string()
.required(config.messages.no_input.format({ field: config.web.text.query_target })),
});
const FormRow = ({ children, ...props }) => (
<Flex
flexDirection="row"
flexWrap="wrap"
w="100%"
justifyContent={['center', 'center', 'space-between', 'space-between']}
{...props}>
{children}
</Flex>
);
export const HyperglassForm = forwardRef(
({ isSubmitting, setSubmitting, setFormData, greetingAck, setGreetingAck, ...props }, ref) => {
const config = useConfig();
const { handleSubmit, register, unregister, setValue, errors } = useForm({
validationSchema: formSchema(config),
defaultValues: { query_vrf: 'default', query_target: '' },
});
const [queryLocation, setQueryLocation] = useState([]);
const [queryType, setQueryType] = useState('');
const [queryVrf, setQueryVrf] = useState('');
const [queryTarget, setQueryTarget] = useState('');
const [availVrfs, setAvailVrfs] = useState([]);
const [fqdnTarget, setFqdnTarget] = useState('');
const [displayTarget, setDisplayTarget] = useState('');
const [families, setFamilies] = useState([]);
const onSubmit = values => {
if (!greetingAck && config.web.greeting.required) {
window.location.reload(false);
setGreetingAck(false);
} else {
setFormData(values);
setSubmitting(true);
}
};
const handleLocChange = locObj => {
setQueryLocation(locObj.value);
const allVrfs = [];
const deviceVrfs = [];
locObj.value.map(loc => {
const locVrfs = [];
config.devices[loc].vrfs.map(vrf => {
locVrfs.push({
label: vrf.display_name,
value: vrf.id,
});
deviceVrfs.push([{ id: vrf.id, ipv4: vrf.ipv4, ipv6: vrf.ipv6 }]);
});
allVrfs.push(locVrfs);
});
const intersecting = intersectionWith(...allVrfs, isEqual);
setAvailVrfs(intersecting);
!intersecting.includes(queryVrf) && queryVrf !== 'default' && setQueryVrf('default');
let ipv4 = 0;
let ipv6 = 0;
deviceVrfs.length !== 0 &&
intersecting.length !== 0 &&
deviceVrfs
.filter(v => intersecting.every(i => i.id === v.id))
.reduce((a, b) => a.concat(b))
.filter(v => v.id === 'default')
.map(v => {
v.ipv4 === true && ipv4++;
v.ipv6 === true && ipv6++;
});
if (ipv4 !== 0 && ipv4 === ipv6) {
setFamilies([4, 6]);
} else if (ipv4 > ipv6) {
setFamilies([4]);
} else if (ipv4 < ipv6) {
setFamilies([6]);
} else {
setFamilies([]);
}
};
const handleChange = e => {
setValue(e.field, e.value);
e.field === 'query_location'
? handleLocChange(e)
: e.field === 'query_type'
? setQueryType(e.value)
: e.field === 'query_vrf'
? setQueryVrf(e.value)
: e.field === 'query_target'
? setQueryTarget(e.value)
: null;
};
const vrfContent = config.content.vrf[queryVrf]?.[queryType];
const validFqdnQueryType =
['ping', 'traceroute', 'bgp_route'].includes(queryType) &&
fqdnTarget &&
queryVrf === 'default'
? fqdnTarget
: null;
useEffect(() => {
register({ name: 'query_location' });
register({ name: 'query_type' });
register({ name: 'query_vrf' });
}, [register]);
Object.keys(errors).length >= 1 && console.error(errors);
return (
<Box
as="form"
onSubmit={handleSubmit(onSubmit)}
maxW={['100%', '100%', '75%', '75%']}
w="100%"
p={0}
mx="auto"
my={4}
textAlign="left"
ref={ref}
{...props}>
<FormRow>
<FormField
label={config.web.text.query_location}
name="query_location"
error={errors.query_location}>
<QueryLocation
onChange={handleChange}
locations={config.networks}
label={config.web.text.query_location}
/>
</FormField>
<FormField
label={config.web.text.query_type}
name="query_type"
error={errors.query_type}
labelAddOn={vrfContent && <HelpModal item={vrfContent} name="query_type" />}>
<QueryType
onChange={handleChange}
queryTypes={config.queries.list}
label={config.web.text.query_type}
/>
</FormField>
</FormRow>
<FormRow>
{availVrfs.length > 1 && (
<FormField label={config.web.text.query_vrf} name="query_vrf" error={errors.query_vrf}>
<QueryVrf
label={config.web.text.query_vrf}
vrfs={availVrfs}
onChange={handleChange}
/>
</FormField>
)}
<FormField
label={config.web.text.query_target}
name="query_target"
error={errors.query_target}
fieldAddOn={
queryLocation.length !== 0 &&
validFqdnQueryType && (
<ResolvedTarget
queryTarget={queryTarget}
fqdnTarget={validFqdnQueryType}
setTarget={handleChange}
families={families}
availVrfs={availVrfs}
/>
)
}>
{queryType === 'bgp_community' && config.queries.bgp_community.mode === 'select' ? (
<CommunitySelect
label={config.queries.bgp_community.display_name}
name="query_target"
register={register}
unregister={unregister}
onChange={handleChange}
communities={config.queries.bgp_community.communities}
/>
) : (
<QueryTarget
name="query_target"
placeholder={config.web.text.query_target}
register={register}
unregister={unregister}
resolveTarget={['ping', 'traceroute', 'bgp_route'].includes(queryType)}
value={queryTarget}
setFqdn={setFqdnTarget}
setTarget={handleChange}
displayValue={displayTarget}
setDisplayValue={setDisplayTarget}
/>
)}
</FormField>
</FormRow>
<FormRow mt={0} justifyContent="flex-end">
<Flex
w="100%"
maxW="100%"
ml="auto"
my={2}
mr={[0, 0, 2, 2]}
flexDirection="column"
flex="0 0 0">
<SubmitButton isLoading={isSubmitting} />
</Flex>
</FormRow>
</Box>
);
},
);

View File

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

View File

@@ -1,25 +0,0 @@
import { forwardRef } from 'react';
import dynamic from 'next/dynamic';
import { Button, Icon } from '@chakra-ui/react';
import { useLGState } from '~/hooks';
import type { ButtonProps } from '@chakra-ui/react';
const ChevronLeft = dynamic<MeronexIcon>(() =>
import('@meronex/icons/fi').then(i => i.FiChevronLeft),
);
export const ResetButton = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
const { isSubmitting } = useLGState();
return (
<Button
ref={ref}
color="current"
variant="ghost"
aria-label="Reset Form"
opacity={isSubmitting.value ? 1 : 0}
{...props}>
<Icon as={ChevronLeft} boxSize={8} />
</Button>
);
});

View File

@@ -1,29 +0,0 @@
import type { IconButtonProps, ButtonProps } from '@chakra-ui/react';
import type { OnChangeArgs } from '~/types';
export interface TCopyButton extends ButtonProps {
copyValue: string;
}
export interface TColorModeToggle extends ButtonProps {
size?: string;
}
export interface TSubmitButton extends Omit<IconButtonProps, 'aria-label'> {
handleChange(e: OnChangeArgs): void;
}
export interface TRequeryButton extends ButtonProps {
requery(): void;
}
export interface TRSubmitButton {
isOpen: boolean;
onClose(): void;
onChange(e: OnChangeArgs): void;
children: React.ReactNode;
}
export interface TPathButton {
onOpen(): void;
}

View File

@@ -1,14 +0,0 @@
import { createContext, useContext } from 'react';
import { createState, useState } from '@hookstate/core';
import type { THeaderCtx, THeaderState } from './types';
const HeaderCtx = createContext<THeaderCtx>({
showSubtitle: true,
titleRef: {} as React.MutableRefObject<HTMLHeadingElement>,
});
export const HeaderProvider = HeaderCtx.Provider;
export const useHeaderCtx = (): THeaderCtx => useContext(HeaderCtx);
const HeaderState = createState<THeaderState>({ fontSize: '' });
export const useHeader = () => useState<THeaderState>(HeaderState);

View File

@@ -1,9 +1,7 @@
import { useRef } from 'react';
import { Flex, ScaleFade } from '@chakra-ui/react';
import { ColorModeToggle } from '~/components';
import { useColorValue, useBreakpointValue } from '~/context';
import { useBooleanValue, useLGState } from '~/hooks';
import { HeaderProvider } from './context';
import { Title } from './title';
import type { THeader } from './types';
@@ -26,35 +24,28 @@ export const Header = (props: THeader) => {
const justify = useBreakpointValue({ base: 'flex-start', lg: 'center' });
return (
<HeaderProvider value={{ showSubtitle: !isSubmitting.value, titleRef }}>
<Flex
px={4}
pt={6}
bg={bg}
minH={16}
zIndex={4}
as="header"
width="full"
flex="0 1 auto"
color="gray.500"
{...rest}>
<ScaleFade in initialScale={0.5} style={{ width: '100%' }}>
<Flex
d="flex"
key="title"
height="100%"
ref={titleRef}
mx={{ base: 0, lg: 'auto' }}
maxW={titleWidth}
// This is here for the logo
justifyContent={justify}>
<Title />
</Flex>
</ScaleFade>
{/* <Flex pos="absolute" right={0} top={0} m={4}>
<ColorModeToggle />
</Flex> */}
</Flex>
</HeaderProvider>
<Flex
px={4}
pt={6}
bg={bg}
minH={16}
zIndex={4}
as="header"
width="full"
flex="0 1 auto"
color="gray.500"
{...rest}>
<ScaleFade in initialScale={0.5} style={{ width: '100%' }}>
<Flex
height="100%"
ref={titleRef}
maxW={titleWidth}
// This is here for the logo
justifyContent={justify}
mx={{ base: 0, lg: 'auto' }}>
<Title />
</Flex>
</ScaleFade>
</Flex>
);
};

View File

@@ -5,7 +5,6 @@ import { useConfig, useMobile } from '~/context';
import { useBooleanValue, useLGState } from '~/hooks';
import { Logo } from './logo';
import { TitleOnly } from './titleOnly';
import { useHeaderCtx } from './context';
import { SubtitleOnly } from './subtitleOnly';
import type { StackProps } from '@chakra-ui/react';
@@ -22,13 +21,13 @@ const MWrapper = (props: StackProps) => <VStack spacing={1} alignItems="flex-sta
* Title wrapper for desktop devices, breakpoints lg & xl.
*/
const DWrapper = (props: TDWrapper) => {
const { showSubtitle } = useHeaderCtx();
const { isSubmitting } = useLGState();
return (
<AnimatedVStack
spacing={1}
initial="main"
alignItems="center"
animate={showSubtitle ? 'main' : 'submitting'}
animate={isSubmitting.value ? 'submitting' : 'main'}
transition={{ damping: 15, type: 'spring', stiffness: 100 }}
variants={{ submitting: { scale: 0.5 }, main: { scale: 1 } }}
{...props}

View File

@@ -1,14 +1,13 @@
import { Heading } from '@chakra-ui/react';
import { useConfig } from '~/context';
import { useBooleanValue } from '~/hooks';
import { useHeaderCtx } from './context';
import { useBooleanValue, useLGState } from '~/hooks';
import { useTitleSize } from './useTitleSize';
export const TitleOnly = () => {
const { showSubtitle } = useHeaderCtx();
const { web } = useConfig();
const { isSubmitting } = useLGState();
const margin = useBooleanValue(showSubtitle, 2, 0);
const margin = useBooleanValue(isSubmitting.value, 0, 2);
const sizeSm = useTitleSize(web.text.title, '2xl', []);
return (

View File

@@ -25,7 +25,3 @@ export interface THeaderCtx {
showSubtitle: boolean;
titleRef: React.MutableRefObject<HTMLHeadingElement>;
}
export interface THeaderState {
fontSize: string;
}

View File

@@ -1,4 +1,3 @@
export * from './buttons';
export * from './card';
export * from './codeBlock';
export * from './countdown';
@@ -18,5 +17,6 @@ export * from './output';
export * from './path';
export * from './results';
export * from './select';
export * from './submit';
export * from './table';
export * from './util';

View File

@@ -15,7 +15,7 @@ export const Chart = (props: TChart) => {
const { data } = props;
const { primary_asn, org_name } = useConfig();
const dots = useColorToken('blackAlpha.500', 'whiteAlpha.400');
const dots = useColorToken('colors', 'blackAlpha.500', 'whiteAlpha.400');
const flowProps = useBreakpointValue<Omit<ReactFlowProps, 'elements'>>({
base: { defaultPosition: [0, 300], defaultZoom: 0 },

View File

@@ -8,9 +8,9 @@ import {
Skeleton,
ModalCloseButton,
} from '@chakra-ui/react';
import { PathButton } from '~/components';
import { useColorValue } from '~/context';
import { useLGState } from '~/hooks';
import { PathButton } from './button';
import { Chart } from './chart';
import type { TPath } from './types';

View File

@@ -23,3 +23,7 @@ export interface BasePath {
asn: string;
name: string;
}
export interface TPathButton {
onOpen(): void;
}

View File

@@ -12,11 +12,13 @@ import {
import { motion } from 'framer-motion';
import { BsLightningFill } from '@meronex/icons/bs';
import { startCase } from 'lodash';
import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If, Path } from '~/components';
import { BGPTable, Countdown, TextOutput, If, Path } from '~/components';
import { useColorValue, useConfig, useMobile } from '~/context';
import { useStrf, useLGQuery, useLGState, useTableToString } from '~/hooks';
import { isStructuredOutput, isStringOutput } from '~/types';
import { isStackError, isFetchError, isLGError } from './guards';
import { RequeryButton } from './requeryButton';
import { CopyButton } from './copyButton';
import { FormattedError } from './error';
import { ResultHeader } from './header';

View File

@@ -18,7 +18,7 @@ export const RequeryButton = forwardRef<HTMLButtonElement, TRequeryButton>((prop
size="sm"
zIndex="1"
variant="ghost"
onClick={requery}
onClick={requery as TRequeryButton['onClick']}
colorScheme="secondary"
{...rest}>
<Icon as={Repeat} boxSize="16px" />

View File

@@ -1,4 +1,5 @@
import type { FlexProps } from '@chakra-ui/react';
import type { ButtonProps, FlexProps } from '@chakra-ui/react';
import type { QueryResultBase } from 'react-query';
import type { TDevice, TQueryTypes } from '~/types';
export interface TResultHeader {
@@ -31,3 +32,11 @@ export interface TResult {
}
export type TErrorLevels = 'success' | 'warning' | 'error';
export interface TCopyButton extends ButtonProps {
copyValue: string;
}
export interface TRequeryButton extends ButtonProps {
requery: QueryResultBase<TQueryResponse>['refetch'];
}

View File

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

View File

@@ -0,0 +1,13 @@
import type { IconButtonProps } from '@chakra-ui/react';
import type { OnChangeArgs } from '~/types';
export interface TSubmitButton extends Omit<IconButtonProps, 'aria-label'> {
handleChange(e: OnChangeArgs): void;
}
export interface TRSubmitButton {
isOpen: boolean;
onClose(): void;
onChange(e: OnChangeArgs): void;
children: React.ReactNode;
}

View File

@@ -1,31 +1,6 @@
import { createState, useState } from '@hookstate/core';
import type { TGlobalState, TUseGlobalState } from './types';
// const StateContext = createContext(null);
// export const StateProvider = ({ children }) => {
// const [isSubmitting, setSubmitting] = useState(false);
// const [formData, setFormData] = useState({});
// const [greetingAck, setGreetingAck] = useSessionStorage('hyperglass-greeting-ack', false);
// const resetForm = layoutRef => {
// layoutRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
// setSubmitting(false);
// setFormData({});
// };
// const value = useMemo(() => ({
// isSubmitting,
// setSubmitting,
// formData,
// setFormData,
// greetingAck,
// setGreetingAck,
// resetForm,
// }));
// return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
// };
// export const useHyperglassState = () => useContext(StateContext);
const defaultFormData = {
query_location: [],
query_target: '',

View File

@@ -31,8 +31,11 @@ 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): string =>
useColorModeValue(useToken('colors', light), useToken('colors', dark));
export const useColorToken = <L extends string, D extends string>(
token: keyof ITheme,
light: L,
dark: D,
): L | D => useColorModeValue(useToken(token, light), useToken(token, dark));
export {
useColorMode,

View File

@@ -5,7 +5,6 @@ export * from './useGreeting';
export * from './useLGQuery';
export * from './useLGState';
export * from './useOpposingColor';
export * from './useSessionStorage';
export * from './useStrf';
export * from './useTableToString';
export * from './useScaledText';

View File

@@ -1,42 +0,0 @@
/*
react-use: useSessionStorage
https://github.com/streamich/react-use/blob/master/src/useSessionStorage.ts
*/
import { useEffect, useState } from 'react';
export const useSessionStorage = (key, initialValue, raw) => {
const isClient = typeof window === 'object';
if (!isClient) {
return [initialValue, () => {}];
}
const [state, setState] = useState(() => {
try {
const sessionStorageValue = sessionStorage.getItem(key);
if (typeof sessionStorageValue !== 'string') {
sessionStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue));
return initialValue;
} else {
return raw ? sessionStorageValue : JSON.parse(sessionStorageValue || 'null');
}
} catch {
// If user is in private mode or has storage restriction
// sessionStorage can throw. JSON.parse and JSON.stringify
// cat throw, too.
return initialValue;
}
});
useEffect(() => {
try {
const serializedState = raw ? String(state) : JSON.stringify(state);
sessionStorage.setItem(key, serializedState);
} catch {
// If user is in private mode or has storage restriction
// sessionStorage can throw. Also JSON.stringify can throw.
}
});
return [state, setState];
};

View File

@@ -1,29 +0,0 @@
declare module 'react-textfit' {
type RenderFunction = (text: string) => React.ReactNode;
interface TextfitProps {
children: React.ReactNode | RenderFunction;
text?: string;
/**
* @default number 1
*/
min?: number;
/**
* @default number 100
*/
max?: number;
/**
* @default single|multi multi
*/
mode?: 'single' | 'multi';
/**
* @default boolean true
*/
forceSingleModeWidth?: boolean;
/**
* @default number 50
*/
throttle?: number;
onReady?: (mid: number) => void;
}
class Textfit extends React.Component<TextfitProps> {}
}