Files
nym/explorer/src/components/Nav.tsx
T
Mark Sinclair 5dadd73dfd Network Explorer (#881)
* fixed styled component

* dynamic colour for isSelected

* corrected type names

* wrapped nav for routing and positioning of main section and dynamic colour for selected section

* overview info panes added

* quick refactor break out components separately

* WorldMap implemented but not data

* map changed and updated to shades correctly

* live data in cards

* added any types for react simple map

* nested routing added but needs tidying and types refactored

* added tooltip to worldmap

* worldmap killed unused props

* updated MUI version to stable v5

* dark mode and ContentCard refactor complete

* refactor of DarkMode context and API into class and context setup

* context refactor for multiple APIs at top level

* mui typography used for error msging instead of jsx/html

* added typeDefs for node api types

* small changes to sx styling

* added types for api responses and main context

* promiseAll for better error handling of individual async calls

* switch out to live API for country nodes

* removal of unnecessary type any and shortening sx style block

* routing and basic mixnodes table and linking

* fixed TS error handling and ts exclude files

* refactor of class API fetch reqs

* renaming to more appropriate explorer-api

* broken - passing to Fouad

* fix for types in context main

* mixnode detail page

* rebasing back before fetch mixnode by ID was implemented

* added basic cache for huge dump of mixnode data

* broken mixnodes context

* fixed mixnode detail fetch

* added hardcoded BondBreakdown section

* added 2 col table for detail page and small refactor of ApiState type for consistent use throughout app

* basic chart with basic dark/theme implemented - no live data

* added scrollToTop useRef for Detail page

* tidied grid items

* media qry for smaller screens

* small changes

* added live data to bonds breakdown 1/2

* small changes/tweaks

* Bondbreakdown retrieves live data

* mixnode stats using live data

* added node status live data

* uptime story added with live data

* date formatting added

* mixnode map

* error handling for mixnode stats

* error handling for port stats

* improved error handling for table - unfinished

* error handling for mixnode table

* handle Loading state for 2colSmallTable

* Uptime story loading handling

* set up data grid component

* remove mixnode value check as handled inside MixnodesDataGrid component

* use loading prop in data grid component

* undo unintentional code formatting

* map blur and linkable data-grid added

* getting ready for gateways and removing con logs

* quickfix for map blur

* PR comment changes

* refactored data grid for reuseability

* Link to open Big Dipper for Blocks

* passing element to title instead of string for routing to Big Dipper

* quick fix for element passed as title for contentCard

* fix for colour coding nodes

* nuts and bolts of search and results per page are working

* media query for responsive search and no-per-page toolbar

* broke out search and pagesize to separate toolbar

* fix for going back to mixnodes datagrid and refetching

* corrected typings for WorldMap

* removed API for topojson

* Cleaner implementation of formatting inline for datagrid

* added Type to Datagrid Rows for mixnode

* removed optional from type for Datagrid

* added page listing the Gateway nodes

* adding clickable location to handleSearch

* tidying util functions and removing dead useEffect

* Add missing constant

* Validators link to Big Dipper

* added validators link to side nav as per Issues card

* SVG icons

* PR tweak to move logic to routes

* removed dead code post rebase

* fixed light dark mode for DataGrid

* light dark mode works on SVGs in Nav

* moving logic back to Nav to avoid window object issues

* neater ternary for SVG icons dark mode

* Better Linking/Styling for cells

* corrected prop/attr name in svg to Reactify it

* moved api url to constants

* SVGs dark now governed by context not props so reverting renderIcon method back to key value setup

* percentage for bond total added

* SVGs for Overview cards Mixnodes Gateways and Validators

* decimalised formatted punks and % of bond for BondBreakdown card

* number formatting via validator module

* adding cossmjs math pkg

* unfinished refactor BondBreakdown

* first few ui tweaks

* Adding google font Open Sans as per designs

* DataGrid unstylable in theme so nuking in css

* adding theming to Block Height card not hardcoded colours

* DataGrid styling

* Nav styling colours but without hover fix

* theme for bond breakdown

* killing con logs

* Datagrid styling

* Nav bar working

* added lines to nav

* removed cursive from fallback fonts

* trimming and refactoring

* removed dead isActive code from nav options

* Color correction for theme on 2col table

* Moved cell styles out to UniversalDatagrid for reuseability

* Nav colors moved to theme

* Removing comments and dead code

* DataGrid UI improvements

* theming for Overview content card

* Bonds updated from UPUNK to PUNK

* corrected SVG warning on stroke-width

* added Boolean class instead of ternary

* fixing up svg attr to jsx props

* merging UPUNK changes into ui-tweaks

* corrected SVG warning on stroke-width

* added Boolean class instead of ternary

* last instance of Boolean

* BondBreakdown handles 0 delegations

* formatting for webpack config and svgs

* Add `npm run lint` and `npm run lint:fix` targets to `package.json`

* Allow `.vscode` directories - exclude them individually like has been done already in the `.gitignore` directory

* Add `vscode` action to run `eslint` on save for the `/explorer/**` sub-directory

* eslint auto fix

* Fix some easy eslint issues

* removing grid pipes and pastel map colors

* Grid xl lg values to align with Search Toolbar

* GitHub Actions: do not trigger Rust actions when the paths are only `/explorer/**`

See https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onpushpull_requestpaths for details.

* GitHub Actions: run eslint and annotate pull requests

* socials added to Appbar and Footer

* smaller darkmode icon for mobile

* cleaner code for nav dark light selector

* almost all lint fixes

* post linting Nav fix

* killed con log and removed unused dep

* ref type and removed 1x ts ignore from worldMap

* disabl nested tern w/ nav is refactored on diff br

* icons smaller put into mui List format

* Added hover effect to match DarkLight switch

* ts ignore for worldMap vs no ts decl

* parking changes

* Flipped to MUI SVGs

* re-added external links to Socials

* nav functionality working

* spacing on Mixnode detail page

* datagrid alignment & detail page spacing

* map but no datagrid yet

* killed old SVGs now using mui icons

* added palette instead of strings

* Mixnode Map page working still needs tidying

* better lg xl responsive on Overview and sanitized Page Titles

* removed typography from imports as unused now

* search, sort working & added LG XL responsiveness

* Routing root reqs direct to Overview

* basic 404 page and btn back to overview

* killed fragments and comments

* updated Bond total in column

* Change bond col to type number not text

* Added field to DataGrid and updated MixnodeToGridrow logic

* Added type number so sorting works properly.

* added %self to Detail page

* basic scroll working desktop

* delegations now popout and scroll according to designs

* added stickyHeader and killed dead code

* ExpandMore only renders if delegations exist

* killed old svg icons

* added theme to Overview SVGs

* bringing Title into other pages

* linting fix

* pagination and spacing of gateways cols

* linting fix

* style override for pagination

* added hamburger and changed appbar to fixed

* bringing in other lint fix to pass linting

* PR feedback changes

* Add README.md for theme customisation

* Add hook to get app state context

* Add Nym theme typings to MUI `Theme` types

* Use new theme provider

* Fixing up components to use theme typings
Updated Overview Footer and ContentCard
Footer and Nav socials
Title, Nav chevron and Nav SVGs
Overview SVGs
Light Dark switch
BondBreakdown and 2 col table
DataGrid and 2col Tables
WorldMap UptimeChart and theme changes
WorldMap colors
merge changes
added StatsCard for overview

* Bug fix: do no close drawer when clicking on mixnodes or gateways

* Theme primary colour set to orange highlight, so that default/primary actions are clear to the user. This fixes colours on the pagination page list.

* Fixing up map projection

* Map view uses stroke colour from theme

* added useTheme from correct pkg

* react types upgrade to kill SXProps issue

* SXProps fix removing dead mixins from fixed AppBar

* Scale of Map changed to see more countries

* return type for main Context required

* Fixed map so more countries show

* type for useMainContext hook added

* Remove unused file

* Tidy up imports

* Remove use of `any` by using strongly typed hook to get the app state

* Remove module declaration so that @types/react-simple-maps is used

* API map response changed to indexed object

* Map view uses correct typings from `@types/react-simple-maps` and `d3-scale`

* Make content responsive and fills the view when screen widest

* Link network explorer in title to overview page

* Increase size of card headers to differentiate

* Fix column widths

* Fixed icons showing incorrectly the stats card in mixnode detail view

* Set default sort on mixnodes and gateways to be `bond` descending.

There is an error in MUI data-grid that does not adjust the sort caret based on initial `sortModel` value. Needs investigation.

* GitHub Action for deployment: prefix with network explorer to stop collisions with other deployment projects

* Mixnode list: fix up header title for `host`

* Fix up notification URLs and tidy up readme

* Fix up license information

Co-authored-by: Adrian Thompson <adrian@nymtech.net>
Co-authored-by: Adrian Thompson <adrianthompson@Adrians-MacBook-Air.local>
Co-authored-by: fmtabbara <fmtabbara@hotmail.co.uk>
Co-authored-by: Aid19801 <adrianThompson19801@gmail.com>
2021-11-08 12:23:27 +00:00

398 lines
10 KiB
TypeScript

/* eslint-disable no-nested-ternary */
import * as React from 'react';
import { Link } from 'react-router-dom';
import { ChevronLeft, ExpandLess, ExpandMore, Menu } from '@mui/icons-material';
import { CSSObject, styled, Theme, useTheme } from '@mui/material/styles';
import MuiLink from '@mui/material/Link';
import Box from '@mui/material/Box';
import ListItem from '@mui/material/ListItem';
import MuiDrawer from '@mui/material/Drawer';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import List from '@mui/material/List';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import { NymLogoSVG } from 'src/icons/NymLogoSVG';
import { BIG_DIPPER, NYM_WEBSITE } from 'src/api/constants';
import { useMediaQuery } from '@mui/material';
import { OverviewSVG } from '../icons/OverviewSVG';
import { NetworkComponentsSVG } from '../icons/NetworksSVG';
import { NodemapSVG } from '../icons/NodemapSVG';
import { Socials } from './Socials';
import { Footer } from './Footer';
import { DarkLightSwitchDesktop, DarkLightSwitchMobile } from './Switch';
const drawerWidth = 300;
const openedMixin = (theme: Theme): CSSObject => ({
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
});
const closedMixin = (theme: Theme): CSSObject => ({
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: 'hidden',
width: `calc(${theme.spacing(7)} + 1px)`,
});
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
height: 64,
}));
const Drawer = styled(MuiDrawer, {
shouldForwardProp: (prop) => prop !== 'open',
})(({ theme, open }) => ({
width: drawerWidth,
flexShrink: 0,
whiteSpace: 'nowrap',
boxSizing: 'border-box',
...(open && {
...openedMixin(theme),
'& .MuiDrawer-paper': openedMixin(theme),
}),
...(!open && {
...closedMixin(theme),
'& .MuiDrawer-paper': closedMixin(theme),
}),
}));
type navOptionType = {
id: number;
isActive?: boolean;
url: string;
title: string;
Icon?: React.ReactNode;
nested?: navOptionType[];
isExpandedChild?: boolean;
};
const originalNavOptions: navOptionType[] = [
{
id: 0,
isActive: false,
url: '/overview',
title: 'Overview',
Icon: <OverviewSVG />,
},
{
id: 1,
isActive: false,
url: '/network-components',
title: 'Network Components',
Icon: <NetworkComponentsSVG />,
nested: [
{
id: 3,
url: '/network-components/mixnodes',
title: 'Mixnodes',
},
{
id: 4,
url: '/network-components/gateways',
title: 'Gateways',
},
{
id: 5,
url: `${BIG_DIPPER}/validators`,
title: 'Validators',
},
],
},
{
id: 2,
isActive: false,
url: '/nodemap',
title: 'Nodemap',
Icon: <NodemapSVG />,
},
];
type ExpandableButtonType = {
id: number;
title: string;
url: string;
isActive?: boolean;
Icon?: React.ReactNode;
nested?: navOptionType[];
isChild?: boolean;
openDrawer: () => void;
closeDrawer: () => void;
drawIsOpen: boolean;
setToActive: (num: number) => void;
};
const ExpandableButton: React.FC<ExpandableButtonType> = ({
id,
url,
setToActive,
isActive,
openDrawer,
closeDrawer,
drawIsOpen,
Icon,
title,
nested,
isChild,
}) => {
const [dynamicStyle, setDynamicStyle] = React.useState({});
const [nestedOptions, toggleNestedOptions] = React.useState(false);
const [isExternal, setIsExternal] = React.useState<boolean>(false);
const { palette } = useTheme();
const handleClick = () => {
openDrawer();
if (title === 'Network Components' && nested) {
toggleNestedOptions(!nestedOptions);
}
setToActive(id);
};
React.useEffect(() => {
if (url) {
setIsExternal(url.includes('http'));
}
if (nested) {
setDynamicStyle({
background: '#242C3D',
borderRight: `3px solid ${palette.nym.highlight}`,
});
}
if (isChild) {
setDynamicStyle({
background: '#3C4558',
fontWeight: 800,
});
}
if (!nested && !isChild) {
setDynamicStyle({
background: '#242C3D',
borderRight: `3px solid ${palette.nym.highlight}`,
});
}
}, [url]);
React.useEffect(() => {
if (!drawIsOpen && nestedOptions) {
toggleNestedOptions(false);
}
}, [drawIsOpen]);
return (
<>
<ListItem
disablePadding
disableGutters
component={!nested ? Link : 'div'}
to={isExternal ? { pathname: url } : url}
target={isExternal ? '_blank' : ''}
sx={
isActive
? dynamicStyle
: { background: '#111826', borderRight: 'none' }
}
>
<ListItemButton
onClick={handleClick}
sx={{
pt: 2,
pb: 2,
background: isChild ? '#3C4558' : 'none',
}}
>
<ListItemIcon>{Icon}</ListItemIcon>
<ListItemText
primary={title}
sx={{
color: palette.nym.networkExplorer.nav.text,
}}
primaryTypographyProps={{
style: {
fontWeight: isActive ? 800 : 300,
},
}}
/>
{nested && nestedOptions && <ExpandLess />}
{nested && !nestedOptions && <ExpandMore />}
</ListItemButton>
</ListItem>
{nestedOptions &&
nested?.map((each) => (
<ExpandableButton
id={each.id}
url={each.url}
key={each.title}
title={each.title}
openDrawer={openDrawer}
drawIsOpen={drawIsOpen}
closeDrawer={closeDrawer}
setToActive={setToActive}
isChild
/>
))}
</>
);
};
ExpandableButton.defaultProps = {
Icon: null,
nested: undefined,
isChild: false,
isActive: false,
};
export const Nav: React.FC = ({ children }) => {
const [navOptionsState, updateNavOptionsState] =
React.useState(originalNavOptions);
const [open, setOpen] = React.useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const setToActive = (id: number) => {
const updated = navOptionsState.map((option) => ({
...option,
isActive: option.id === id,
}));
updateNavOptionsState(updated);
};
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<>
<Box sx={{ display: 'flex' }}>
<AppBar
sx={{
background: theme.palette.nym.networkExplorer.nav.background,
}}
>
<Toolbar
disableGutters
sx={{
display: 'flex',
justifyContent: 'space-between',
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: 205,
}}
>
<IconButton component="a" href={NYM_WEBSITE} target="_blank">
<NymLogoSVG />
</IconButton>
<Typography
variant="h6"
noWrap
sx={{
color: theme.palette.nym.networkExplorer.nav.text,
fontSize: '18px',
}}
>
<MuiLink
component={Link}
to="/overview"
underline="none"
color="inherit"
>
Network Explorer
</MuiLink>
</Typography>
</Box>
<Box
sx={{
mr: 2,
alignItems: 'center',
display: 'flex',
}}
>
{!isMobile && (
<Box
sx={{
display: 'flex',
flexDirection: 'row',
width: 'auto',
pr: 0,
pl: 2,
justifyContent: 'flex-end',
alignItems: 'center',
}}
>
<Socials />
<DarkLightSwitchDesktop defaultChecked />
</Box>
)}
{isMobile && <DarkLightSwitchMobile />}
</Box>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
open={open}
sx={{
background: theme.palette.nym.networkExplorer.nav.background,
}}
>
<DrawerHeader
sx={{
justifyContent: open ? 'flex-end' : 'center',
paddingLeft: 0,
}}
>
<IconButton
onClick={open ? handleDrawerClose : handleDrawerOpen}
sx={{
padding: 0,
ml: '7px',
color: theme.palette.nym.networkExplorer.nav.text,
}}
>
{open ? <ChevronLeft /> : <Menu />}
</IconButton>
</DrawerHeader>
<List sx={{ pt: 0, pb: 0 }}>
{navOptionsState.map((props) => (
<ExpandableButton
setToActive={setToActive}
key={props.url}
openDrawer={handleDrawerOpen}
drawIsOpen={open}
closeDrawer={handleDrawerClose}
{...props}
/>
))}
</List>
</Drawer>
<Box sx={{ width: '100%', p: 4, mt: 7 }}>
{children}
<Footer />
</Box>
</Box>
</>
);
};