feat: files page updated to latest design (#218)

* feat: altered the design of the tabs and redid the download tab

* feat: redesign the upload file

* fix: styles of tabs on hover

* fix: display troubleshoot component when the status of the node is not OK

* fix: when removing the file, remove the reference upload reference as well

* fix: on inputs the label should not be selectable

* feat: add placeholder to inputs and make the label non-selectable

* refactor: improved the readability of the upload file component

* chore: removed PeerDetail component

* fix: replaced "batch" with (postage) "stamp" for clarity

* refactor: address PR review comments

* feat: disable the download button if there is no value
This commit is contained in:
Vojtech Simetka
2021-10-08 13:34:55 +02:00
committed by GitHub
parent 03265687ad
commit 93af7f35a3
9 changed files with 237 additions and 224 deletions
+15 -53
View File
@@ -1,66 +1,28 @@
import { ReactElement, useState, useContext } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Paper, InputBase, IconButton, FormHelperText } from '@material-ui/core'
import { Search } from '@material-ui/icons'
import { Context as SettingsContext } from '../../providers/Settings'
import ExpandableListItemInput from '../../components/ExpandableListItemInput'
import { Utils } from '@ethersphere/bee-js'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
padding: theme.spacing(0.25),
display: 'flex',
alignItems: 'center',
},
input: {
marginLeft: theme.spacing(1),
flex: 1,
},
iconButton: {
padding: 10,
},
divider: {
height: 28,
margin: 4,
},
}),
)
export default function Files(): ReactElement {
const classes = useStyles()
const { apiUrl } = useContext(SettingsContext)
const [referenceInput, setReferenceInput] = useState('')
const [referenceError, setReferenceError] = useState<Error | null>(null)
const [referenceError, setReferenceError] = useState<string | undefined>(undefined)
const handleReferenceChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setReferenceInput(e.target.value)
if (Utils.isHexString(e.target.value, 64) || Utils.isHexString(e.target.value, 128)) setReferenceError(null)
else setReferenceError(new Error('Incorrect format of swarm hash'))
const validateChange = (value: string) => {
if (Utils.isHexString(value, 64) || Utils.isHexString(value, 128)) setReferenceError(undefined)
else setReferenceError('Incorrect format of swarm hash. Expected 64 or 128 hexstring characters.')
}
return (
<>
<Paper className={classes.root}>
<InputBase
className={classes.input}
placeholder="Enter swarm reference e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
inputProps={{ 'aria-label': 'retrieve file from swarm' }}
value={referenceInput}
onChange={handleReferenceChange}
/>
<IconButton
href={`${apiUrl}/bzz/${referenceInput}`}
target="_blank"
disabled={referenceError !== null || !referenceInput}
className={classes.iconButton}
aria-label="download"
>
<Search />
</IconButton>
</Paper>
{referenceError && <FormHelperText error>{referenceError.message}</FormHelperText>}
</>
<ExpandableListItemInput
label="Swarm Hash"
onConfirm={value => window.open(`${apiUrl}/bzz/${value}`, '_blank')}
onChange={validateChange}
helperText={referenceError}
confirmLabel={'Download'}
confirmLabelDisabled={Boolean(referenceError)}
placeholder="e.g. 31fb0362b1a42536134c86bc58b97ac0244e5c6630beec3e27c2d1cecb38c605"
expandedOnly
/>
)
}
+4 -8
View File
@@ -1,9 +1,5 @@
import Button from '@material-ui/core/Button'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import React, { ReactElement } from 'react'
import PeerDetailDrawer from '../../components/PeerDetail'
import { Button, ListItemIcon, Typography, Menu, MenuItem } from '@material-ui/core'
import { EnrichedPostageBatch } from '../../providers/Stamps'
interface Props {
@@ -25,10 +21,10 @@ export default function SimpleMenu({ stamps, selectedStamp, setSelected }: Props
return (
<div>
<Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
<Button variant="contained" aria-haspopup="true" onClick={handleClick}>
Change
</Button>
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
<Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
{stamps.map(stamp => (
<MenuItem
key={stamp.batchID}
@@ -39,7 +35,7 @@ export default function SimpleMenu({ stamps, selectedStamp, setSelected }: Props
selected={stamp.batchID === selectedStamp?.batchID}
>
<ListItemIcon>{stamp.usageText}</ListItemIcon>
<PeerDetailDrawer peerId={stamp.batchID} />
<Typography variant="body2">{stamp.batchID.substr(0, 8)}[]</Typography>
</MenuItem>
))}
</Menu>
+106 -57
View File
@@ -1,20 +1,31 @@
import { Button, CircularProgress, Container } from '@material-ui/core'
import Avatar from '@material-ui/core/Avatar'
import Chip from '@material-ui/core/Chip'
import { Button, CircularProgress, Container, Avatar, Chip, Typography } from '@material-ui/core'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { DropzoneArea } from 'material-ui-dropzone'
import { useSnackbar } from 'notistack'
import { RotateCcw, Check } from 'react-feather'
import { ReactElement, useContext, useEffect, useState } from 'react'
import UploadSizeAlert from '../../components/AlertUploadSize'
import ClipboardCopy from '../../components/ClipboardCopy'
import PeerDetailDrawer from '../../components/PeerDetail'
import { Context, EnrichedPostageBatch } from '../../providers/Stamps'
import { Context as SettingsContext } from '../../providers/Settings'
import CreatePostageStamp from '../stamps/CreatePostageStampModal'
import SelectStamp from './SelectStamp'
import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import ExpandableListItemNote from '../../components/ExpandableListItemNote'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
content: { marginTop: theme.spacing(2) },
loadingProgress: { textAlign: 'center', padding: '50px' },
}),
)
const MAX_FILE_SIZE = 1_000_000_000 // 1 gigabyte
export default function Files(): ReactElement {
const classes = useStyles()
const [dropzoneKey, setDropzoneKey] = useState(0)
const [file, setFile] = useState<File | null>(null)
const [uploadReference, setUploadReference] = useState('')
@@ -22,10 +33,14 @@ export default function Files(): ReactElement {
const [selectedStamp, setSelectedStamp] = useState<EnrichedPostageBatch | null>(null)
const { isLoading, error, stamps } = useContext(Context)
const { isLoading, error, stamps, refresh } = useContext(Context)
const { beeApi } = useContext(SettingsContext)
const { enqueueSnackbar } = useSnackbar()
useEffect(() => {
refresh()
}, [])
// Choose a postage stamp that has the lowest usage
useEffect(() => {
if (!selectedStamp && stamps && stamps.length > 0) {
@@ -47,68 +62,102 @@ export default function Files(): ReactElement {
setIsUploadingFile(true)
beeApi
.uploadFile(selectedStamp.batchID, file)
.then(hash => {
window.setTimeout(() => {
setFile(null)
setUploadReference(hash.reference)
setDropzoneKey(dropzoneKey + 1)
}, 0)
})
.then(hash => setUploadReference(hash.reference))
.catch(e => enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' }))
.finally(() => {
setIsUploadingFile(false)
})
.finally(() => setIsUploadingFile(false))
}
const uploadNew = () => {
setTimeout(() => {
setFile(null)
setDropzoneKey(dropzoneKey + 1)
setUploadReference('')
}, 0)
}
const handleChange = (files?: File[]) => {
setUploadReference('')
if (files) {
setFile(files[0])
}
}
return (
<div>
<div>
<DropzoneArea
key={'dropzone-' + dropzoneKey}
onChange={handleChange}
filesLimit={1}
maxFileSize={MAX_FILE_SIZE}
/>
<div style={{ marginTop: '15px' }}>
{selectedStamp && (
<div style={{ display: 'flex' }}>
<small>
with Postage Stamp{' '}
<Chip
avatar={<Avatar>{selectedStamp.usageText}</Avatar>}
label={<PeerDetailDrawer peerId={selectedStamp.batchID} characterLength={6} />}
deleteIcon={<ClipboardCopy value={selectedStamp.batchID} />}
onDelete={() => {} /* eslint-disable-line*/}
variant="outlined"
/>
</small>
<SelectStamp stamps={stamps} selectedStamp={selectedStamp} setSelected={setSelectedStamp} />
</div>
)}
{!selectedStamp && <CreatePostageStamp />}
<Button disabled={!file && isUploadingFile && !selectedStamp} onClick={() => uploadFile()}>
Upload
</Button>
{file && <UploadSizeAlert file={file} />}
{isUploadingFile && (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)}
{uploadReference && (
<div style={{ marginBottom: '15px', display: 'flex' }}>
<span>{uploadReference}</span>
<ClipboardCopy value={uploadReference} />
</div>
)}
</div>
<>
<DropzoneArea
key={'dropzone-' + dropzoneKey}
onChange={handleChange}
filesLimit={1}
maxFileSize={MAX_FILE_SIZE}
/>
<div className={classes.content}>
{/* We have file and can upload display stamp selection */}
{file && !isUploadingFile && !uploadReference && (
<>
<ExpandableListItemNote>
To upload this file to your node, you need a postage stamp. You can buy a new one or you can use an
existing stamp (providing its sufficient for this file).
</ExpandableListItemNote>
{selectedStamp && (
<ExpandableListItem
label={
<>
Upload with Postage Stamp{' '}
<Chip
avatar={<Avatar>{selectedStamp.usageText}</Avatar>}
label={<Typography variant="body2">{selectedStamp.batchID.substr(0, 8)}[]</Typography>}
deleteIcon={<ClipboardCopy value={selectedStamp.batchID} />}
onDelete={() => {} /* eslint-disable-line*/}
variant="outlined"
/>
</>
}
value={<SelectStamp stamps={stamps} selectedStamp={selectedStamp} setSelected={setSelectedStamp} />}
/>
)}
{!selectedStamp && (
<ExpandableListItemActions>
<CreatePostageStamp />
</ExpandableListItemActions>
)}
</>
)}
{/* We have file and can upload display upload button */}
{file && !uploadReference && (
<>
<ExpandableListItemActions>
<Button
variant="contained"
disabled={!file && isUploadingFile && !selectedStamp}
onClick={() => uploadFile()}
startIcon={<Check size="1rem" />}
>
Upload
</Button>
{isUploadingFile && (
<Container className={classes.loadingProgress}>
<CircularProgress />
</Container>
)}
</ExpandableListItemActions>
<UploadSizeAlert file={file} />
</>
)}
{/* File has already been uploaded */}
{uploadReference && (
<>
<ExpandableListItemKey label="Swarm Reference" value={uploadReference} />
<ExpandableListItemActions>
<Button variant="contained" onClick={uploadNew} startIcon={<RotateCcw size="1rem" />}>
Upload New File
</Button>
</ExpandableListItemActions>
</>
)}
</div>
</div>
</>
)
}
+15 -19
View File
@@ -1,32 +1,28 @@
import { ReactElement, useContext } from 'react'
import { Container } from '@material-ui/core'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context } from '../../providers/Bee'
import Download from './Download'
import Upload from './Upload'
import TabsContainer from '../../components/TabsContainer'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee'
export default function Files(): ReactElement {
const { status } = useContext(Context)
const { status } = useContext(BeeContext)
if (!status.all) return <TroubleshootConnectionCard />
return (
<Container maxWidth="sm">
<TabsContainer
values={[
{
label: 'download',
component: <Download />,
},
{
label: 'upload',
component: <Upload />,
},
]}
/>
</Container>
<TabsContainer
values={[
{
label: 'download',
component: <Download />,
},
{
label: 'upload',
component: <Upload />,
},
]}
/>
)
}
+8 -2
View File
@@ -5,7 +5,9 @@ import { Container, CircularProgress } from '@material-ui/core'
import StampsTable from './StampsTable'
import CreatePostageStampModal from './CreatePostageStampModal'
import { Context } from '../../providers/Stamps'
import { Context as StampsContext } from '../../providers/Stamps'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee'
const useStyles = makeStyles(() =>
createStyles({
@@ -25,7 +27,11 @@ const useStyles = makeStyles(() =>
export default function Accounting(): ReactElement {
const classes = useStyles()
const { stamps, isLoading, error, start, stop } = useContext(Context)
const { stamps, isLoading, error, start, stop } = useContext(StampsContext)
const { status } = useContext(BeeContext)
if (!status.all) return <TroubleshootConnectionCard />
useEffect(() => {
start()