forked from mirrors/thatmattlove-hyperglass
UI improvements
This commit is contained in:
parent
22973cd21b
commit
ebe848bbf9
15 changed files with 491 additions and 272 deletions
|
|
@ -1,11 +1,19 @@
|
|||
import React from "react";
|
||||
import { Button, Icon, Tooltip, useClipboard } from "@chakra-ui/core";
|
||||
|
||||
export default ({ bg = "secondary", copyValue }) => {
|
||||
export default ({ bg = "secondary", copyValue, ...props }) => {
|
||||
const { onCopy, hasCopied } = useClipboard(copyValue);
|
||||
return (
|
||||
<Tooltip hasArrow label="Copy Output" placement="top">
|
||||
<Button size="sm" variantColor={bg} zIndex="1" onClick={onCopy} mx={1}>
|
||||
<Button
|
||||
as="a"
|
||||
size="sm"
|
||||
variantColor={bg}
|
||||
zIndex="dropdown"
|
||||
onClick={onCopy}
|
||||
mx={1}
|
||||
{...props}
|
||||
>
|
||||
{hasCopied ? <Icon name="check" size="16px" /> : <Icon name="copy" size="16px" />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export default ({ general, help, extLink, credit, terms, content }) => {
|
|||
/>
|
||||
)}
|
||||
<Flex
|
||||
py={2}
|
||||
py={[4, 4, 2, 2]}
|
||||
px={6}
|
||||
w="100%"
|
||||
as="footer"
|
||||
|
|
|
|||
|
|
@ -1,32 +1,28 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormErrorMessage,
|
||||
useTheme,
|
||||
useColorMode
|
||||
} from "@chakra-ui/core";
|
||||
import { Flex, FormControl, FormLabel, FormErrorMessage, useColorMode } from "@chakra-ui/core";
|
||||
import HelpModal from "~/components/HelpModal";
|
||||
|
||||
export default ({ label, name, error, hiddenLabels, helpIcon, children, ...props }) => {
|
||||
const theme = useTheme();
|
||||
const { colorMode } = useColorMode();
|
||||
const labelColor =
|
||||
colorMode === "dark" ? theme.colors.whiteAlpha[600] : theme.colors.blackAlpha[600];
|
||||
const labelColor = { dark: "whiteAlpha.600", light: "blackAlpha.600" };
|
||||
return (
|
||||
<FormControl
|
||||
as={Flex}
|
||||
flexDirection="column"
|
||||
flexGrow={1}
|
||||
flexBasis={0}
|
||||
flex={["1 0 100%", "1 0 100%", "1 0 33.33%", "1 0 33.33%"]}
|
||||
w="100%"
|
||||
maxW="100%"
|
||||
mx={2}
|
||||
my={[2, 2, 4, 4]}
|
||||
isInvalid={error && error.message}
|
||||
{...props}
|
||||
>
|
||||
<FormLabel htmlFor={name} color={labelColor} pl={1} opacity={hiddenLabels ? 0 : null}>
|
||||
<FormLabel
|
||||
htmlFor={name}
|
||||
color={labelColor[colorMode]}
|
||||
pl={1}
|
||||
opacity={hiddenLabels ? 0 : null}
|
||||
>
|
||||
{label}
|
||||
{helpIcon?.enable && <HelpModal item={helpIcon} name={name} />}
|
||||
</FormLabel>
|
||||
|
|
|
|||
|
|
@ -1,44 +1,123 @@
|
|||
import React from "react";
|
||||
import { Flex, IconButton, useColorMode, useTheme } from "@chakra-ui/core";
|
||||
import { motion } from "framer-motion";
|
||||
import { Flex, IconButton, useColorMode } from "@chakra-ui/core";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import ResetButton from "~/components/ResetButton";
|
||||
import useMedia from "~/components/MediaProvider";
|
||||
import Title from "~/components/Title";
|
||||
|
||||
const AnimatedFlex = motion.custom(Flex);
|
||||
const AnimatedResetButton = motion.custom(ResetButton);
|
||||
|
||||
export default () => {
|
||||
const theme = useTheme();
|
||||
const titleVariants = {
|
||||
sm: {
|
||||
fullSize: { scale: 1, marginLeft: 0 },
|
||||
small: { marginLeft: "auto" }
|
||||
},
|
||||
md: {
|
||||
fullSize: { scale: 1 },
|
||||
small: { scale: 1 }
|
||||
},
|
||||
lg: {
|
||||
fullSize: { scale: 1 },
|
||||
small: { scale: 1 }
|
||||
},
|
||||
xl: {
|
||||
fullSize: { scale: 1 },
|
||||
small: { scale: 1 }
|
||||
}
|
||||
};
|
||||
|
||||
const icon = { light: "moon", dark: "sun" };
|
||||
const bg = { light: "white", dark: "black" };
|
||||
const colorSwitch = { dark: "Switch to light mode", light: "Switch to dark mode" };
|
||||
const headerTransition = { type: "spring", ease: "anticipate", damping: 15, stiffness: 100 };
|
||||
|
||||
export default ({ height, isSubmitting, handleFormReset, ...props }) => {
|
||||
const { colorMode, toggleColorMode } = useColorMode();
|
||||
const bg = { light: theme.colors.white, dark: theme.colors.black };
|
||||
const icon = { light: "moon", dark: "sun" };
|
||||
const { mediaSize } = useMedia();
|
||||
const resetButton = (
|
||||
<AnimatePresence key="resetButton">
|
||||
<AnimatedFlex
|
||||
layoutTransition={headerTransition}
|
||||
initial={{ opacity: 0, x: -50 }}
|
||||
animate={{ opacity: 1, x: 0, width: "unset" }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
alignItems="center"
|
||||
mb={[null, "auto"]}
|
||||
>
|
||||
<AnimatedResetButton isSubmitting={isSubmitting} onClick={handleFormReset} />
|
||||
</AnimatedFlex>
|
||||
</AnimatePresence>
|
||||
);
|
||||
const title = (
|
||||
<AnimatedFlex
|
||||
key="title"
|
||||
px={1}
|
||||
alignItems={isSubmitting ? "center" : ["center", "center", "flex-end", "flex-end"]}
|
||||
positionTransition={headerTransition}
|
||||
initial={{ scale: 0.5 }}
|
||||
animate={isSubmitting ? "small" : "fullSize"}
|
||||
variants={titleVariants[mediaSize]}
|
||||
justifyContent="center"
|
||||
mb={[null, isSubmitting ? "auto" : null]}
|
||||
mt={[null, isSubmitting ? null : "auto"]}
|
||||
>
|
||||
<Title isSubmitting={isSubmitting} onClick={handleFormReset} />
|
||||
</AnimatedFlex>
|
||||
);
|
||||
const colorModeToggle = (
|
||||
<AnimatedFlex
|
||||
layoutTransition={headerTransition}
|
||||
key="colorModeToggle"
|
||||
alignItems="center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
mb={[null, "auto"]}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={colorSwitch[colorMode]}
|
||||
variant="ghost"
|
||||
color="current"
|
||||
ml={2}
|
||||
pl={0}
|
||||
fontSize="20px"
|
||||
onClick={toggleColorMode}
|
||||
icon={icon[colorMode]}
|
||||
/>
|
||||
</AnimatedFlex>
|
||||
);
|
||||
const layout = {
|
||||
false: {
|
||||
sm: [title, resetButton, colorModeToggle],
|
||||
md: [resetButton, title, colorModeToggle],
|
||||
lg: [resetButton, title, colorModeToggle],
|
||||
xl: [resetButton, title, colorModeToggle]
|
||||
},
|
||||
true: {
|
||||
sm: [resetButton, colorModeToggle, title],
|
||||
md: [resetButton, title, colorModeToggle],
|
||||
lg: [resetButton, title, colorModeToggle],
|
||||
xl: [resetButton, title, colorModeToggle]
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Flex
|
||||
position="fixed"
|
||||
as="header"
|
||||
px={2}
|
||||
top="0"
|
||||
zIndex="4"
|
||||
bg={bg[colorMode]}
|
||||
color={theme.colors.gray[500]}
|
||||
left="0"
|
||||
right="0"
|
||||
zIndex="4"
|
||||
as="header"
|
||||
width="full"
|
||||
height="4rem"
|
||||
flex="1 0 auto"
|
||||
position="fixed"
|
||||
bg={bg[colorMode]}
|
||||
color="gray.500"
|
||||
height={height}
|
||||
{...props}
|
||||
>
|
||||
<Flex w="100%" mx="auto" px={6} justifyContent="flex-end">
|
||||
<AnimatedFlex
|
||||
align="center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={`Switch to ${colorMode === "light" ? "dark" : "light"} mode`}
|
||||
variant="ghost"
|
||||
color="current"
|
||||
ml="2"
|
||||
fontSize="20px"
|
||||
onClick={toggleColorMode}
|
||||
icon={icon[colorMode]}
|
||||
/>
|
||||
</AnimatedFlex>
|
||||
<Flex w="100%" mx="auto" py={6} justify="space-between" alignItems="center">
|
||||
{layout[isSubmitting][mediaSize]}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ import QueryType from "~/components/QueryType";
|
|||
import QueryTarget from "~/components/QueryTarget";
|
||||
import QueryVrf from "~/components/QueryVrf";
|
||||
import SubmitButton from "~/components/SubmitButton";
|
||||
import useConfig from "~/components/HyperglassProvider";
|
||||
|
||||
format.extend(String.prototype, {});
|
||||
|
||||
const all = (...items) => [...items].every(i => (i ? true : false));
|
||||
|
||||
const formSchema = config =>
|
||||
yup.object().shape({
|
||||
query_location: yup
|
||||
|
|
@ -33,13 +32,20 @@ const formSchema = config =>
|
|||
});
|
||||
|
||||
const FormRow = ({ children, ...props }) => (
|
||||
<Flex flexDirection="row" flexWrap="wrap" w="100%" my={4} {...props}>
|
||||
<Flex
|
||||
flexDirection="row"
|
||||
flexWrap="wrap"
|
||||
w="100%"
|
||||
justifyContent={["center", "center", "space-between", "space-between"]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default React.forwardRef(
|
||||
({ config, isSubmitting, setSubmitting, setFormData, ...props }, ref) => {
|
||||
const HyperglassForm = React.forwardRef(
|
||||
({ isSubmitting, setSubmitting, setFormData, ...props }, ref) => {
|
||||
const config = useConfig();
|
||||
const { handleSubmit, register, setValue, errors } = useForm({
|
||||
validationSchema: formSchema(config)
|
||||
});
|
||||
|
|
@ -47,8 +53,6 @@ export default React.forwardRef(
|
|||
const [queryType, setQueryType] = useState("");
|
||||
const [queryVrf, setQueryVrf] = useState("");
|
||||
const [availVrfs, setAvailVrfs] = useState([]);
|
||||
// const [showHelpIcon, setShowHelpIcon] = useState(false);
|
||||
|
||||
const onSubmit = values => {
|
||||
setFormData(values);
|
||||
setSubmitting(true);
|
||||
|
|
@ -138,17 +142,25 @@ export default React.forwardRef(
|
|||
register={register}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
flexGrow={0}
|
||||
label="Submit"
|
||||
error={errors.query_target}
|
||||
hiddenLabels
|
||||
</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} />
|
||||
</FormField>
|
||||
</Flex>
|
||||
</FormRow>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
HyperglassForm.displayName = "HyperglassForm";
|
||||
export default HyperglassForm;
|
||||
|
|
|
|||
|
|
@ -2,20 +2,27 @@ import React from "react";
|
|||
import { Flex, useColorMode, useTheme } from "@chakra-ui/core";
|
||||
|
||||
export default React.forwardRef(
|
||||
({ value, label, labelBg, labelColor, valueBg, valueColor }, ref) => {
|
||||
({ value, label, labelColor, valueBg, valueColor, ...props }, ref) => {
|
||||
const theme = useTheme();
|
||||
const { colorMode } = useColorMode();
|
||||
const _labelBg = { light: theme.colors.black, dark: theme.colors.gray[200] };
|
||||
const _labelColor = { light: theme.colors.white, dark: theme.colors.white };
|
||||
const _labelColor = { dark: "whiteAlpha.700", light: "blackAlpha.700" };
|
||||
const _valueBg = { light: theme.colors.primary[600], dark: theme.colors.primary[600] };
|
||||
const _valueColor = { light: theme.colors.white, dark: theme.colors.white };
|
||||
const _valueColor = { light: "white", dark: "white" };
|
||||
return (
|
||||
<Flex ref={ref} flexWrap="wrap" alignItems="center" justifyContent="flex-start" mx={2}>
|
||||
<Flex
|
||||
ref={ref}
|
||||
flexWrap="nowrap"
|
||||
alignItems="center"
|
||||
justifyContent="flex-start"
|
||||
mx={[1, 2, 2, 2]}
|
||||
my={2}
|
||||
{...props}
|
||||
>
|
||||
<Flex
|
||||
display="inline-flex"
|
||||
justifyContent="center"
|
||||
lineHeight="1.5"
|
||||
px={3}
|
||||
px={[1, 3, 3, 3]}
|
||||
whiteSpace="nowrap"
|
||||
mb={2}
|
||||
mr={0}
|
||||
|
|
@ -25,8 +32,8 @@ export default React.forwardRef(
|
|||
borderTopLeftRadius={4}
|
||||
borderBottomRightRadius={0}
|
||||
borderTopRightRadius={0}
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
fontSize={["xs", "sm", "sm", "sm"]}
|
||||
>
|
||||
{value}
|
||||
</Flex>
|
||||
|
|
@ -39,13 +46,13 @@ export default React.forwardRef(
|
|||
mb={2}
|
||||
ml={0}
|
||||
mr={0}
|
||||
bg={labelBg || _labelBg[colorMode]}
|
||||
boxShadow={`inset 0px 0px 0px 1px ${valueBg || _valueBg[colorMode]}`}
|
||||
color={labelColor || _labelColor[colorMode]}
|
||||
borderBottomRightRadius={4}
|
||||
borderTopRightRadius={4}
|
||||
borderBottomLeftRadius={0}
|
||||
borderTopLeftRadius={0}
|
||||
fontSize="sm"
|
||||
fontSize={["xs", "sm", "sm", "sm"]}
|
||||
>
|
||||
{label}
|
||||
</Flex>
|
||||
|
|
|
|||
|
|
@ -1,67 +1,67 @@
|
|||
import React, { useState } from "react";
|
||||
import { Flex, useColorMode, useTheme } from "@chakra-ui/core";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Flex, useColorMode } from "@chakra-ui/core";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import ResetButton from "~/components/ResetButton";
|
||||
import HyperglassForm from "~/components/HyperglassForm";
|
||||
import Results from "~/components/Results";
|
||||
import Header from "~/components/Header";
|
||||
import Footer from "~/components/Footer";
|
||||
import Title from "~/components/Title";
|
||||
import Meta from "~/components/Meta";
|
||||
import useConfig from "~/components/HyperglassProvider";
|
||||
import Debugger from "~/components/Debugger";
|
||||
|
||||
const AnimatedForm = motion.custom(HyperglassForm);
|
||||
const AnimatedTitle = motion.custom(Title);
|
||||
const AnimatedResetButton = motion.custom(ResetButton);
|
||||
|
||||
export default ({ config }) => {
|
||||
const theme = useTheme();
|
||||
const bg = { light: "white", dark: "black" };
|
||||
const color = { light: "black", dark: "white" };
|
||||
const headerHeightDefault = { true: [16, 16, 16, 16], false: [24, 64, 64, 64] };
|
||||
const headerHeightAll = { true: [32, 32, 32, 32], false: [48, "20rem", "20rem", "20rem"] };
|
||||
|
||||
const Layout = () => {
|
||||
const config = useConfig();
|
||||
const { colorMode } = useColorMode();
|
||||
const bg = { light: theme.colors.white, dark: theme.colors.black };
|
||||
const color = { light: theme.colors.black, dark: theme.colors.white };
|
||||
const [isSubmitting, setSubmitting] = useState(false);
|
||||
const [formData, setFormData] = useState({});
|
||||
const containerRef = useRef(null);
|
||||
const handleFormReset = () => {
|
||||
containerRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
setSubmitting(false);
|
||||
};
|
||||
const headerHeight =
|
||||
config.branding.text.title_mode === "all"
|
||||
? headerHeightAll[isSubmitting]
|
||||
: headerHeightDefault[isSubmitting];
|
||||
return (
|
||||
<>
|
||||
<Meta config={config} />
|
||||
<Meta />
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
minHeight="100vh"
|
||||
w="100%"
|
||||
ref={containerRef}
|
||||
minHeight="100vh"
|
||||
bg={bg[colorMode]}
|
||||
flexDirection="column"
|
||||
color={color[colorMode]}
|
||||
>
|
||||
<Header />
|
||||
<Flex px={2} flex="1 1 auto" flexGrow={0} flexDirection="column">
|
||||
<Header
|
||||
isSubmitting={isSubmitting}
|
||||
handleFormReset={handleFormReset}
|
||||
height={headerHeight}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex
|
||||
as="main"
|
||||
w="100%"
|
||||
flexGrow={1}
|
||||
flexShrink={1}
|
||||
flexBasis="auto"
|
||||
alignItems="center"
|
||||
justifyContent="start"
|
||||
textAlign="center"
|
||||
flexDirection="column"
|
||||
px={2}
|
||||
py={0}
|
||||
mt={["5%", "5%", "5%", "10%"]}
|
||||
w="100%"
|
||||
as="main"
|
||||
mt={headerHeight}
|
||||
flex="1 1 auto"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
justifyContent="start"
|
||||
flexDirection="column"
|
||||
>
|
||||
<AnimatePresence>
|
||||
<AnimatedTitle
|
||||
initial={{ opacity: 0, y: -300 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
exit={{ opacity: 0, y: -300 }}
|
||||
text={config.branding.text}
|
||||
logo={config.branding.logo}
|
||||
resetForm={handleFormReset}
|
||||
/>
|
||||
</AnimatePresence>
|
||||
{isSubmitting && formData && (
|
||||
<Results
|
||||
config={config}
|
||||
queryLocation={formData.query_location}
|
||||
queryType={formData.query_type}
|
||||
queryVrf={formData.query_vrf}
|
||||
|
|
@ -76,7 +76,6 @@ export default ({ config }) => {
|
|||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
exit={{ opacity: 0, x: -300 }}
|
||||
config={config}
|
||||
isSubmitting={isSubmitting}
|
||||
setSubmitting={setSubmitting}
|
||||
setFormData={setFormData}
|
||||
|
|
@ -84,18 +83,7 @@ export default ({ config }) => {
|
|||
)}
|
||||
</AnimatePresence>
|
||||
</Flex>
|
||||
<AnimatePresence>
|
||||
{isSubmitting && (
|
||||
<AnimatedResetButton
|
||||
initial={{ opacity: 0, x: -50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
isSubmitting={isSubmitting}
|
||||
onClick={handleFormReset}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{config.general.debug && <Debugger />}
|
||||
<Footer
|
||||
general={config.general}
|
||||
content={config.content}
|
||||
|
|
@ -108,3 +96,6 @@ export default ({ config }) => {
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Layout.displayName = "HyperglassLayout";
|
||||
export default Layout;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { useTheme } from "@chakra-ui/core";
|
||||
import useConfig from "~/components/HyperglassProvider";
|
||||
import { googleFontUrl } from "~/util";
|
||||
|
||||
export default ({ config }) => {
|
||||
export default () => {
|
||||
const config = useConfig();
|
||||
const theme = useTheme();
|
||||
const [location, setLocation] = useState({});
|
||||
const title = config?.general.org_name || "hyperglass";
|
||||
|
|
|
|||
|
|
@ -1,21 +1,18 @@
|
|||
import React from "react";
|
||||
import { Button, Flex, Heading, Spinner, useTheme, useColorMode } from "@chakra-ui/core";
|
||||
import {
|
||||
Button,
|
||||
ColorModeProvider,
|
||||
CSSReset,
|
||||
Flex,
|
||||
Heading,
|
||||
Spinner,
|
||||
ThemeProvider,
|
||||
useTheme,
|
||||
useColorMode
|
||||
} from "@chakra-ui/core";
|
||||
import { defaultTheme } from "~/theme";
|
||||
|
||||
const ErrorMsg = ({ title }) => (
|
||||
<>
|
||||
<Heading mb={4} color="danger.500" as="h1" fontSize="2xl">
|
||||
{title}
|
||||
</Heading>
|
||||
</>
|
||||
);
|
||||
|
||||
const ErrorBtn = ({ text, onClick }) => (
|
||||
<Button variant="outline" variantColor="danger" onClick={onClick}>
|
||||
{text}
|
||||
</Button>
|
||||
);
|
||||
|
||||
export default ({ loading, error, refresh }) => {
|
||||
const PreConfig = ({ loading, error, refresh }) => {
|
||||
const theme = useTheme();
|
||||
const { colorMode } = useColorMode();
|
||||
const bg = { light: theme.colors.white, dark: theme.colors.dark };
|
||||
|
|
@ -45,15 +42,26 @@ export default ({ loading, error, refresh }) => {
|
|||
{loading && <Spinner color="primary.500" w="6rem" h="6rem" />}
|
||||
{!loading && error && (
|
||||
<>
|
||||
<ErrorMsg
|
||||
title={
|
||||
error.response?.data?.output || error.message || "An Error Occurred"
|
||||
}
|
||||
/>
|
||||
<ErrorBtn text="Retry" onClick={refresh} />
|
||||
<Heading mb={4} color="danger.500" as="h1" fontSize="2xl">
|
||||
{error.response?.data?.output || error.message || "An Error Occurred"}
|
||||
</Heading>
|
||||
<Button variant="outline" variantColor="danger" onClick={refresh}>
|
||||
Retry
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ({ loading, error, refresh }) => {
|
||||
return (
|
||||
<ThemeProvider theme={defaultTheme}>
|
||||
<ColorModeProvider>
|
||||
<CSSReset />
|
||||
<PreConfig loading={loading} error={error} refresh={refresh} />
|
||||
</ColorModeProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
import React from "react";
|
||||
import { Button, Icon, Spinner, Tooltip } from "@chakra-ui/core";
|
||||
import { Button, Icon, Tooltip } from "@chakra-ui/core";
|
||||
|
||||
export default ({ isLoading, requery, bg = "secondary" }) => {
|
||||
export default ({ requery, bg = "secondary", ...props }) => {
|
||||
return (
|
||||
<Tooltip hasArrow label="Reload Query" placement="top">
|
||||
<Button size="sm" variantColor={bg} zIndex="1" onClick={requery} mx={1}>
|
||||
{isLoading ? <Spinner size="sm" /> : <Icon size="16px" name="repeat" />}
|
||||
<Button
|
||||
as="a"
|
||||
size="sm"
|
||||
variantColor={bg}
|
||||
zIndex="1"
|
||||
onClick={requery}
|
||||
mx={1}
|
||||
{...props}
|
||||
>
|
||||
<Icon size="16px" name="repeat" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
import React from "react";
|
||||
import { Box, Button } from "@chakra-ui/core";
|
||||
import { Button } from "@chakra-ui/core";
|
||||
import { FiChevronLeft } from "react-icons/fi";
|
||||
|
||||
export default React.forwardRef(({ isSubmitting, onClick }, ref) => (
|
||||
<Box ref={ref} position="fixed" bottom={16} left={8} opacity={isSubmitting ? 1 : 0}>
|
||||
<Button variantColor="primary" variant="outline" p={2} onClick={onClick}>
|
||||
<FiChevronLeft size={24} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Button
|
||||
ref={ref}
|
||||
aria-label="Reset Form"
|
||||
opacity={isSubmitting ? 1 : 0}
|
||||
variant="ghost"
|
||||
color="current"
|
||||
onClick={onClick}
|
||||
pl={0}
|
||||
>
|
||||
<FiChevronLeft size={24} />
|
||||
</Button>
|
||||
));
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import {
|
|||
AccordionItem,
|
||||
AccordionHeader,
|
||||
AccordionPanel,
|
||||
AccordionIcon,
|
||||
Alert,
|
||||
Box,
|
||||
ButtonGroup,
|
||||
css,
|
||||
Flex,
|
||||
Text,
|
||||
useTheme,
|
||||
|
|
@ -15,26 +15,35 @@ import {
|
|||
import styled from "@emotion/styled";
|
||||
import useAxios from "axios-hooks";
|
||||
import strReplace from "react-string-replace";
|
||||
import useConfig from "~/components/HyperglassProvider";
|
||||
import CopyButton from "~/components/CopyButton";
|
||||
import RequeryButton from "~/components/RequeryButton";
|
||||
import ResultHeader from "~/components/ResultHeader";
|
||||
|
||||
const PreBox = styled(Box)`
|
||||
&::selection {
|
||||
background-color: ${props => props.selectionBg};
|
||||
color: ${props => props.selectionColor};
|
||||
}
|
||||
`;
|
||||
|
||||
const FormattedError = ({ keywords, message }) => {
|
||||
const patternStr = `(${keywords.join("|")})`;
|
||||
const pattern = new RegExp(patternStr, "gi");
|
||||
const errorFmt = strReplace(message, pattern, match => <Text as="strong">{match}</Text>);
|
||||
const errorFmt = strReplace(message, pattern, match => (
|
||||
<Text key={match} as="strong">
|
||||
{match}
|
||||
</Text>
|
||||
));
|
||||
return <Text>{errorFmt}</Text>;
|
||||
};
|
||||
|
||||
export default React.forwardRef(
|
||||
({ config, device, timeout, queryLocation, queryType, queryVrf, queryTarget }, ref) => {
|
||||
const AccordionHeaderWrapper = styled(Flex)`
|
||||
justify-content: space-between;
|
||||
&:hover {
|
||||
background-color: ${props => props.hoverBg};
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: "outline";
|
||||
}
|
||||
`;
|
||||
|
||||
const Result = React.forwardRef(
|
||||
({ device, timeout, queryLocation, queryType, queryVrf, queryTarget }, ref) => {
|
||||
const config = useConfig();
|
||||
const theme = useTheme();
|
||||
const { colorMode } = useColorMode();
|
||||
const bg = { dark: theme.colors.gray[800], light: theme.colors.blackAlpha[100] };
|
||||
|
|
@ -68,60 +77,68 @@ export default React.forwardRef(
|
|||
<AccordionItem
|
||||
isDisabled={loading}
|
||||
ref={ref}
|
||||
css={{
|
||||
css={css({
|
||||
"&:last-of-type": { borderBottom: "none" },
|
||||
"&:first-of-type": { borderTop: "none" }
|
||||
}}
|
||||
})}
|
||||
>
|
||||
<AccordionHeader justifyContent="space-between">
|
||||
<ResultHeader
|
||||
config={config}
|
||||
title={device.display_name}
|
||||
loading={loading}
|
||||
error={error}
|
||||
/>
|
||||
<Flex>
|
||||
<AccordionIcon />
|
||||
<AccordionHeaderWrapper hoverBg={theme.colors.blackAlpha[50]}>
|
||||
<AccordionHeader flex="1 0 auto" py={2} _hover={{}} _focus={{}} w="unset">
|
||||
<ResultHeader title={device.display_name} loading={loading} error={error} />
|
||||
</AccordionHeader>
|
||||
<ButtonGroup px={3} py={2}>
|
||||
<CopyButton copyValue={cleanOutput} variant="ghost" />
|
||||
<RequeryButton requery={refetch} variant="ghost" />
|
||||
</ButtonGroup>
|
||||
</AccordionHeaderWrapper>
|
||||
<AccordionPanel
|
||||
pb={4}
|
||||
overflowX="auto"
|
||||
css={css({ WebkitOverflowScrolling: "touch" })}
|
||||
>
|
||||
<Flex direction="row" flexWrap="wrap">
|
||||
<Flex direction="column" flex="1 0 auto">
|
||||
{data && (
|
||||
<Box
|
||||
fontFamily="mono"
|
||||
mt={5}
|
||||
mx={2}
|
||||
p={3}
|
||||
border="1px"
|
||||
borderColor="inherit"
|
||||
rounded="md"
|
||||
bg={bg[colorMode]}
|
||||
color={color[colorMode]}
|
||||
fontSize="sm"
|
||||
whiteSpace="pre-wrap"
|
||||
as="pre"
|
||||
css={css({
|
||||
"&::selection": {
|
||||
backgroundColor: selectionBg[colorMode],
|
||||
color: selectionColor[colorMode]
|
||||
}
|
||||
})}
|
||||
>
|
||||
{cleanOutput}
|
||||
</Box>
|
||||
)}
|
||||
{error && (
|
||||
<Alert
|
||||
rounded="lg"
|
||||
my={2}
|
||||
py={4}
|
||||
status={error.response?.data?.alert || "error"}
|
||||
>
|
||||
<FormattedError keywords={errorKw} message={errorMsg} />
|
||||
</Alert>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel pb={4}>
|
||||
<Box position="relative">
|
||||
{data && (
|
||||
<PreBox
|
||||
fontFamily="mono"
|
||||
mt={5}
|
||||
p={3}
|
||||
border="1px"
|
||||
borderColor="inherit"
|
||||
rounded="md"
|
||||
bg={bg[colorMode]}
|
||||
color={color[colorMode]}
|
||||
fontSize="sm"
|
||||
whiteSpace="pre-wrap"
|
||||
as="pre"
|
||||
selectionBg={selectionBg[colorMode]}
|
||||
selectionColor={selectionColor[colorMode]}
|
||||
>
|
||||
{cleanOutput}
|
||||
</PreBox>
|
||||
)}
|
||||
{error && (
|
||||
<Alert
|
||||
rounded="lg"
|
||||
my={2}
|
||||
py={4}
|
||||
status={error.response?.data?.alert || "error"}
|
||||
>
|
||||
<FormattedError keywords={errorKw} message={errorMsg} />
|
||||
</Alert>
|
||||
)}
|
||||
<ButtonGroup position="absolute" top={0} right={5} py={3} spacing={4}>
|
||||
<CopyButton copyValue={cleanOutput} />
|
||||
<RequeryButton isLoading={loading} requery={refetch} />
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Result.displayName = "HyperglassQueryResult";
|
||||
export default Result;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,29 @@
|
|||
import React from "react";
|
||||
import { Icon, Spinner, Stack, Text, Tooltip, useColorMode, useTheme } from "@chakra-ui/core";
|
||||
import {
|
||||
AccordionIcon,
|
||||
Icon,
|
||||
Spinner,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
useColorMode,
|
||||
useTheme
|
||||
} from "@chakra-ui/core";
|
||||
import useConfig from "~/components/HyperglassProvider";
|
||||
|
||||
export default React.forwardRef(({ config, title, loading, error }, ref) => {
|
||||
export default React.forwardRef(({ title, loading, error }, ref) => {
|
||||
const config = useConfig();
|
||||
const theme = useTheme();
|
||||
const { colorMode } = useColorMode();
|
||||
const statusColor = { dark: theme.colors.primary[300], light: theme.colors.primary[500] };
|
||||
const defaultWarningColor = { dark: theme.colors.danger[300], light: theme.colors.danger[500] };
|
||||
const statusColor = { dark: "primary.300", light: "primary.500" };
|
||||
const defaultWarningColor = { dark: "danger.300", light: "danger.500" };
|
||||
const warningColor = { dark: 300, light: 500 };
|
||||
const defaultStatusColor = {
|
||||
dark: theme.colors.success[300],
|
||||
light: theme.colors.success[500]
|
||||
dark: "success.300",
|
||||
light: "success.500"
|
||||
};
|
||||
return (
|
||||
<Stack ref={ref} isInline alignItems="center">
|
||||
<Stack ref={ref} isInline alignItems="center" w="100%">
|
||||
{loading ? (
|
||||
<Spinner size="sm" mr={4} color={statusColor[colorMode]} />
|
||||
) : error ? (
|
||||
|
|
@ -36,6 +47,7 @@ export default React.forwardRef(({ config, title, loading, error }, ref) => {
|
|||
<Icon name="check" color={defaultStatusColor[colorMode]} mr={4} size={6} />
|
||||
)}
|
||||
<Text fontSize="lg">{title}</Text>
|
||||
<AccordionIcon ml="auto" />
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,25 +1,60 @@
|
|||
import React from "react";
|
||||
import { Accordion, Box, Stack, useColorMode, useTheme } from "@chakra-ui/core";
|
||||
import { Accordion, Box, Stack, useTheme } from "@chakra-ui/core";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Label from "~/components/Label";
|
||||
import Result from "~/components/Result";
|
||||
import useConfig from "~/components/HyperglassProvider";
|
||||
import useMedia from "~/components/MediaProvider";
|
||||
|
||||
const AnimatedResult = motion.custom(Result);
|
||||
const AnimatedLabel = motion.custom(Label);
|
||||
|
||||
export default ({
|
||||
config,
|
||||
queryLocation,
|
||||
queryType,
|
||||
queryVrf,
|
||||
queryTarget,
|
||||
setSubmitting,
|
||||
...props
|
||||
}) => {
|
||||
const labelInitial = {
|
||||
left: {
|
||||
sm: { opacity: 0, x: -100 },
|
||||
md: { opacity: 0, x: -100 },
|
||||
lg: { opacity: 0, x: -100 },
|
||||
xl: { opacity: 0, x: -100 }
|
||||
},
|
||||
center: {
|
||||
sm: { opacity: 0 },
|
||||
md: { opacity: 0 },
|
||||
lg: { opacity: 0 },
|
||||
xl: { opacity: 0 }
|
||||
},
|
||||
right: {
|
||||
sm: { opacity: 0, x: 100 },
|
||||
md: { opacity: 0, x: 100 },
|
||||
lg: { opacity: 0, x: 100 },
|
||||
xl: { opacity: 0, x: 100 }
|
||||
}
|
||||
};
|
||||
const labelAnimate = {
|
||||
left: {
|
||||
sm: { opacity: 1, x: 0 },
|
||||
md: { opacity: 1, x: 0 },
|
||||
lg: { opacity: 1, x: 0 },
|
||||
xl: { opacity: 1, x: 0 }
|
||||
},
|
||||
center: {
|
||||
sm: { opacity: 1 },
|
||||
md: { opacity: 1 },
|
||||
lg: { opacity: 1 },
|
||||
xl: { opacity: 1 }
|
||||
},
|
||||
right: {
|
||||
sm: { opacity: 1, x: 0 },
|
||||
md: { opacity: 1, x: 0 },
|
||||
lg: { opacity: 1, x: 0 },
|
||||
xl: { opacity: 1, x: 0 }
|
||||
}
|
||||
};
|
||||
|
||||
const Results = ({ queryLocation, queryType, queryVrf, queryTarget, setSubmitting, ...props }) => {
|
||||
const config = useConfig();
|
||||
const theme = useTheme();
|
||||
const { colorMode } = useColorMode();
|
||||
const { mediaSize } = useMedia();
|
||||
const matchedVrf = config.vrfs.filter(v => v.id === queryVrf)[0];
|
||||
const labelColor = { light: theme.colors.white, dark: theme.colors.black };
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
|
|
@ -31,39 +66,39 @@ export default ({
|
|||
textAlign="left"
|
||||
{...props}
|
||||
>
|
||||
<Stack isInline align="center" justify="center" mt={4}>
|
||||
<Stack isInline align="center" justify="center" mt={4} flexWrap="wrap">
|
||||
<AnimatePresence>
|
||||
{queryLocation && (
|
||||
<>
|
||||
<AnimatedLabel
|
||||
initial={{ opacity: 0, x: -100 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
initial={labelInitial.left[mediaSize]}
|
||||
animate={labelAnimate.left[mediaSize]}
|
||||
transition={{ duration: 0.3, delay: 0.3 }}
|
||||
exit={{ opacity: 0, x: -100 }}
|
||||
label={config.branding.text.query_type}
|
||||
value={config.branding.text[queryType]}
|
||||
valueBg={theme.colors.cyan[500]}
|
||||
labelColor={labelColor[colorMode]}
|
||||
fontSize={["xs", "sm", "sm", "sm"]}
|
||||
/>
|
||||
<AnimatedLabel
|
||||
initial={{ opacity: 0, scale: 0.5 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
initial={labelInitial.center[mediaSize]}
|
||||
animate={labelAnimate.center[mediaSize]}
|
||||
transition={{ duration: 0.3, delay: 0.3 }}
|
||||
exit={{ opacity: 0, scale: 0.5 }}
|
||||
label={config.branding.text.query_target}
|
||||
value={queryTarget}
|
||||
valueBg={theme.colors.teal[600]}
|
||||
labelColor={labelColor[colorMode]}
|
||||
fontSize={["xs", "sm", "sm", "sm"]}
|
||||
/>
|
||||
<AnimatedLabel
|
||||
initial={{ opacity: 0, x: 100 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
initial={labelInitial.right[mediaSize]}
|
||||
animate={labelAnimate.right[mediaSize]}
|
||||
transition={{ duration: 0.3, delay: 0.3 }}
|
||||
exit={{ opacity: 0, x: 100 }}
|
||||
label={config.branding.text.query_vrf}
|
||||
value={matchedVrf.display_name}
|
||||
valueBg={theme.colors.blue[500]}
|
||||
labelColor={labelColor[colorMode]}
|
||||
fontSize={["xs", "sm", "sm", "sm"]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -71,7 +106,7 @@ export default ({
|
|||
</Stack>
|
||||
</Box>
|
||||
<Box
|
||||
maxW={["100%", "100%", "75%", "50%"]}
|
||||
maxW={["100%", "100%", "75%", "75%"]}
|
||||
w="100%"
|
||||
p={0}
|
||||
mx="auto"
|
||||
|
|
@ -91,7 +126,6 @@ export default ({
|
|||
{queryLocation &&
|
||||
queryLocation.map((loc, i) => (
|
||||
<AnimatedResult
|
||||
config={config}
|
||||
initial={{ opacity: 0, y: 300 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, delay: i * 0.3 }}
|
||||
|
|
@ -112,3 +146,6 @@ export default ({
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Results.displayName = "HyperglassResults";
|
||||
export default Results;
|
||||
|
|
|
|||
|
|
@ -1,62 +1,98 @@
|
|||
import React from "react";
|
||||
import { Button, Flex, Heading, Image, Stack, useColorMode } from "@chakra-ui/core";
|
||||
import { Button, Heading, Image, Stack, useColorMode } from "@chakra-ui/core";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import useConfig from "~/components/HyperglassProvider";
|
||||
import useMedia from "~/components/MediaProvider";
|
||||
|
||||
const TitleOnly = ({ text }) => (
|
||||
<Heading as="h1" size="2xl">
|
||||
const subtitleAnimation = {
|
||||
transition: { duration: 0.2, type: "tween" },
|
||||
initial: { opacity: 1, scale: 1 },
|
||||
animate: { opacity: 1, scale: 1 },
|
||||
exit: { opacity: 0, scale: 0.3 }
|
||||
};
|
||||
|
||||
const titleSize = { true: "2xl", false: "lg" };
|
||||
const titleMargin = { true: 2, false: 0 };
|
||||
|
||||
const TitleOnly = ({ text, showSubtitle }) => (
|
||||
<Heading as="h1" mb={titleMargin[showSubtitle]} size={titleSize[showSubtitle]}>
|
||||
{text}
|
||||
</Heading>
|
||||
);
|
||||
|
||||
const SubtitleOnly = ({ text }) => (
|
||||
<Heading as="h3" size="md">
|
||||
const SubtitleOnly = React.forwardRef(({ text, size = "md", ...props }, ref) => (
|
||||
<Heading ref={ref} as="h3" size={size} {...props}>
|
||||
{text}
|
||||
</Heading>
|
||||
);
|
||||
));
|
||||
|
||||
const TextOnly = ({ text }) => (
|
||||
<Stack spacing={2}>
|
||||
<TitleOnly text={text.title} />
|
||||
<SubtitleOnly text={text.subtitle} />
|
||||
const AnimatedSubtitle = motion.custom(SubtitleOnly);
|
||||
|
||||
const textAlignment = { false: ["right", "center"], true: ["left", "center"] };
|
||||
|
||||
const TextOnly = ({ text, mediaSize, showSubtitle, ...props }) => (
|
||||
<Stack spacing={2} textAlign={textAlignment[showSubtitle]} {...props}>
|
||||
<TitleOnly text={text.title} showSubtitle={showSubtitle} />
|
||||
<AnimatePresence>
|
||||
{showSubtitle && <AnimatedSubtitle text={text.subtitle} {...subtitleAnimation} />}
|
||||
</AnimatePresence>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const LogoOnly = ({ text, logo }) => {
|
||||
const Logo = ({ text, logo }) => {
|
||||
const { colorMode } = useColorMode();
|
||||
const logoColor = { light: logo.dark, dark: logo.light };
|
||||
const logoPath = logoColor[colorMode];
|
||||
return (
|
||||
<Image
|
||||
src={`http://localhost:8001${logoPath}`}
|
||||
alt={text.title}
|
||||
w={logo.width}
|
||||
h={logo.height || null}
|
||||
/>
|
||||
);
|
||||
return <Image src={logoPath} alt={text.title} />;
|
||||
};
|
||||
|
||||
const LogoTitle = ({ text, logo }) => (
|
||||
const LogoTitle = ({ text, logo, showSubtitle }) => (
|
||||
<>
|
||||
<LogoOnly text={text} logo={logo} />
|
||||
<SubtitleOnly text={text.title} />
|
||||
<Logo text={text} logo={logo} />
|
||||
<AnimatePresence>
|
||||
{showSubtitle && (
|
||||
<AnimatedSubtitle mt={2} text={text.subtitle} {...subtitleAnimation} />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
|
||||
const All = ({ text, logo }) => (
|
||||
const All = ({ text, logo, mediaSize, showSubtitle }) => (
|
||||
<>
|
||||
<LogoOnly text={text} logo={logo} />
|
||||
<TextOnly text={text} />
|
||||
<Logo text={text} logo={logo} />
|
||||
<TextOnly mediaSize={mediaSize} showSubtitle={showSubtitle} mt={2} text={text} />
|
||||
</>
|
||||
);
|
||||
|
||||
const modeMap = { text_only: TextOnly, logo_only: LogoOnly, logo_title: LogoTitle, all: All };
|
||||
const modeMap = { text_only: TextOnly, logo_only: Logo, logo_title: LogoTitle, all: All };
|
||||
|
||||
export default React.forwardRef(({ text, logo, resetForm }, ref) => {
|
||||
const MatchedMode = modeMap[text.title_mode];
|
||||
const btnJustify = {
|
||||
true: ["flex-end", "center"],
|
||||
false: ["flex-start", "center"]
|
||||
};
|
||||
export default React.forwardRef(({ onClick, isSubmitting, ...props }, ref) => {
|
||||
const { branding } = useConfig();
|
||||
const { mediaSize } = useMedia();
|
||||
const titleMode = branding.text.title_mode;
|
||||
const MatchedMode = modeMap[titleMode];
|
||||
return (
|
||||
<Button variant="link" onClick={resetForm} _focus={{ boxShadow: "non" }}>
|
||||
<Flex ref={ref}>
|
||||
<MatchedMode text={text} logo={logo} />
|
||||
</Flex>
|
||||
<Button
|
||||
ref={ref}
|
||||
variant="link"
|
||||
onClick={onClick}
|
||||
flexWrap="wrap"
|
||||
_focus={{ boxShadow: "none" }}
|
||||
_hover={{ textDecoration: "none" }}
|
||||
justifyContent={btnJustify[isSubmitting]}
|
||||
px={0}
|
||||
{...props}
|
||||
>
|
||||
<MatchedMode
|
||||
mediaSize={mediaSize}
|
||||
showSubtitle={!isSubmitting}
|
||||
text={branding.text}
|
||||
logo={branding.logo}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue