mirror of
https://github.com/thatmattlove/hyperglass.git
synced 2026-01-17 08:48:05 +00:00
improve form & results style & layout [skip ci]
This commit is contained in:
parent
7cb445d9e7
commit
80af987034
10 changed files with 49 additions and 41 deletions
|
|
@ -37,9 +37,9 @@ export const FormField = (props: TField) => {
|
|||
htmlFor={name}
|
||||
display="flex"
|
||||
opacity={opacity}
|
||||
color={error !== false ? errorColor : labelColor}
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
justifyContent="space-between"
|
||||
color={error !== false ? errorColor : labelColor}>
|
||||
{label}
|
||||
<If c={typeof labelAddOn !== 'undefined'}>{labelAddOn}</If>
|
||||
</FormLabel>
|
||||
|
|
|
|||
|
|
@ -19,5 +19,4 @@ export * from './path';
|
|||
export * from './results';
|
||||
export * from './select';
|
||||
export * from './table';
|
||||
export * from './title';
|
||||
export * from './util';
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '~/components';
|
||||
import { useConfig } from '~/context';
|
||||
import { useStrf, useGreeting, useDevice, useLGState } from '~/hooks';
|
||||
import { isQueryType, isString } from '~/types';
|
||||
import { isQueryType, isQueryContent, isString } from '~/types';
|
||||
|
||||
import type { TFormData, TDeviceVrf, OnChangeArgs } from '~/types';
|
||||
|
||||
|
|
@ -63,8 +63,6 @@ export const HyperglassForm = () => {
|
|||
} = useLGState();
|
||||
|
||||
function submitHandler(values: TFormData) {
|
||||
console.dir(values);
|
||||
console.dir(formData.value);
|
||||
if (!greetingAck && web.greeting.required) {
|
||||
window.location.reload(false);
|
||||
setGreetingAck(false);
|
||||
|
|
@ -143,15 +141,15 @@ export const HyperglassForm = () => {
|
|||
} else if (e.field === 'query_target' && isString(e.value)) {
|
||||
queryTarget.set(e.value);
|
||||
}
|
||||
console.log(e.field, e.value);
|
||||
console.dir(getValues());
|
||||
}
|
||||
|
||||
const vrfContent = useMemo(() => {
|
||||
if (Object.keys(content.vrf).includes(queryVrf.value) && queryType.value !== '') {
|
||||
return content.vrf[queryVrf.value][queryType.value];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [queryVrf]);
|
||||
}, [queryVrf.value, queryLocation.value, queryType.value]);
|
||||
|
||||
const isFqdnQuery = useMemo(() => {
|
||||
return ['bgp_route', 'ping', 'traceroute'].includes(queryType.value);
|
||||
|
|
@ -185,7 +183,9 @@ export const HyperglassForm = () => {
|
|||
<FormField
|
||||
name="query_type"
|
||||
label={web.text.query_type}
|
||||
labelAddOn={vrfContent && <HelpModal item={vrfContent} name="query_type" />}>
|
||||
labelAddOn={
|
||||
<HelpModal visible={isQueryContent(vrfContent)} item={vrfContent} name="query_type" />
|
||||
}>
|
||||
<QueryType onChange={handleChange} label={web.text.query_type} />
|
||||
</FormField>
|
||||
</FormRow>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const Chart = (props: TChart) => {
|
|||
|
||||
const flowProps = useBreakpointValue<Omit<ReactFlowProps, 'elements'>>({
|
||||
base: { defaultPosition: [0, 300], defaultZoom: 0 },
|
||||
lg: { defaultPosition: [500, 450] },
|
||||
lg: { defaultPosition: [100, 300], defaultZoom: 0.7 },
|
||||
});
|
||||
|
||||
const elements = useMemo(() => [...buildElements({ asn: primary_asn, name: org_name }, data)], [
|
||||
|
|
@ -29,7 +29,7 @@ export const Chart = (props: TChart) => {
|
|||
return (
|
||||
<ReactFlowProvider>
|
||||
<Box boxSize="100%" zIndex={1}>
|
||||
<ReactFlow elements={elements} nodeTypes={{ TestNode }} {...flowProps}>
|
||||
<ReactFlow elements={elements} nodeTypes={{ ASNode }} {...flowProps}>
|
||||
<Background color={dots} />
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
|
|
@ -38,7 +38,7 @@ export const Chart = (props: TChart) => {
|
|||
);
|
||||
};
|
||||
|
||||
const TestNode = (props: TNode<TNodeData>) => {
|
||||
const ASNode = (props: TNode<TNodeData>) => {
|
||||
const { data } = props;
|
||||
const { asn, name, hasChildren, hasParents } = data;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export const Path = (props: TPath) => {
|
|||
const response = getResponse(device);
|
||||
const output = response?.output as TStructuredResponse;
|
||||
const bg = useColorValue('whiteFaded.50', 'blackFaded.900');
|
||||
|
||||
return (
|
||||
<>
|
||||
<PathButton onOpen={onOpen} />
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ function treeToElement(part: PathPart, len: number, index: number): FlowElement[
|
|||
let elements = [
|
||||
{
|
||||
id: String(part.base),
|
||||
type: 'TestNode',
|
||||
type: 'ASNode',
|
||||
position: { x, y },
|
||||
data: {
|
||||
asn: part.base,
|
||||
|
|
@ -50,7 +50,7 @@ export function* buildElements(base: BasePath, data: TStructuredResponse): Gener
|
|||
// Add the first hop at the base.
|
||||
yield {
|
||||
id: base.asn,
|
||||
type: 'TestNode',
|
||||
type: 'ASNode',
|
||||
position: { x: 150, y: numHops * 10 },
|
||||
data: { asn: base.asn, name: base.name, hasChildren: true, hasParents: false },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Accordion, Box, Stack, useToken } from '@chakra-ui/react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { AnimatedDiv, Label } from '~/components';
|
||||
|
|
@ -63,7 +63,7 @@ export const Results = () => {
|
|||
xl: { opacity: 0, x: 100 },
|
||||
});
|
||||
|
||||
const [resultsComplete, setComplete] = useState<number | null>(null);
|
||||
const [resultsComplete, setComplete] = useState<number[]>([]);
|
||||
|
||||
const matchedVrf =
|
||||
vrfs.filter(v => v.id === queryVrf.value)[0] ?? vrfs.filter(v => v.id === 'default')[0];
|
||||
|
|
@ -73,6 +73,13 @@ export const Results = () => {
|
|||
queryTypeLabel = queries[queryType.value].display_name;
|
||||
}
|
||||
|
||||
// Scroll to the top of the page when results load - primarily for mobile.
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
|
|
@ -141,7 +148,7 @@ export const Results = () => {
|
|||
transition={{ duration: 0.3 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
maxW={{ base: '100%', md: '75%' }}>
|
||||
<Accordion allowMultiple allowToggle>
|
||||
<Accordion allowMultiple allowToggle index={resultsComplete}>
|
||||
<AnimatePresence>
|
||||
{queryLocation &&
|
||||
queryLocation.map((loc, i) => {
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If, Path }
|
|||
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 { FormattedError } from './error';
|
||||
import { ResultHeader } from './header';
|
||||
import { isStackError, isFetchError, isLGError } from './guards';
|
||||
|
||||
import type { TAccordionHeaderWrapper, TResult, TErrorLevels } from './types';
|
||||
|
||||
|
|
@ -71,12 +71,15 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||
|
||||
const cacheLabel = useStrf(web.text.cache_icon, { time: data?.timestamp }, [data?.timestamp]);
|
||||
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [hasOverride, setOverride] = useState(false);
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen(!isOpen);
|
||||
setOverride(true);
|
||||
// Close if open.
|
||||
if (resultsComplete.includes(index)) {
|
||||
setComplete(p => p.filter(i => i !== index));
|
||||
}
|
||||
// Open if closed.
|
||||
else if (!resultsComplete.includes(index)) {
|
||||
setComplete(p => [...p, index]);
|
||||
}
|
||||
};
|
||||
|
||||
const errorKeywords = useMemo(() => {
|
||||
|
|
@ -140,17 +143,12 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||
copyValue = errorMsg;
|
||||
}
|
||||
|
||||
// If this is the first completed result, open it.
|
||||
useEffect(() => {
|
||||
if (isLoading && resultsComplete === null) {
|
||||
setComplete(index);
|
||||
if (!isLoading && !isError && resultsComplete.length === 0) {
|
||||
setComplete([index]);
|
||||
}
|
||||
}, [isLoading, resultsComplete]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resultsComplete === index && !hasOverride) {
|
||||
setOpen(true);
|
||||
}
|
||||
}, [resultsComplete, index]);
|
||||
}, [isLoading, isError]);
|
||||
|
||||
return (
|
||||
<AnimatedAccordionItem
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ export interface TResult {
|
|||
index: number;
|
||||
device: TDevice;
|
||||
queryVrf: string;
|
||||
queryType: TQueryTypes;
|
||||
queryTarget: string;
|
||||
setComplete(v: number | null): void;
|
||||
queryLocation: string;
|
||||
resultsComplete: number | null;
|
||||
queryType: TQueryTypes;
|
||||
resultsComplete: number[];
|
||||
setComplete: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
}
|
||||
|
||||
export type TErrorLevels = 'success' | 'warning' | 'error';
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ export const useControlStyle = (base: TStyles, state: TControl): TStyles => {
|
|||
);
|
||||
const focusBorder = useColorValue(useToken('colors', 'blue.500'), useToken('colors', 'blue.300'));
|
||||
const invalidBorder = useColorValue(useToken('colors', 'red.500'), useToken('colors', 'red.300'));
|
||||
const borderColor = useColorValue('inherit', useToken('colors', 'whiteAlpha.50'));
|
||||
const borderColor = useColorValue(
|
||||
useToken('colors', 'gray.100'),
|
||||
useToken('colors', 'whiteAlpha.50'),
|
||||
);
|
||||
const borderRadius = useToken('radii', 'md');
|
||||
const minHeight = useToken('space', 12);
|
||||
const color = useColorValue(useToken('colors', 'black'), useToken('colors', 'whiteAlpha.800'));
|
||||
|
|
@ -132,11 +135,11 @@ export const useOptionStyle = (base: TStyles, state: TOption): TStyles => {
|
|||
const color = useOpposingColor(backgroundColor);
|
||||
|
||||
const styles = {
|
||||
backgroundColor,
|
||||
color,
|
||||
fontSize,
|
||||
'&:focus': { backgroundColor: active, color: activeColor },
|
||||
color: backgroundColor === 'transparent' ? 'currentColor' : color,
|
||||
'&:active': { backgroundColor: active, color: activeColor },
|
||||
'&:focus': { backgroundColor: active, color: activeColor },
|
||||
backgroundColor,
|
||||
fontSize,
|
||||
};
|
||||
|
||||
return useMemo(() => mergeWith({}, base, styles), [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue