forked from mirrors/thatmattlove-hyperglass
continue typescript & chakra v1 migrations [skip ci]
This commit is contained in:
parent
257f802f3b
commit
0f0e61f403
27 changed files with 363 additions and 310 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { forwardRef } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Button, Icon } from '@chakra-ui/react';
|
||||
import { useGlobalState } from '~/context';
|
||||
import { useLGState } from '~/hooks';
|
||||
|
||||
import type { ButtonProps } from '@chakra-ui/react';
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ const ChevronLeft = dynamic<MeronexIcon>(() =>
|
|||
);
|
||||
|
||||
export const ResetButton = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
|
||||
const { isSubmitting } = useGlobalState();
|
||||
const { isSubmitting } = useLGState();
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
|
|
|
|||
|
|
@ -1,51 +1,110 @@
|
|||
import {
|
||||
IconButton,
|
||||
Modal,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverArrow,
|
||||
PopoverCloseButton,
|
||||
ModalBody,
|
||||
IconButton,
|
||||
PopoverBody,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
PopoverArrow,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
ModalCloseButton,
|
||||
PopoverCloseButton,
|
||||
} from '@chakra-ui/react';
|
||||
import { FiSearch } from '@meronex/icons/fi';
|
||||
import { ResolvedTarget } from '~/components';
|
||||
import { If, ResolvedTarget } from '~/components';
|
||||
import { useMobile } from '~/context';
|
||||
import { useLGState } from '~/hooks';
|
||||
|
||||
import type { TSubmitButton } from './types';
|
||||
import type { IconButtonProps } from '@chakra-ui/react';
|
||||
import type { OnChangeArgs } from '~/types';
|
||||
import type { TSubmitButton, TRSubmitButton } from './types';
|
||||
|
||||
const SubmitIcon = (props: Omit<IconButtonProps, 'aria-label'>) => {
|
||||
const { isLoading } = props;
|
||||
return (
|
||||
<IconButton
|
||||
size="lg"
|
||||
width={16}
|
||||
type="submit"
|
||||
icon={<FiSearch />}
|
||||
title="Submit Query"
|
||||
colorScheme="primary"
|
||||
isLoading={isLoading}
|
||||
aria-label="Submit Query"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mobile Submit Button
|
||||
*/
|
||||
const MSubmitButton = (props: TRSubmitButton) => {
|
||||
const { children, isOpen, onClose, onChange } = props;
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<Modal
|
||||
size="xs"
|
||||
isCentered
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
closeOnEsc={false}
|
||||
closeOnOverlayClick={false}
|
||||
motionPreset="slideInBottom">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalCloseButton />
|
||||
<ModalBody px={4} py={10}>
|
||||
{isOpen && <ResolvedTarget setTarget={onChange} />}
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Desktop Submit Button
|
||||
*/
|
||||
const DSubmitButton = (props: TRSubmitButton) => {
|
||||
const { children, isOpen, onClose, onChange } = props;
|
||||
return (
|
||||
<Popover isOpen={isOpen} onClose={onClose} closeOnBlur={false}>
|
||||
<PopoverTrigger>{children}</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverCloseButton />
|
||||
<PopoverBody p={6}>{isOpen && <ResolvedTarget setTarget={onChange} />}</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export const SubmitButton = (props: TSubmitButton) => {
|
||||
const { children, handleChange, ...rest } = props;
|
||||
const { btnLoading, resolvedIsOpen, resolvedClose } = useLGState();
|
||||
const { handleChange } = props;
|
||||
const { btnLoading, resolvedIsOpen, resolvedClose, resetForm } = useLGState();
|
||||
const isMobile = useMobile();
|
||||
|
||||
function handleClose(): void {
|
||||
btnLoading.set(false);
|
||||
resetForm();
|
||||
resolvedClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover isOpen={resolvedIsOpen.value} onClose={handleClose} closeOnBlur={false}>
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
size="lg"
|
||||
width={16}
|
||||
type="submit"
|
||||
icon={<FiSearch />}
|
||||
title="Submit Query"
|
||||
aria-label="Submit Query"
|
||||
colorScheme="primary"
|
||||
isLoading={btnLoading.value}
|
||||
{...rest}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverCloseButton />
|
||||
<PopoverBody p={6}>
|
||||
{resolvedIsOpen.value && <ResolvedTarget setTarget={handleChange} />}
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<If c={isMobile}>
|
||||
<MSubmitButton isOpen={resolvedIsOpen.value} onClose={handleClose} onChange={handleChange}>
|
||||
<SubmitIcon isLoading={btnLoading.value} />
|
||||
</MSubmitButton>
|
||||
</If>
|
||||
<If c={!isMobile}>
|
||||
<DSubmitButton isOpen={resolvedIsOpen.value} onClose={handleClose} onChange={handleChange}>
|
||||
<SubmitIcon isLoading={btnLoading.value} />
|
||||
</DSubmitButton>
|
||||
</If>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,3 +16,10 @@ export interface TSubmitButton extends Omit<IconButtonProps, 'aria-label'> {
|
|||
export interface TRequeryButton extends ButtonProps {
|
||||
requery(): void;
|
||||
}
|
||||
|
||||
export interface TRSubmitButton {
|
||||
isOpen: boolean;
|
||||
onClose(): void;
|
||||
onChange(e: OnChangeArgs): void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
Tag,
|
||||
Modal,
|
||||
Stack,
|
||||
HStack,
|
||||
Button,
|
||||
useTheme,
|
||||
ModalBody,
|
||||
|
|
@ -14,6 +14,30 @@ import {
|
|||
} from '@chakra-ui/react';
|
||||
import { useConfig, useColorValue, useBreakpointValue } from '~/context';
|
||||
import { CodeBlock } from '~/components';
|
||||
import type { UseDisclosureReturn } from '@chakra-ui/react';
|
||||
|
||||
interface TViewer extends Pick<UseDisclosureReturn, 'isOpen' | 'onClose'> {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Viewer = (props: TViewer) => {
|
||||
const { title, isOpen, onClose, children } = props;
|
||||
const bg = useColorValue('white', 'black');
|
||||
const color = useColorValue('black', 'white');
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="full" scrollBehavior="inside">
|
||||
<ModalOverlay />
|
||||
<ModalContent bg={bg} color={color} py={4} borderRadius="md" maxW="90%">
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<CodeBlock>{children}</CodeBlock>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const Debugger = () => {
|
||||
const { isOpen: configOpen, onOpen: onConfigOpen, onClose: configClose } = useDisclosure();
|
||||
|
|
@ -21,17 +45,16 @@ export const Debugger = () => {
|
|||
const { colorMode } = useColorMode();
|
||||
const config = useConfig();
|
||||
const theme = useTheme();
|
||||
const bg = useColorValue('white', 'black');
|
||||
const color = useColorValue('black', 'white');
|
||||
const borderColor = useColorValue('gray.100', 'gray.600');
|
||||
const mediaSize =
|
||||
useBreakpointValue({ base: 'SMALL', md: 'MEDIUM', lg: 'LARGE', xl: 'X-LARGE' }) ?? 'UNKNOWN';
|
||||
const tagSize = useBreakpointValue({ base: 'sm', lg: 'lg' }) ?? 'lg';
|
||||
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' }) ?? 'sm';
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
<HStack
|
||||
py={4}
|
||||
px={4}
|
||||
isInline
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
|
|
@ -40,36 +63,27 @@ export const Debugger = () => {
|
|||
borderWidth="1px"
|
||||
position="relative"
|
||||
justifyContent="center"
|
||||
borderColor={borderColor}>
|
||||
<Tag colorScheme="gray">{colorMode.toUpperCase()}</Tag>
|
||||
<Tag colorScheme="teal">{mediaSize}</Tag>
|
||||
<Button size="sm" colorScheme="cyan" onClick={onConfigOpen}>
|
||||
borderColor={borderColor}
|
||||
spacing={{ base: 2, lg: 8 }}>
|
||||
<Tag size={tagSize} colorScheme="gray">
|
||||
{colorMode.toUpperCase()}
|
||||
</Tag>
|
||||
<Button size={btnSize} colorScheme="blue" onClick={onConfigOpen}>
|
||||
View Config
|
||||
</Button>
|
||||
<Button size="sm" colorScheme="purple" onClick={onThemeOpen}>
|
||||
<Button size={btnSize} colorScheme="red" onClick={onThemeOpen}>
|
||||
View Theme
|
||||
</Button>
|
||||
</Stack>
|
||||
<Modal isOpen={configOpen} onClose={configClose} size="full">
|
||||
<ModalOverlay />
|
||||
<ModalContent bg={bg} color={color} py={4} borderRadius="md" maxW="90%">
|
||||
<ModalHeader>Loaded Configuration</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<CodeBlock>{JSON.stringify(config, null, 4)}</CodeBlock>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Modal isOpen={themeOpen} onClose={themeClose} size="full">
|
||||
<ModalOverlay />
|
||||
<ModalContent bg={bg} color={color} py={4} borderRadius="md" maxW="90%">
|
||||
<ModalHeader>Loaded Theme</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<CodeBlock>{JSON.stringify(theme, null, 4)}</CodeBlock>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Tag size={tagSize} colorScheme="teal">
|
||||
{mediaSize}
|
||||
</Tag>
|
||||
</HStack>
|
||||
<Viewer isOpen={configOpen} onClose={configClose} title="Config">
|
||||
{JSON.stringify(config, null, 4)}
|
||||
</Viewer>
|
||||
<Viewer isOpen={themeOpen} onClose={themeClose} title="Theme">
|
||||
{JSON.stringify(theme, null, 4)}
|
||||
</Viewer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const Option = (props: OptionProps<Dict, false>) => {
|
|||
return (
|
||||
<components.Option {...props}>
|
||||
<Text as="span">{label}</Text>
|
||||
<br />
|
||||
<Text fontSize="xs" as="span">
|
||||
{data.description}
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -1,34 +1,24 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Flex, FormControl, FormLabel, FormErrorMessage } from '@chakra-ui/react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { If } from '~/components';
|
||||
import { useColorValue } from '~/context';
|
||||
import { useBooleanValue } from '~/hooks';
|
||||
|
||||
import { TField } from './types';
|
||||
import { TField, TFormError } from './types';
|
||||
|
||||
export const FormField = (props: TField) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
errors,
|
||||
children,
|
||||
labelAddOn,
|
||||
fieldAddOn,
|
||||
hiddenLabels = false,
|
||||
...rest
|
||||
} = props;
|
||||
const { name, label, children, labelAddOn, fieldAddOn, hiddenLabels = false, ...rest } = props;
|
||||
const labelColor = useColorValue('blackAlpha.700', 'whiteAlpha.700');
|
||||
const errorColor = useColorValue('red.500', 'red.300');
|
||||
const opacity = useBooleanValue(hiddenLabels, 0, undefined);
|
||||
|
||||
const error = useMemo<string | undefined>(() => {
|
||||
let result;
|
||||
if (Array.isArray(errors)) {
|
||||
result = errors.join(', ');
|
||||
} else if (typeof errors === 'string') {
|
||||
result = errors;
|
||||
}
|
||||
return result;
|
||||
}, [errors]);
|
||||
const { errors } = useFormContext();
|
||||
|
||||
const error = name in errors && (errors[name] as TFormError);
|
||||
|
||||
if (error !== false) {
|
||||
console.warn(`${label} Error: ${error.message}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
|
|
@ -38,7 +28,7 @@ export const FormField = (props: TField) => {
|
|||
maxW="100%"
|
||||
flexDir="column"
|
||||
my={{ base: 2, lg: 4 }}
|
||||
isInvalid={typeof error !== 'undefined'}
|
||||
isInvalid={error !== false}
|
||||
flex={{ base: '1 0 100%', lg: '1 0 33.33%' }}
|
||||
{...rest}>
|
||||
<FormLabel
|
||||
|
|
@ -47,7 +37,7 @@ export const FormField = (props: TField) => {
|
|||
htmlFor={name}
|
||||
display="flex"
|
||||
opacity={opacity}
|
||||
color={labelColor}
|
||||
color={error !== false ? errorColor : labelColor}
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
{label}
|
||||
|
|
@ -59,7 +49,7 @@ export const FormField = (props: TField) => {
|
|||
{fieldAddOn}
|
||||
</Flex>
|
||||
</If>
|
||||
<FormErrorMessage opacity={opacity}>{error}</FormErrorMessage>
|
||||
<FormErrorMessage opacity={opacity}>{error && error.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Select } from '~/components';
|
||||
import { useConfig } from '~/context';
|
||||
|
||||
|
|
@ -21,6 +22,8 @@ export const QueryLocation = (props: TQuerySelectField) => {
|
|||
const { onChange, label } = props;
|
||||
|
||||
const { networks } = useConfig();
|
||||
const { errors } = useFormContext();
|
||||
|
||||
const options = useMemo(() => buildOptions(networks), [networks.length]);
|
||||
|
||||
function handleChange(e: TSelectOption | TSelectOption[]): void {
|
||||
|
|
@ -44,6 +47,7 @@ export const QueryLocation = (props: TQuerySelectField) => {
|
|||
name="query_location"
|
||||
onChange={handleChange}
|
||||
closeMenuOnSelect={false}
|
||||
isError={typeof errors.query_location !== 'undefined'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,69 +1,42 @@
|
|||
import { useEffect } from 'react';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { useColorValue } from '~/context';
|
||||
import { useLGState } from '~/hooks';
|
||||
|
||||
import type { TQueryTarget } from './types';
|
||||
|
||||
const fqdnPattern = /^(?!:\/\/)([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-][a-zA-Z0-9-]+\.[a-zA-Z-]{2,6}?$/gim;
|
||||
|
||||
export const QueryTarget = (props: TQueryTarget) => {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
setFqdn,
|
||||
register,
|
||||
setTarget,
|
||||
unregister,
|
||||
placeholder,
|
||||
displayValue,
|
||||
resolveTarget,
|
||||
setDisplayValue,
|
||||
} = props;
|
||||
const { name, register, setTarget, placeholder, resolveTarget } = props;
|
||||
|
||||
const bg = useColorValue('white', 'whiteAlpha.100');
|
||||
const color = useColorValue('gray.400', 'whiteAlpha.800');
|
||||
const border = useColorValue('gray.100', 'whiteAlpha.50');
|
||||
const placeholderColor = useColorValue('gray.600', 'whiteAlpha.700');
|
||||
|
||||
function handleBlur(): void {
|
||||
if (resolveTarget && displayValue && fqdnPattern.test(displayValue)) {
|
||||
setFqdn(displayValue);
|
||||
} else if (resolveTarget && !displayValue) {
|
||||
setFqdn(null);
|
||||
}
|
||||
}
|
||||
const { queryTarget, fqdnTarget, displayTarget } = useLGState();
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setDisplayValue(e.target.value);
|
||||
displayTarget.set(e.target.value);
|
||||
setTarget({ field: name, value: e.target.value });
|
||||
}
|
||||
|
||||
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (['Tab', 'NumpadEnter'].includes(e.key)) {
|
||||
handleBlur();
|
||||
if (resolveTarget && displayTarget.value && fqdnPattern.test(displayTarget.value)) {
|
||||
fqdnTarget.set(displayTarget.value);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
register({ name });
|
||||
return () => unregister(name);
|
||||
}, [register, unregister, name]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<input hidden readOnly name={name} ref={register} value={value} />
|
||||
<input hidden readOnly name={name} ref={register} value={queryTarget.value} />
|
||||
<Input
|
||||
bg={bg}
|
||||
size="lg"
|
||||
color={color}
|
||||
borderRadius="md"
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleBlur}
|
||||
value={displayValue}
|
||||
value={displayTarget.value}
|
||||
borderColor={border}
|
||||
onChange={handleChange}
|
||||
aria-label={placeholder}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={placeholder}
|
||||
name="query_target_display"
|
||||
_placeholder={{ color: placeholderColor }}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Select } from '~/components';
|
||||
import { useConfig } from '~/context';
|
||||
|
||||
|
|
@ -14,6 +15,7 @@ function buildOptions(queryTypes: TQuery[]): TSelectOption[] {
|
|||
export const QueryType = (props: TQuerySelectField) => {
|
||||
const { onChange, label } = props;
|
||||
const { queries } = useConfig();
|
||||
const { errors } = useFormContext();
|
||||
|
||||
const options = useMemo(() => buildOptions(queries.list), [queries.list.length]);
|
||||
|
||||
|
|
@ -30,6 +32,7 @@ export const QueryType = (props: TQuerySelectField) => {
|
|||
options={options}
|
||||
aria-label={label}
|
||||
onChange={handleChange}
|
||||
isError={typeof errors.query_type !== 'undefined'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useEffect, useMemo } from 'react';
|
|||
import { Button, Stack, Text, VStack } from '@chakra-ui/react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { FiArrowRightCircle as RightArrow } from '@meronex/icons/fi';
|
||||
import { useConfig, useColorValue, useGlobalState } from '~/context';
|
||||
import { useConfig, useColorValue } from '~/context';
|
||||
import { useStrf, useLGState } from '~/hooks';
|
||||
|
||||
import type { DnsOverHttps } from '~/types';
|
||||
|
|
@ -20,8 +20,7 @@ function findAnswer(data: DnsOverHttps.Response | undefined): string {
|
|||
export const ResolvedTarget = (props: TResolvedTarget) => {
|
||||
const { setTarget } = props;
|
||||
const { web } = useConfig();
|
||||
const { isSubmitting } = useGlobalState();
|
||||
const { fqdnTarget, queryTarget, families, formData } = useLGState();
|
||||
const { fqdnTarget, isSubmitting, families, formData } = useLGState();
|
||||
|
||||
const color = useColorValue('secondary.500', 'secondary.300');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import type { FormControlProps } from '@chakra-ui/react';
|
||||
import type { FieldError, Control } from 'react-hook-form';
|
||||
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs, TFormData } from '~/types';
|
||||
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs } from '~/types';
|
||||
import type { ValidationError } from 'yup';
|
||||
|
||||
export type TFormError = Pick<ValidationError, 'message' | 'type'>;
|
||||
|
||||
export interface TField extends FormControlProps {
|
||||
name: string;
|
||||
label: string;
|
||||
errors?: FieldError | FieldError[];
|
||||
hiddenLabels?: boolean;
|
||||
labelAddOn?: React.ReactNode;
|
||||
fieldAddOn?: React.ReactNode;
|
||||
|
|
@ -30,32 +32,12 @@ export interface TCommunitySelect {
|
|||
unregister: Control['unregister'];
|
||||
}
|
||||
|
||||
/**
|
||||
* placeholder,
|
||||
register,
|
||||
unregister,
|
||||
setFqdn,
|
||||
|
||||
name,
|
||||
value,
|
||||
|
||||
setTarget,
|
||||
resolveTarget,
|
||||
|
||||
displayValue,
|
||||
setDisplayValue,
|
||||
*/
|
||||
export interface TQueryTarget {
|
||||
name: string;
|
||||
placeholder: string;
|
||||
displayValue: string;
|
||||
resolveTarget: boolean;
|
||||
setFqdn(f: string | null): void;
|
||||
setTarget(e: OnChangeArgs): void;
|
||||
register: Control['register'];
|
||||
value: TFormData['query_target'];
|
||||
setDisplayValue(d: string): void;
|
||||
unregister: Control['unregister'];
|
||||
setTarget(e: OnChangeArgs): void;
|
||||
}
|
||||
|
||||
export interface TResolvedTarget {
|
||||
|
|
|
|||
|
|
@ -11,31 +11,40 @@ import {
|
|||
} from '@chakra-ui/react';
|
||||
import { If, Markdown } from '~/components';
|
||||
import { useConfig, useColorValue } from '~/context';
|
||||
import { useGreeting, useOpposingColor } from '~/hooks';
|
||||
|
||||
import type { TGreeting } from './types';
|
||||
|
||||
export const Greeting = (props: TGreeting) => {
|
||||
const { onClickThrough, ...rest } = props;
|
||||
const { web, content } = useConfig();
|
||||
const { isOpen, onClose } = useDisclosure();
|
||||
const [greetingAck, setGreetingAck] = useGreeting();
|
||||
|
||||
const bg = useColorValue('white', 'black');
|
||||
const color = useColorValue('black', 'white');
|
||||
const bg = useColorValue('white', 'gray.800');
|
||||
const color = useOpposingColor(bg);
|
||||
|
||||
function handleClick(): void {
|
||||
onClickThrough();
|
||||
onClose();
|
||||
return;
|
||||
function handleClose(ack: boolean = false): void {
|
||||
if (web.greeting.required && !greetingAck && !ack) {
|
||||
setGreetingAck(false);
|
||||
} else if (web.greeting.required && !greetingAck && ack) {
|
||||
setGreetingAck();
|
||||
onClose();
|
||||
} else if (web.greeting.required && greetingAck) {
|
||||
onClose();
|
||||
} else if (!web.greeting.required) {
|
||||
setGreetingAck();
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="lg"
|
||||
isCentered
|
||||
size="full"
|
||||
isOpen={isOpen}
|
||||
onClose={handleClick}
|
||||
closeOnEsc={!web.greeting.required}
|
||||
closeOnOverlayClick={!web.greeting.required}>
|
||||
onClose={handleClose}
|
||||
motionPreset="slideInBottom"
|
||||
closeOnEsc={web.greeting.required}
|
||||
isOpen={!greetingAck ? true : isOpen}
|
||||
closeOnOverlayClick={web.greeting.required}>
|
||||
<ModalOverlay />
|
||||
<ModalContent
|
||||
py={4}
|
||||
|
|
@ -43,7 +52,7 @@ export const Greeting = (props: TGreeting) => {
|
|||
color={color}
|
||||
borderRadius="md"
|
||||
maxW={{ base: '95%', md: '75%' }}
|
||||
{...rest}>
|
||||
{...props}>
|
||||
<ModalHeader>{web.greeting.title}</ModalHeader>
|
||||
<If c={!web.greeting.required}>
|
||||
<ModalCloseButton />
|
||||
|
|
@ -52,7 +61,7 @@ export const Greeting = (props: TGreeting) => {
|
|||
<Markdown content={content.greeting} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button colorScheme="primary" onClick={handleClick}>
|
||||
<Button colorScheme="primary" onClick={() => handleClose(true)}>
|
||||
{web.greeting.button}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { BoxProps } from '@chakra-ui/react';
|
||||
|
||||
export interface TGreeting extends BoxProps {
|
||||
onClickThrough(): void;
|
||||
}
|
||||
export interface TGreeting extends BoxProps {}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Flex } from '@chakra-ui/react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { AnimatedDiv, Title, ResetButton, ColorModeToggle } from '~/components';
|
||||
import { useColorValue, useConfig, useGlobalState, useBreakpointValue } from '~/context';
|
||||
import { useBooleanValue } from '~/hooks';
|
||||
import { useColorValue, useConfig, useBreakpointValue } from '~/context';
|
||||
import { useBooleanValue, useLGState } from '~/hooks';
|
||||
|
||||
import type { ResponsiveValue } from '@chakra-ui/react';
|
||||
import type { THeader, TTitleMode, THeaderLayout } from './types';
|
||||
|
|
@ -39,7 +39,7 @@ export const Header = (props: THeader) => {
|
|||
const bg = useColorValue('white', 'black');
|
||||
|
||||
const { web } = useConfig();
|
||||
const { isSubmitting } = useGlobalState();
|
||||
const { isSubmitting } = useLGState();
|
||||
|
||||
const mlResetButton = useBooleanValue(isSubmitting.value, { base: 0, md: 2 }, undefined);
|
||||
const titleHeight = useBooleanValue(isSubmitting.value, undefined, { md: '20vh' });
|
||||
|
|
|
|||
|
|
@ -1,33 +1,31 @@
|
|||
import { useRef } from 'react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useConfig, useColorValue, useGlobalState } from '~/context';
|
||||
import { useConfig, useColorValue } from '~/context';
|
||||
import { If, Debugger, Greeting, Footer, Header } from '~/components';
|
||||
import { useGreeting } from '~/hooks';
|
||||
import { useLGState } from '~/hooks';
|
||||
|
||||
import type { TFrame } from './types';
|
||||
|
||||
export const Frame = (props: TFrame) => {
|
||||
const { web, developer_mode } = useConfig();
|
||||
const { isSubmitting, formData } = useGlobalState();
|
||||
const [greetingAck, setGreetingAck] = useGreeting();
|
||||
const { developer_mode } = useConfig();
|
||||
const { isSubmitting, resetForm } = useLGState();
|
||||
|
||||
const bg = useColorValue('white', 'black');
|
||||
const color = useColorValue('black', 'white');
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>({} as HTMLDivElement);
|
||||
|
||||
function resetForm(): void {
|
||||
function handleReset(): void {
|
||||
containerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
isSubmitting.set(false);
|
||||
formData.set({ query_location: [], query_target: '', query_type: '', query_vrf: '' });
|
||||
return;
|
||||
resetForm();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex bg={bg} w="100%" color={color} flexDir="column" minHeight="100vh" ref={containerRef}>
|
||||
<Flex px={2} flex="0 1 auto" flexDirection="column">
|
||||
<Header resetForm={resetForm} />
|
||||
<Header resetForm={handleReset} />
|
||||
</Flex>
|
||||
<Flex
|
||||
px={2}
|
||||
|
|
@ -46,9 +44,7 @@ export const Frame = (props: TFrame) => {
|
|||
<Debugger />
|
||||
</If>
|
||||
</Flex>
|
||||
<If c={web.greeting.enable && !greetingAck}>
|
||||
<Greeting onClickThrough={setGreetingAck} />
|
||||
</If>
|
||||
<Greeting />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import { AnimatePresence } from 'framer-motion';
|
||||
import { If, HyperglassForm, Results } from '~/components';
|
||||
import { useGlobalState } from '~/context';
|
||||
import { useLGState } from '~/hooks';
|
||||
import { all } from '~/util';
|
||||
import { Frame } from './frame';
|
||||
|
||||
export const Layout: React.FC = () => {
|
||||
const { isSubmitting } = useGlobalState();
|
||||
const { formData } = useLGState();
|
||||
const { isSubmitting, formData } = useLGState();
|
||||
return (
|
||||
<Frame>
|
||||
<If
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { intersectionWith } from 'lodash';
|
||||
import * as yup from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import {
|
||||
If,
|
||||
AnimatedForm,
|
||||
|
|
@ -16,7 +17,7 @@ import {
|
|||
QueryLocation,
|
||||
CommunitySelect,
|
||||
} from '~/components';
|
||||
import { useConfig, useGlobalState } from '~/context';
|
||||
import { useConfig } from '~/context';
|
||||
import { useStrf, useGreeting, useDevice, useLGState } from '~/hooks';
|
||||
import { isQueryType, isString } from '~/types';
|
||||
|
||||
|
|
@ -27,7 +28,6 @@ const fqdnPattern = /^(?!:\/\/)([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-][a-zA-Z0-9-]+\.[a-z
|
|||
export const HyperglassForm = () => {
|
||||
const { web, content, messages, queries } = useConfig();
|
||||
|
||||
const { isSubmitting } = useGlobalState();
|
||||
const [greetingAck, setGreetingAck] = useGreeting();
|
||||
const getDevice = useDevice();
|
||||
|
||||
|
|
@ -42,23 +42,24 @@ export const HyperglassForm = () => {
|
|||
query_target: yup.string().required(noQueryTarget),
|
||||
});
|
||||
|
||||
const { handleSubmit, register, unregister, setValue, errors, reset } = useForm<TFormData>({
|
||||
validationSchema: formSchema,
|
||||
const formInstance = useForm<TFormData>({
|
||||
resolver: yupResolver(formSchema),
|
||||
defaultValues: { query_vrf: 'default', query_target: '', query_location: [], query_type: '' },
|
||||
});
|
||||
const { handleSubmit, register, unregister, setValue, errors } = formInstance;
|
||||
|
||||
const {
|
||||
queryVrf,
|
||||
families,
|
||||
formData,
|
||||
queryType,
|
||||
availVrfs,
|
||||
fqdnTarget,
|
||||
btnLoading,
|
||||
queryTarget,
|
||||
isSubmitting,
|
||||
resolvedOpen,
|
||||
queryLocation,
|
||||
displayTarget,
|
||||
formData,
|
||||
} = useLGState();
|
||||
|
||||
function submitHandler(values: TFormData) {
|
||||
|
|
@ -150,96 +151,81 @@ export const HyperglassForm = () => {
|
|||
|
||||
const isFqdnQuery = useMemo(() => {
|
||||
return ['bgp_route', 'ping', 'traceroute'].includes(queryType.value);
|
||||
}, [queryType]);
|
||||
|
||||
const fqdnQuery = useMemo(() => {
|
||||
let result = null;
|
||||
if (fqdnTarget && queryVrf.value === 'default' && fqdnTarget) {
|
||||
result = fqdnTarget;
|
||||
}
|
||||
return result;
|
||||
}, [queryVrf, queryType]);
|
||||
}, [queryType.value]);
|
||||
|
||||
useEffect(() => {
|
||||
register({ name: 'query_location', required: true });
|
||||
register({ name: 'query_type', required: true });
|
||||
register({ name: 'query_vrf' });
|
||||
register({ name: 'query_target', required: true });
|
||||
}, [register]);
|
||||
|
||||
Object.keys(errors).length >= 1 && console.error(errors);
|
||||
|
||||
return (
|
||||
<AnimatedForm
|
||||
p={0}
|
||||
my={4}
|
||||
w="100%"
|
||||
mx="auto"
|
||||
textAlign="left"
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
exit={{ opacity: 0, x: -300 }}
|
||||
initial={{ opacity: 0, y: 300 }}
|
||||
onSubmit={handleSubmit(submitHandler)}
|
||||
maxW={{ base: '100%', lg: '75%' }}>
|
||||
<FormRow>
|
||||
<FormField
|
||||
name="query_location"
|
||||
errors={errors.query_location}
|
||||
label={web.text.query_location}>
|
||||
<QueryLocation onChange={handleChange} label={web.text.query_location} />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="query_type"
|
||||
errors={errors.query_type}
|
||||
label={web.text.query_type}
|
||||
labelAddOn={vrfContent && <HelpModal item={vrfContent} name="query_type" />}>
|
||||
<QueryType onChange={handleChange} label={web.text.query_type} />
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<If c={availVrfs.length > 1}>
|
||||
<FormField label={web.text.query_vrf} name="query_vrf" errors={errors.query_vrf}>
|
||||
<QueryVrf label={web.text.query_vrf} vrfs={availVrfs.value} onChange={handleChange} />
|
||||
<FormProvider {...formInstance}>
|
||||
<AnimatedForm
|
||||
p={0}
|
||||
my={4}
|
||||
w="100%"
|
||||
mx="auto"
|
||||
textAlign="left"
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
exit={{ opacity: 0, x: -300 }}
|
||||
initial={{ opacity: 0, y: 300 }}
|
||||
maxW={{ base: '100%', lg: '75%' }}
|
||||
onSubmit={handleSubmit(submitHandler)}>
|
||||
<FormRow>
|
||||
<FormField name="query_location" label={web.text.query_location}>
|
||||
<QueryLocation onChange={handleChange} label={web.text.query_location} />
|
||||
</FormField>
|
||||
</If>
|
||||
<FormField name="query_target" errors={errors.query_target} label={web.text.query_target}>
|
||||
<If c={queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select'}>
|
||||
<CommunitySelect
|
||||
name="query_target"
|
||||
register={register}
|
||||
unregister={unregister}
|
||||
onChange={handleChange}
|
||||
communities={queries.bgp_community.communities}
|
||||
/>
|
||||
<FormField
|
||||
name="query_type"
|
||||
label={web.text.query_type}
|
||||
labelAddOn={vrfContent && <HelpModal item={vrfContent} name="query_type" />}>
|
||||
<QueryType onChange={handleChange} label={web.text.query_type} />
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<If c={availVrfs.length > 1}>
|
||||
<FormField label={web.text.query_vrf} name="query_vrf">
|
||||
<QueryVrf label={web.text.query_vrf} vrfs={availVrfs.value} onChange={handleChange} />
|
||||
</FormField>
|
||||
</If>
|
||||
<If c={!(queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select')}>
|
||||
<QueryTarget
|
||||
name="query_target"
|
||||
register={register}
|
||||
value={queryTarget.value}
|
||||
unregister={unregister}
|
||||
setFqdn={fqdnTarget.set}
|
||||
setTarget={handleChange}
|
||||
resolveTarget={isFqdnQuery}
|
||||
displayValue={displayTarget.value}
|
||||
setDisplayValue={displayTarget.set}
|
||||
placeholder={web.text.query_target}
|
||||
/>
|
||||
</If>
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow mt={0} justifyContent="flex-end">
|
||||
<Flex
|
||||
my={2}
|
||||
w="100%"
|
||||
ml="auto"
|
||||
maxW="100%"
|
||||
flex="0 0 0"
|
||||
flexDir="column"
|
||||
mr={{ base: 0, lg: 2 }}>
|
||||
<SubmitButton handleChange={handleChange} />
|
||||
</Flex>
|
||||
</FormRow>
|
||||
</AnimatedForm>
|
||||
<FormField name="query_target" label={web.text.query_target}>
|
||||
<If c={queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select'}>
|
||||
<CommunitySelect
|
||||
name="query_target"
|
||||
register={register}
|
||||
unregister={unregister}
|
||||
onChange={handleChange}
|
||||
communities={queries.bgp_community.communities}
|
||||
/>
|
||||
</If>
|
||||
<If
|
||||
c={!(queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select')}>
|
||||
<QueryTarget
|
||||
name="query_target"
|
||||
register={register}
|
||||
setTarget={handleChange}
|
||||
resolveTarget={isFqdnQuery}
|
||||
placeholder={web.text.query_target}
|
||||
/>
|
||||
</If>
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow mt={0} justifyContent="flex-end">
|
||||
<Flex
|
||||
my={2}
|
||||
w="100%"
|
||||
ml="auto"
|
||||
maxW="100%"
|
||||
flex="0 0 0"
|
||||
flexDir="column"
|
||||
mr={{ base: 0, lg: 2 }}>
|
||||
<SubmitButton handleChange={handleChange} />
|
||||
</Flex>
|
||||
</FormRow>
|
||||
</AnimatedForm>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -147,23 +147,16 @@ export const Results = () => {
|
|||
queryLocation.map((loc, i) => {
|
||||
const device = getDevice(loc.value);
|
||||
return (
|
||||
<motion.div
|
||||
key={loc.value}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
initial={{ opacity: 0, y: 300 }}
|
||||
transition={{ duration: 0.3, delay: i * 0.3 }}
|
||||
exit={{ opacity: 0, y: 300 }}>
|
||||
<Result
|
||||
index={i}
|
||||
device={device}
|
||||
queryLocation={loc.value}
|
||||
queryVrf={queryVrf.value}
|
||||
setComplete={setComplete}
|
||||
queryType={queryType.value}
|
||||
queryTarget={queryTarget.value}
|
||||
resultsComplete={resultsComplete}
|
||||
/>
|
||||
</motion.div>
|
||||
<Result
|
||||
index={i}
|
||||
device={device}
|
||||
queryLocation={loc.value}
|
||||
queryVrf={queryVrf.value}
|
||||
setComplete={setComplete}
|
||||
queryType={queryType.value}
|
||||
queryTarget={queryTarget.value}
|
||||
resultsComplete={resultsComplete}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
AccordionPanel,
|
||||
AccordionButton,
|
||||
} from '@chakra-ui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { BsLightningFill } from '@meronex/icons/bs';
|
||||
import { startCase } from 'lodash';
|
||||
import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If } from '~/components';
|
||||
|
|
@ -21,6 +22,8 @@ import { isStackError, isFetchError, isLGError } from './guards';
|
|||
|
||||
import type { TAccordionHeaderWrapper, TResult, TErrorLevels } from './types';
|
||||
|
||||
const AnimatedAccordionItem = motion.custom(AccordionItem);
|
||||
|
||||
const AccordionHeaderWrapper = (props: TAccordionHeaderWrapper) => {
|
||||
const { hoverBg, ...rest } = props;
|
||||
return (
|
||||
|
|
@ -143,9 +146,13 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||
}, [resultsComplete, index]);
|
||||
|
||||
return (
|
||||
<AccordionItem
|
||||
<AnimatedAccordionItem
|
||||
ref={ref}
|
||||
isDisabled={isLoading}
|
||||
exit={{ opacity: 0, y: 300 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
initial={{ opacity: 0, y: 300 }}
|
||||
transition={{ duration: 0.3, delay: index * 0.3 }}
|
||||
css={{
|
||||
'&:last-of-type': { borderBottom: 'none' },
|
||||
'&:first-of-type': { borderTop: 'none' },
|
||||
|
|
@ -234,6 +241,6 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||
</Flex>
|
||||
</Flex>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</AnimatedAccordionItem>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,12 +26,16 @@ export const useSelectContext = () => useContext(SelectContext);
|
|||
const ReactSelectAsBox = (props: TBoxAsReactSelect) => <Box as={ReactSelect} {...props} />;
|
||||
|
||||
export const Select = (props: TSelectBase) => {
|
||||
const { ctl, options, multi, onSelect, ...rest } = props;
|
||||
const { ctl, options, multi, onSelect, isError = false, ...rest } = props;
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const selectContext = useMemo<TSelectContext>(() => ({ colorMode, isOpen }), [colorMode, isOpen]);
|
||||
const selectContext = useMemo<TSelectContext>(() => ({ colorMode, isOpen, isError }), [
|
||||
colorMode,
|
||||
isError,
|
||||
isOpen,
|
||||
]);
|
||||
|
||||
const handleChange = (changed: TSelectOption | TSelectOption[]) => {
|
||||
if (!Array.isArray(changed)) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import type {
|
|||
|
||||
export const useControlStyle = (base: TStyles, state: TControl): TStyles => {
|
||||
const { isFocused } = state;
|
||||
const { colorMode } = useSelectContext();
|
||||
const { colorMode, isError } = useSelectContext();
|
||||
const borderHover = useColorValue(
|
||||
useToken('colors', 'gray.300'),
|
||||
useToken('colors', 'whiteAlpha.400'),
|
||||
|
|
@ -41,14 +41,18 @@ export const useControlStyle = (base: TStyles, state: TControl): TStyles => {
|
|||
color,
|
||||
minHeight,
|
||||
transition: 'all 0.2s',
|
||||
borderColor: isFocused ? focusBorder : borderColor,
|
||||
boxShadow: isFocused ? `0 0 0 1px ${focusBorder}` : undefined,
|
||||
borderColor: isError ? invalidBorder : isFocused ? focusBorder : borderColor,
|
||||
boxShadow: isError
|
||||
? `0 0 0 1px ${invalidBorder}`
|
||||
: isFocused
|
||||
? `0 0 0 1px ${focusBorder}`
|
||||
: undefined,
|
||||
'&:hover': { borderColor: isFocused ? focusBorder : borderHover },
|
||||
'&:hover > div > span': { backgroundColor: borderHover },
|
||||
'&:focus': { borderColor: focusBorder },
|
||||
'&:focus': { borderColor: isError ? invalidBorder : focusBorder },
|
||||
'&.invalid': { borderColor: invalidBorder, boxShadow: `0 0 0 1px ${invalidBorder}` },
|
||||
};
|
||||
return useMemo(() => mergeWith({}, base, styles), [colorMode, isFocused]);
|
||||
return useMemo(() => mergeWith({}, base, styles), [colorMode, isFocused, isError]);
|
||||
};
|
||||
|
||||
export const useMenuStyle = (base: TStyles, state: TMenu): TStyles => {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export type TBoxAsReactSelect = Omit<IReactSelect, 'isMulti' | 'onSelect' | 'onC
|
|||
export interface TSelectBase extends TBoxAsReactSelect {
|
||||
name: string;
|
||||
multi?: boolean;
|
||||
isError?: boolean;
|
||||
options: TOptions;
|
||||
required?: boolean;
|
||||
onSelect?: (s: TSelectOption[]) => void;
|
||||
|
|
@ -34,6 +35,7 @@ export interface TSelectBase extends TBoxAsReactSelect {
|
|||
export interface TSelectContext {
|
||||
colorMode: 'light' | 'dark';
|
||||
isOpen: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
export interface TMultiValueRemoveProps {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { forwardRef } from 'react';
|
||||
import { Button, Stack } from '@chakra-ui/react';
|
||||
import { If } from '~/components';
|
||||
import { useConfig, useGlobalState } from '~/context';
|
||||
import { useBooleanValue } from '~/hooks';
|
||||
import { useConfig } from '~/context';
|
||||
import { useBooleanValue, useLGState } from '~/hooks';
|
||||
import { TitleOnly } from './titleOnly';
|
||||
import { SubtitleOnly } from './subtitleOnly';
|
||||
import { Logo } from './logo';
|
||||
|
|
@ -47,7 +47,7 @@ export const Title = forwardRef<HTMLButtonElement, TTitle>((props, ref) => {
|
|||
const { web } = useConfig();
|
||||
const titleMode = web.text.title_mode;
|
||||
|
||||
const { isSubmitting } = useGlobalState();
|
||||
const { isSubmitting } = useLGState();
|
||||
|
||||
const justify = useBooleanValue(
|
||||
isSubmitting.value,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { useState } from '@hookstate/core';
|
||||
import { createState, useState } from '@hookstate/core';
|
||||
import { Persistence } from '@hookstate/persistence';
|
||||
|
||||
import type { TUseGreetingReturn } from './types';
|
||||
|
||||
const greeting = createState<boolean>(false);
|
||||
|
||||
export function useGreeting(): TUseGreetingReturn {
|
||||
const state = useState<boolean>(false);
|
||||
const state = useState<boolean>(greeting);
|
||||
if (typeof window !== 'undefined') {
|
||||
state.attach(Persistence('hyperglass-greeting'));
|
||||
}
|
||||
|
|
@ -13,7 +15,6 @@ export function useGreeting(): TUseGreetingReturn {
|
|||
if (!state.get()) {
|
||||
state.set(v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return [state.value, setAck];
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { State } from '@hookstate/core';
|
|||
import type { Families, TDeviceVrf, TQueryTypes, TFormData } from '~/types';
|
||||
|
||||
type TLGState = {
|
||||
isSubmitting: boolean;
|
||||
queryVrf: string;
|
||||
families: Families;
|
||||
queryTarget: string;
|
||||
|
|
@ -20,9 +21,11 @@ type TLGState = {
|
|||
type TLGStateHandlers = {
|
||||
resolvedOpen(): void;
|
||||
resolvedClose(): void;
|
||||
resetForm(): void;
|
||||
};
|
||||
|
||||
const LGState = createState<TLGState>({
|
||||
isSubmitting: false,
|
||||
resolvedIsOpen: false,
|
||||
displayTarget: '',
|
||||
queryLocation: [],
|
||||
|
|
@ -44,6 +47,20 @@ export function useLGState(): State<TLGState> & TLGStateHandlers {
|
|||
function resolvedClose() {
|
||||
state.resolvedIsOpen.set(false);
|
||||
}
|
||||
function resetForm() {
|
||||
state.merge({
|
||||
queryVrf: '',
|
||||
families: [],
|
||||
queryType: '',
|
||||
queryTarget: '',
|
||||
fqdnTarget: null,
|
||||
queryLocation: [],
|
||||
displayTarget: '',
|
||||
resolvedIsOpen: false,
|
||||
btnLoading: false,
|
||||
formData: { query_location: [], query_target: '', query_type: '', query_vrf: '' },
|
||||
});
|
||||
}
|
||||
|
||||
return { resolvedOpen, resolvedClose, ...state };
|
||||
return { resetForm, resolvedOpen, resolvedClose, ...state };
|
||||
}
|
||||
|
|
|
|||
3
hyperglass/ui/package.json
vendored
3
hyperglass/ui/package.json
vendored
|
|
@ -19,6 +19,7 @@
|
|||
"@chakra-ui/react": "^1.0.3",
|
||||
"@emotion/react": "^11.1.1",
|
||||
"@emotion/styled": "^11.0.0",
|
||||
"@hookform/resolvers": "^1.2.0",
|
||||
"@hookstate/core": "^3.0.1",
|
||||
"@hookstate/persistence": "^3.0.0",
|
||||
"@meronex/icons": "^4.0.0",
|
||||
|
|
@ -34,7 +35,7 @@
|
|||
"react-countdown": "^2.2.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-fast-compare": "^3.2.0",
|
||||
"react-hook-form": "^5.7",
|
||||
"react-hook-form": "^6.13.1",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-query": "^2.26.4",
|
||||
"react-select": "^3.1.1",
|
||||
|
|
|
|||
13
hyperglass/ui/yarn.lock
vendored
13
hyperglass/ui/yarn.lock
vendored
|
|
@ -906,6 +906,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.0.tgz#6c9eafc78c1529248f8f4d92b0799a712b6052c6"
|
||||
integrity sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw==
|
||||
|
||||
"@hookform/resolvers@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-1.2.0.tgz#3a429b4bd3ec9981764fcdcc2491202f9f72d847"
|
||||
integrity sha512-YCKEj/3Kdo3uNt+zrWKV8txaiuATtvgHyz+KYmun3n5JDjxdI0HcVQgfcmJabmkBXBzKuIIrYfxaV8sRuAPZ8w==
|
||||
|
||||
"@hookstate/core@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@hookstate/core/-/core-3.0.1.tgz#dc46ea71e3bf0ab5c2dc024029c9210ed12fbb84"
|
||||
|
|
@ -5487,10 +5492,10 @@ react-focus-lock@2.4.1:
|
|||
use-callback-ref "^1.2.1"
|
||||
use-sidecar "^1.0.1"
|
||||
|
||||
react-hook-form@^5.7:
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-5.7.2.tgz#a84e259e5d37dd30949af4f79c4dac31101b79ac"
|
||||
integrity sha512-bJvY348vayIvEUmSK7Fvea/NgqbT2racA2IbnJz/aPlQ3GBtaTeDITH6rtCa6y++obZzG6E3Q8VuoXPir7QYUg==
|
||||
react-hook-form@^6.13.1:
|
||||
version "6.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-6.13.1.tgz#b9c0aa61f746db8169ed5e1050de21cacb1947d6"
|
||||
integrity sha512-Q0N7MYcbA8SigYufb02h9z97ZKCpIbe62rywOTPsK4Ntvh6fRTGDXSuzWuRhLHhArLoWbGrWYSNSS4tlb+OFXg==
|
||||
|
||||
react-input-autosize@^2.2.2:
|
||||
version "2.2.2"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue