-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: info page redesign * style: headers and nesting for expandable lists * style: body typography * chore: bee version check * feat: key list item component * style: hover state for the key displaying component * style: left border on expanded items * fix: word break and splitting for hexstrings divisible by 6 * chore: moved the styles to classes * style: tooltips now have arrow * feat: indicate value has been copied * feat: removed the connectivity table in favor of info page * feat: added health tooltips for connectivity * refactor: simplified the topology into single component * fix: spacing between the bee version and the update button/chip * fix: spacing on devices not supporting rowGap
- Loading branch information
1 parent
c4c7d96
commit 57f5a73
Showing
11 changed files
with
306 additions
and
302 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { ReactElement, ReactNode, useState } from 'react' | ||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' | ||
import { Collapse, ListItem, ListItemText, Typography } from '@material-ui/core' | ||
import { ExpandLess, ExpandMore } from '@material-ui/icons' | ||
|
||
const useStyles = makeStyles((theme: Theme) => | ||
createStyles({ | ||
root: { | ||
width: '100%', | ||
padding: 0, | ||
margin: 0, | ||
marginTop: theme.spacing(4), | ||
'&:first-child': { | ||
marginTop: 0, | ||
}, | ||
}, | ||
rootLevel1: { marginTop: theme.spacing(1) }, | ||
rootLevel2: { marginTop: theme.spacing(0.5) }, | ||
header: { | ||
backgroundColor: theme.palette.background.paper, | ||
}, | ||
content: { | ||
marginTop: theme.spacing(1), | ||
}, | ||
}), | ||
) | ||
|
||
interface Props { | ||
children?: ReactNode | ||
label: ReactNode | ||
level?: 0 | 1 | 2 | ||
defaultOpen?: boolean | ||
} | ||
|
||
export default function ExpandableList({ children, label, level, defaultOpen }: Props): ReactElement | null { | ||
const classes = useStyles() | ||
const [open, setOpen] = useState<boolean>(Boolean(defaultOpen)) | ||
|
||
const handleClick = () => { | ||
setOpen(!open) | ||
} | ||
|
||
let rootLevelClass = '' | ||
let typographyVariant: 'h1' | 'h2' | 'h3' = 'h1' | ||
|
||
if (level === 1) { | ||
rootLevelClass = classes.rootLevel1 | ||
typographyVariant = 'h2' | ||
} else if (level === 2) { | ||
rootLevelClass = classes.rootLevel2 | ||
typographyVariant = 'h3' | ||
} | ||
|
||
return ( | ||
<div className={`${classes.root} ${rootLevelClass}`}> | ||
<ListItem button onClick={handleClick} className={classes.header}> | ||
<ListItemText primary={<Typography variant={typographyVariant}>{label}</Typography>} /> | ||
{open ? <ExpandLess /> : <ExpandMore />} | ||
</ListItem> | ||
<Collapse in={open} timeout="auto" unmountOnExit> | ||
<div className={classes.content}>{children}</div> | ||
</Collapse> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { ReactElement, ReactNode } from 'react' | ||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' | ||
import { Typography, Grid, IconButton, Tooltip } from '@material-ui/core' | ||
import { Info } from 'react-feather' | ||
import ListItem from '@material-ui/core/ListItem' | ||
|
||
const useStyles = makeStyles((theme: Theme) => | ||
createStyles({ | ||
header: { | ||
backgroundColor: theme.palette.background.paper, | ||
marginBottom: theme.spacing(0.25), | ||
wordBreak: 'break-word', | ||
}, | ||
copyValue: { | ||
cursor: 'pointer', | ||
padding: theme.spacing(1), | ||
borderRadius: 0, | ||
'&:hover': { | ||
backgroundColor: '#fcf2e8', | ||
color: theme.palette.primary.main, | ||
}, | ||
}, | ||
}), | ||
) | ||
|
||
interface Props { | ||
label?: string | ||
value?: ReactNode | ||
tooltip?: string | ||
} | ||
|
||
export default function ExpandableListItem({ label, value, tooltip }: Props): ReactElement | null { | ||
const classes = useStyles() | ||
|
||
return ( | ||
<ListItem className={classes.header}> | ||
<Grid container direction="row" justifyContent="space-between" alignItems="center"> | ||
{label && <Typography variant="body1">{label}</Typography>} | ||
{value && ( | ||
<Typography variant="body2"> | ||
{value} | ||
{tooltip && ( | ||
<Tooltip title={tooltip} placement="top" arrow> | ||
<IconButton size="small" className={classes.copyValue}> | ||
<Info strokeWidth={1} /> | ||
</IconButton> | ||
</Tooltip> | ||
)} | ||
</Typography> | ||
)} | ||
</Grid> | ||
</ListItem> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { ReactElement, useState } from 'react' | ||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' | ||
import Collapse from '@material-ui/core/Collapse' | ||
import { ListItem, Typography, Grid, IconButton, Tooltip } from '@material-ui/core' | ||
import { Eye, Minus } from 'react-feather' | ||
import { CopyToClipboard } from 'react-copy-to-clipboard' | ||
|
||
const useStyles = makeStyles((theme: Theme) => | ||
createStyles({ | ||
header: { | ||
backgroundColor: theme.palette.background.paper, | ||
marginBottom: theme.spacing(0.25), | ||
borderLeft: `${theme.spacing(0.25)}px solid rgba(0,0,0,0)`, | ||
wordBreak: 'break-word', | ||
}, | ||
headerOpen: { | ||
borderLeft: `${theme.spacing(0.25)}px solid ${theme.palette.primary.main}`, | ||
}, | ||
copyValue: { | ||
cursor: 'pointer', | ||
padding: theme.spacing(1), | ||
borderRadius: 0, | ||
'&:hover': { | ||
backgroundColor: '#fcf2e8', | ||
color: theme.palette.primary.main, | ||
}, | ||
}, | ||
content: { | ||
marginTop: theme.spacing(2), | ||
marginBottom: theme.spacing(2), | ||
}, | ||
keyMargin: { | ||
marginRight: theme.spacing(1), | ||
}, | ||
}), | ||
) | ||
|
||
interface Props { | ||
label: string | ||
value: string | ||
} | ||
|
||
const lengthWithoutPrefix = (s: string) => s.replace(/^0x/i, '').length | ||
|
||
function isPrefixedHexString(s: unknown): boolean { | ||
return typeof s === 'string' && /^0x[0-9a-f]+$/i.test(s) | ||
} | ||
|
||
const split = (s: string): string[] => { | ||
const nonPrefixLength = lengthWithoutPrefix(s) | ||
|
||
if (nonPrefixLength % 6 === 0) return s.match(/(0x|.{6})/gi) || [] | ||
|
||
return s.match(/(0x|.{1,8})/gi) || [] | ||
} | ||
|
||
export default function ExpandableListItemKey({ label, value }: Props): ReactElement | null { | ||
const classes = useStyles() | ||
const [open, setOpen] = useState(false) | ||
const [copied, setCopied] = useState(false) | ||
const toggleOpen = () => setOpen(!open) | ||
|
||
const tooltipClickHandler = () => setCopied(true) | ||
const tooltipCloseHandler = () => setCopied(false) | ||
|
||
const splitValues = split(value) | ||
const hasPrefix = isPrefixedHexString(value) | ||
|
||
return ( | ||
<ListItem className={`${classes.header} ${open ? classes.headerOpen : ''}`}> | ||
<Grid container direction="column" justifyContent="space-between" alignItems="stretch"> | ||
<Grid container direction="row" justifyContent="space-between" alignItems="center"> | ||
{label && <Typography variant="body1">{label}</Typography>} | ||
<Typography variant="body2"> | ||
<div> | ||
{!open && ( | ||
<span className={classes.copyValue}> | ||
<Tooltip title={copied ? 'Copied' : 'Copy'} placement="top" arrow onClose={tooltipCloseHandler}> | ||
<CopyToClipboard text={value}> | ||
<span onClick={tooltipClickHandler}>{`${ | ||
hasPrefix ? `${splitValues[0]} ${splitValues[1]}` : splitValues[0] | ||
}[…]${splitValues[splitValues.length - 1]}`}</span> | ||
</CopyToClipboard> | ||
</Tooltip> | ||
</span> | ||
)} | ||
<IconButton size="small" className={classes.copyValue}> | ||
{open ? <Minus onClick={toggleOpen} strokeWidth={1} /> : <Eye onClick={toggleOpen} strokeWidth={1} />} | ||
</IconButton> | ||
</div> | ||
</Typography> | ||
</Grid> | ||
<Collapse in={open} timeout="auto" unmountOnExit> | ||
<div className={classes.content}> | ||
<Tooltip title={copied ? 'Copied' : 'Copy'} placement="top" arrow onClose={tooltipCloseHandler}> | ||
<CopyToClipboard text={value}> | ||
{/* This has to be wrapped in two spans otherwise either the tooltip or the highlighting does not work*/} | ||
<span onClick={tooltipClickHandler}> | ||
<span className={classes.copyValue}> | ||
{splitValues.map((s, i) => ( | ||
<Typography variant="body2" key={i} className={classes.keyMargin} component="span"> | ||
{s} | ||
</Typography> | ||
))} | ||
</span> | ||
</span> | ||
</CopyToClipboard> | ||
</Tooltip> | ||
</div> | ||
</Collapse> | ||
</Grid> | ||
</ListItem> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,44 @@ | ||
import type { Topology } from '@ethersphere/bee-js' | ||
import { Grid } from '@material-ui/core/' | ||
import type { ReactElement } from 'react' | ||
import { pickThreshold, ThresholdValues } from '../utils/threshold' | ||
import StatCard from './StatCard' | ||
import ExpandableList from './ExpandableList' | ||
import ExpandableListItem from './ExpandableListItem' | ||
|
||
interface RootProps { | ||
interface Props { | ||
topology: Topology | null | ||
} | ||
|
||
interface Props extends RootProps { | ||
thresholds: ThresholdValues | ||
} | ||
|
||
const TopologyStats = (props: RootProps): ReactElement => { | ||
const TopologyStats = (props: Props): ReactElement => { | ||
const thresholds: ThresholdValues = { | ||
connectedPeers: pickThreshold('connectedPeers', props.topology?.connected || 0), | ||
population: pickThreshold('population', props.topology?.population || 0), | ||
depth: pickThreshold('depth', props.topology?.depth || 0), | ||
} | ||
|
||
return ( | ||
<> | ||
<Indicator {...props} thresholds={thresholds} /> | ||
<Metrics {...props} thresholds={thresholds} /> | ||
</> | ||
) | ||
} | ||
|
||
const Indicator = ({ thresholds }: Props): ReactElement => { | ||
const maximumTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.maximumScore, 0) | ||
const actualTotalScore = Object.values(thresholds).reduce((sum, item) => sum + item.score, 0) | ||
const percentageText = Math.round((actualTotalScore / maximumTotalScore) * 100) + '%' | ||
|
||
return ( | ||
<div style={{ marginBottom: '20px' }}> | ||
<StatCard label="Overall Health Indicator" statistic={percentageText} /> | ||
</div> | ||
) | ||
} | ||
|
||
const Metrics = ({ topology, thresholds }: Props): ReactElement => ( | ||
<Grid container spacing={3} style={{ marginBottom: '20px' }}> | ||
<Grid key={1} item xs={12} sm={12} md={6} lg={4} xl={4}> | ||
<StatCard | ||
<ExpandableList label="Connectivity" defaultOpen> | ||
<ExpandableListItem label="Overall Health Indicator" value={percentageText} /> | ||
<ExpandableListItem | ||
label="Connected Peers" | ||
statistic={topology?.connected.toString()} | ||
value={props.topology?.connected.toString()} | ||
tooltip={thresholds.connectedPeers.explanation} | ||
/> | ||
</Grid> | ||
<Grid key={2} item xs={12} sm={12} md={6} lg={4} xl={4}> | ||
<StatCard | ||
label="Population" | ||
statistic={topology?.population.toString()} | ||
<ExpandableListItem | ||
label="Pupulation" | ||
value={props.topology?.population.toString()} | ||
tooltip={thresholds.population.explanation} | ||
/> | ||
</Grid> | ||
<Grid key={3} item xs={12} sm={12} md={6} lg={4} xl={4}> | ||
<StatCard label="Depth" statistic={topology?.depth.toString()} tooltip={thresholds.depth.explanation} /> | ||
</Grid> | ||
</Grid> | ||
) | ||
<ExpandableListItem | ||
label="Depth" | ||
value={props.topology?.depth.toString()} | ||
tooltip={thresholds.depth.explanation} | ||
/> | ||
</ExpandableList> | ||
) | ||
} | ||
|
||
export default TopologyStats |
Oops, something went wrong.