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:
@@ -1,10 +1,11 @@
|
|||||||
import { ReactElement, useState } from 'react'
|
import { ReactElement, ChangeEvent, useState } from 'react'
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
import Collapse from '@material-ui/core/Collapse'
|
import Collapse from '@material-ui/core/Collapse'
|
||||||
import { ListItem, Typography, Grid, IconButton, InputBase, Button } from '@material-ui/core'
|
import { ListItem, Typography, Grid, IconButton, InputBase, Button } from '@material-ui/core'
|
||||||
import { Edit, Minus, RotateCcw, Check } from 'react-feather'
|
import { Edit, Minus, RotateCcw, Check } from 'react-feather'
|
||||||
|
|
||||||
import ExpandableListItemActions from './ExpandableListItemActions'
|
import ExpandableListItemActions from './ExpandableListItemActions'
|
||||||
|
import ExpandableListItemNote from './ExpandableListItemNote'
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@@ -33,30 +34,64 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
keyMargin: {
|
keyMargin: {
|
||||||
marginRight: theme.spacing(1),
|
marginRight: theme.spacing(1),
|
||||||
},
|
},
|
||||||
|
unselectableLabel: {
|
||||||
|
cursor: 'default',
|
||||||
|
userSelect: 'none',
|
||||||
|
// Many browsers don't support yet the general user-select css property
|
||||||
|
WebkitUserSelect: 'none',
|
||||||
|
MozUserSelect: 'none',
|
||||||
|
msUserSelect: 'none',
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string
|
||||||
value: string
|
value?: string
|
||||||
|
placeholder?: string
|
||||||
|
helperText?: string
|
||||||
|
expandedOnly?: boolean
|
||||||
|
confirmLabel?: string
|
||||||
|
confirmLabelDisabled?: boolean
|
||||||
|
onChange?: (value: string) => void
|
||||||
onConfirm: (value: string) => void
|
onConfirm: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ExpandableListItemKey({ label, value, onConfirm }: Props): ReactElement | null {
|
export default function ExpandableListItemKey({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
onConfirm,
|
||||||
|
onChange,
|
||||||
|
confirmLabel,
|
||||||
|
confirmLabelDisabled,
|
||||||
|
expandedOnly,
|
||||||
|
helperText,
|
||||||
|
placeholder,
|
||||||
|
}: Props): ReactElement | null {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(Boolean(expandedOnly))
|
||||||
const [inputValue, setInputValue] = useState(value)
|
const [inputValue, setInputValue] = useState<string>(value || '')
|
||||||
const toggleOpen = () => setOpen(!open)
|
const toggleOpen = () => setOpen(!open)
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setInputValue(e.target.value)
|
||||||
|
|
||||||
|
if (onChange) onChange(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem className={`${classes.header} ${open ? classes.headerOpen : ''}`}>
|
<ListItem className={`${classes.header} ${open ? classes.headerOpen : ''}`}>
|
||||||
<Grid container direction="column" justifyContent="space-between" alignItems="stretch">
|
<Grid container direction="column" justifyContent="space-between" alignItems="stretch">
|
||||||
<Grid container direction="row" justifyContent="space-between" alignItems="center">
|
<Grid container direction="row" justifyContent="space-between" alignItems="center">
|
||||||
{label && <Typography variant="body1">{label}</Typography>}
|
{label && (
|
||||||
|
<Typography variant="body1" className={classes.unselectableLabel}>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
<div>
|
<div>
|
||||||
{!open && value}
|
{!open && value}
|
||||||
|
{!expandedOnly && (
|
||||||
<IconButton size="small" className={classes.copyValue}>
|
<IconButton size="small" className={classes.copyValue}>
|
||||||
{open ? (
|
{open ? (
|
||||||
<Minus onClick={toggleOpen} strokeWidth={1} />
|
<Minus onClick={toggleOpen} strokeWidth={1} />
|
||||||
@@ -64,13 +99,15 @@ export default function ExpandableListItemKey({ label, value, onConfirm }: Props
|
|||||||
<Edit onClick={toggleOpen} strokeWidth={1} />
|
<Edit onClick={toggleOpen} strokeWidth={1} />
|
||||||
)}
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
<InputBase
|
<InputBase
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={e => setInputValue(e.target.value)}
|
placeholder={placeholder}
|
||||||
|
onChange={handleChange}
|
||||||
fullWidth
|
fullWidth
|
||||||
className={classes.content}
|
className={classes.content}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -79,20 +116,25 @@ export default function ExpandableListItemKey({ label, value, onConfirm }: Props
|
|||||||
</Grid>
|
</Grid>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
|
{helperText && <ExpandableListItemNote>{helperText}</ExpandableListItemNote>}
|
||||||
<ExpandableListItemActions>
|
<ExpandableListItemActions>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={inputValue === value}
|
disabled={
|
||||||
|
inputValue === value ||
|
||||||
|
Boolean(confirmLabelDisabled) || // Disable if external validation is provided
|
||||||
|
(inputValue === '' && value === undefined) // Disable if no initial value was not provided and the field is empty. The undefined check is improtant so that it is possible to submit with empty input in other cases
|
||||||
|
}
|
||||||
startIcon={<Check size="1rem" />}
|
startIcon={<Check size="1rem" />}
|
||||||
onClick={() => onConfirm(inputValue)}
|
onClick={() => onConfirm(inputValue)}
|
||||||
>
|
>
|
||||||
Save
|
{confirmLabel || 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={inputValue === value}
|
disabled={inputValue === value || inputValue === ''}
|
||||||
startIcon={<RotateCcw size="1rem" />}
|
startIcon={<RotateCcw size="1rem" />}
|
||||||
onClick={() => setInputValue(value)}
|
onClick={() => setInputValue(value || '')}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import type { ReactElement } from 'react'
|
|
||||||
import { Typography } from '@material-ui/core'
|
|
||||||
|
|
||||||
function truncStringPortion(str: string, firstCharCount = 10, endCharCount = 10) {
|
|
||||||
return `${str.substring(0, firstCharCount)}...${str.substring(str.length - endCharCount, str.length)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
peerId: string
|
|
||||||
characterLength?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PeerDetail({ peerId, characterLength }: Props): ReactElement {
|
|
||||||
return (
|
|
||||||
<Typography
|
|
||||||
variant="button"
|
|
||||||
style={{
|
|
||||||
fontFamily: 'monospace, monospace',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{truncStringPortion(peerId, characterLength, characterLength)}
|
|
||||||
</Typography>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
import React, { ReactElement, ReactNode } from 'react'
|
import React, { ReactElement, ReactNode } from 'react'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
import Tabs from '@material-ui/core/Tabs'
|
import { Tab, Tabs } from '@material-ui/core'
|
||||||
import Tab from '@material-ui/core/Tab'
|
|
||||||
import Typography from '@material-ui/core/Typography'
|
|
||||||
import Box from '@material-ui/core/Box'
|
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
@@ -16,24 +13,25 @@ function TabPanel(props: TabPanelProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div role="tabpanel" hidden={value !== index} {...other}>
|
<div role="tabpanel" hidden={value !== index} {...other}>
|
||||||
{value === index && (
|
{value === index && children}
|
||||||
<Box p={3}>
|
|
||||||
<Typography>{children}</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
},
|
},
|
||||||
}))
|
content: {
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
interface TabsValues {
|
interface TabsValues {
|
||||||
component: ReactNode
|
component: ReactNode
|
||||||
label: string
|
label: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -55,16 +53,18 @@ export default function SimpleTabs({ values, index, indexChanged }: Props): Reac
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<Tabs value={v} onChange={handleChange}>
|
<Tabs value={v} onChange={handleChange} variant="fullWidth">
|
||||||
{values.map(({ label }, idx) => (
|
{values.map(({ label }, idx) => (
|
||||||
<Tab key={idx} label={label} />
|
<Tab key={idx} label={label} />
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
<div className={classes.content}>
|
||||||
{values.map(({ component }, idx) => (
|
{values.map(({ component }, idx) => (
|
||||||
<TabPanel key={idx} value={v} index={idx}>
|
<TabPanel key={idx} value={v} index={idx}>
|
||||||
{component}
|
{component}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +1,28 @@
|
|||||||
import { ReactElement, useState, useContext } from 'react'
|
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 { Context as SettingsContext } from '../../providers/Settings'
|
||||||
|
import ExpandableListItemInput from '../../components/ExpandableListItemInput'
|
||||||
import { Utils } from '@ethersphere/bee-js'
|
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 {
|
export default function Files(): ReactElement {
|
||||||
const classes = useStyles()
|
|
||||||
const { apiUrl } = useContext(SettingsContext)
|
const { apiUrl } = useContext(SettingsContext)
|
||||||
|
|
||||||
const [referenceInput, setReferenceInput] = useState('')
|
const [referenceError, setReferenceError] = useState<string | undefined>(undefined)
|
||||||
const [referenceError, setReferenceError] = useState<Error | null>(null)
|
|
||||||
|
|
||||||
const handleReferenceChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
const validateChange = (value: string) => {
|
||||||
setReferenceInput(e.target.value)
|
if (Utils.isHexString(value, 64) || Utils.isHexString(value, 128)) setReferenceError(undefined)
|
||||||
|
else setReferenceError('Incorrect format of swarm hash. Expected 64 or 128 hexstring characters.')
|
||||||
if (Utils.isHexString(e.target.value, 64) || Utils.isHexString(e.target.value, 128)) setReferenceError(null)
|
|
||||||
else setReferenceError(new Error('Incorrect format of swarm hash'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ExpandableListItemInput
|
||||||
<Paper className={classes.root}>
|
label="Swarm Hash"
|
||||||
<InputBase
|
onConfirm={value => window.open(`${apiUrl}/bzz/${value}`, '_blank')}
|
||||||
className={classes.input}
|
onChange={validateChange}
|
||||||
placeholder="Enter swarm reference e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
|
helperText={referenceError}
|
||||||
inputProps={{ 'aria-label': 'retrieve file from swarm' }}
|
confirmLabel={'Download'}
|
||||||
value={referenceInput}
|
confirmLabelDisabled={Boolean(referenceError)}
|
||||||
onChange={handleReferenceChange}
|
placeholder="e.g. 31fb0362b1a42536134c86bc58b97ac0244e5c6630beec3e27c2d1cecb38c605"
|
||||||
|
expandedOnly
|
||||||
/>
|
/>
|
||||||
<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>}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 React, { ReactElement } from 'react'
|
||||||
import PeerDetailDrawer from '../../components/PeerDetail'
|
import { Button, ListItemIcon, Typography, Menu, MenuItem } from '@material-ui/core'
|
||||||
import { EnrichedPostageBatch } from '../../providers/Stamps'
|
import { EnrichedPostageBatch } from '../../providers/Stamps'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -25,10 +21,10 @@ export default function SimpleMenu({ stamps, selectedStamp, setSelected }: Props
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
|
<Button variant="contained" aria-haspopup="true" onClick={handleClick}>
|
||||||
Change
|
Change
|
||||||
</Button>
|
</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 => (
|
{stamps.map(stamp => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={stamp.batchID}
|
key={stamp.batchID}
|
||||||
@@ -39,7 +35,7 @@ export default function SimpleMenu({ stamps, selectedStamp, setSelected }: Props
|
|||||||
selected={stamp.batchID === selectedStamp?.batchID}
|
selected={stamp.batchID === selectedStamp?.batchID}
|
||||||
>
|
>
|
||||||
<ListItemIcon>{stamp.usageText}</ListItemIcon>
|
<ListItemIcon>{stamp.usageText}</ListItemIcon>
|
||||||
<PeerDetailDrawer peerId={stamp.batchID} />
|
<Typography variant="body2">{stamp.batchID.substr(0, 8)}[…]</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
+84
-35
@@ -1,20 +1,31 @@
|
|||||||
import { Button, CircularProgress, Container } from '@material-ui/core'
|
import { Button, CircularProgress, Container, Avatar, Chip, Typography } from '@material-ui/core'
|
||||||
import Avatar from '@material-ui/core/Avatar'
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
import Chip from '@material-ui/core/Chip'
|
|
||||||
import { DropzoneArea } from 'material-ui-dropzone'
|
import { DropzoneArea } from 'material-ui-dropzone'
|
||||||
import { useSnackbar } from 'notistack'
|
import { useSnackbar } from 'notistack'
|
||||||
|
import { RotateCcw, Check } from 'react-feather'
|
||||||
import { ReactElement, useContext, useEffect, useState } from 'react'
|
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||||
import UploadSizeAlert from '../../components/AlertUploadSize'
|
import UploadSizeAlert from '../../components/AlertUploadSize'
|
||||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
import ClipboardCopy from '../../components/ClipboardCopy'
|
||||||
import PeerDetailDrawer from '../../components/PeerDetail'
|
|
||||||
import { Context, EnrichedPostageBatch } from '../../providers/Stamps'
|
import { Context, EnrichedPostageBatch } from '../../providers/Stamps'
|
||||||
import { Context as SettingsContext } from '../../providers/Settings'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import CreatePostageStamp from '../stamps/CreatePostageStampModal'
|
import CreatePostageStamp from '../stamps/CreatePostageStampModal'
|
||||||
import SelectStamp from './SelectStamp'
|
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
|
const MAX_FILE_SIZE = 1_000_000_000 // 1 gigabyte
|
||||||
|
|
||||||
export default function Files(): ReactElement {
|
export default function Files(): ReactElement {
|
||||||
|
const classes = useStyles()
|
||||||
const [dropzoneKey, setDropzoneKey] = useState(0)
|
const [dropzoneKey, setDropzoneKey] = useState(0)
|
||||||
const [file, setFile] = useState<File | null>(null)
|
const [file, setFile] = useState<File | null>(null)
|
||||||
const [uploadReference, setUploadReference] = useState('')
|
const [uploadReference, setUploadReference] = useState('')
|
||||||
@@ -22,10 +33,14 @@ export default function Files(): ReactElement {
|
|||||||
|
|
||||||
const [selectedStamp, setSelectedStamp] = useState<EnrichedPostageBatch | null>(null)
|
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 { beeApi } = useContext(SettingsContext)
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refresh()
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Choose a postage stamp that has the lowest usage
|
// Choose a postage stamp that has the lowest usage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedStamp && stamps && stamps.length > 0) {
|
if (!selectedStamp && stamps && stamps.length > 0) {
|
||||||
@@ -47,68 +62,102 @@ export default function Files(): ReactElement {
|
|||||||
setIsUploadingFile(true)
|
setIsUploadingFile(true)
|
||||||
beeApi
|
beeApi
|
||||||
.uploadFile(selectedStamp.batchID, file)
|
.uploadFile(selectedStamp.batchID, file)
|
||||||
.then(hash => {
|
.then(hash => setUploadReference(hash.reference))
|
||||||
window.setTimeout(() => {
|
|
||||||
setFile(null)
|
|
||||||
setUploadReference(hash.reference)
|
|
||||||
setDropzoneKey(dropzoneKey + 1)
|
|
||||||
}, 0)
|
|
||||||
})
|
|
||||||
.catch(e => enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' }))
|
.catch(e => enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' }))
|
||||||
.finally(() => {
|
.finally(() => setIsUploadingFile(false))
|
||||||
setIsUploadingFile(false)
|
}
|
||||||
})
|
|
||||||
|
const uploadNew = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setFile(null)
|
||||||
|
setDropzoneKey(dropzoneKey + 1)
|
||||||
|
setUploadReference('')
|
||||||
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChange = (files?: File[]) => {
|
const handleChange = (files?: File[]) => {
|
||||||
|
setUploadReference('')
|
||||||
|
|
||||||
if (files) {
|
if (files) {
|
||||||
setFile(files[0])
|
setFile(files[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<div>
|
|
||||||
<DropzoneArea
|
<DropzoneArea
|
||||||
key={'dropzone-' + dropzoneKey}
|
key={'dropzone-' + dropzoneKey}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
filesLimit={1}
|
filesLimit={1}
|
||||||
maxFileSize={MAX_FILE_SIZE}
|
maxFileSize={MAX_FILE_SIZE}
|
||||||
/>
|
/>
|
||||||
<div style={{ marginTop: '15px' }}>
|
<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 it’s sufficient for this file).
|
||||||
|
</ExpandableListItemNote>
|
||||||
{selectedStamp && (
|
{selectedStamp && (
|
||||||
<div style={{ display: 'flex' }}>
|
<ExpandableListItem
|
||||||
<small>
|
label={
|
||||||
with Postage Stamp{' '}
|
<>
|
||||||
|
Upload with Postage Stamp{' '}
|
||||||
<Chip
|
<Chip
|
||||||
avatar={<Avatar>{selectedStamp.usageText}</Avatar>}
|
avatar={<Avatar>{selectedStamp.usageText}</Avatar>}
|
||||||
label={<PeerDetailDrawer peerId={selectedStamp.batchID} characterLength={6} />}
|
label={<Typography variant="body2">{selectedStamp.batchID.substr(0, 8)}[…]</Typography>}
|
||||||
deleteIcon={<ClipboardCopy value={selectedStamp.batchID} />}
|
deleteIcon={<ClipboardCopy value={selectedStamp.batchID} />}
|
||||||
onDelete={() => {} /* eslint-disable-line*/}
|
onDelete={() => {} /* eslint-disable-line*/}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
</small>
|
</>
|
||||||
<SelectStamp stamps={stamps} selectedStamp={selectedStamp} setSelected={setSelectedStamp} />
|
}
|
||||||
</div>
|
value={<SelectStamp stamps={stamps} selectedStamp={selectedStamp} setSelected={setSelectedStamp} />}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{!selectedStamp && <CreatePostageStamp />}
|
{!selectedStamp && (
|
||||||
<Button disabled={!file && isUploadingFile && !selectedStamp} onClick={() => uploadFile()}>
|
<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
|
Upload
|
||||||
</Button>
|
</Button>
|
||||||
{file && <UploadSizeAlert file={file} />}
|
|
||||||
{isUploadingFile && (
|
{isUploadingFile && (
|
||||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
<Container className={classes.loadingProgress}>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
</ExpandableListItemActions>
|
||||||
|
<UploadSizeAlert file={file} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* File has already been uploaded */}
|
||||||
{uploadReference && (
|
{uploadReference && (
|
||||||
<div style={{ marginBottom: '15px', display: 'flex' }}>
|
<>
|
||||||
<span>{uploadReference}</span>
|
<ExpandableListItemKey label="Swarm Reference" value={uploadReference} />
|
||||||
<ClipboardCopy value={uploadReference} />
|
<ExpandableListItemActions>
|
||||||
</div>
|
<Button variant="contained" onClick={uploadNew} startIcon={<RotateCcw size="1rem" />}>
|
||||||
|
Upload New File
|
||||||
|
</Button>
|
||||||
|
</ExpandableListItemActions>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import { ReactElement, useContext } from 'react'
|
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 Download from './Download'
|
||||||
import Upload from './Upload'
|
import Upload from './Upload'
|
||||||
import TabsContainer from '../../components/TabsContainer'
|
import TabsContainer from '../../components/TabsContainer'
|
||||||
|
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||||
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
|
|
||||||
export default function Files(): ReactElement {
|
export default function Files(): ReactElement {
|
||||||
const { status } = useContext(Context)
|
const { status } = useContext(BeeContext)
|
||||||
|
|
||||||
if (!status.all) return <TroubleshootConnectionCard />
|
if (!status.all) return <TroubleshootConnectionCard />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="sm">
|
|
||||||
<TabsContainer
|
<TabsContainer
|
||||||
values={[
|
values={[
|
||||||
{
|
{
|
||||||
@@ -27,6 +24,5 @@ export default function Files(): ReactElement {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Container>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { Container, CircularProgress } from '@material-ui/core'
|
|||||||
import StampsTable from './StampsTable'
|
import StampsTable from './StampsTable'
|
||||||
import CreatePostageStampModal from './CreatePostageStampModal'
|
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(() =>
|
const useStyles = makeStyles(() =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@@ -25,7 +27,11 @@ const useStyles = makeStyles(() =>
|
|||||||
|
|
||||||
export default function Accounting(): ReactElement {
|
export default function Accounting(): ReactElement {
|
||||||
const classes = useStyles()
|
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(() => {
|
useEffect(() => {
|
||||||
start()
|
start()
|
||||||
|
|
||||||
|
|||||||
+6
-20
@@ -68,32 +68,18 @@ const componentsOverrides = (theme: Theme) => ({
|
|||||||
},
|
},
|
||||||
MuiTab: {
|
MuiTab: {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: theme.palette.background.paper,
|
||||||
fontWeight: theme.typography.fontWeightRegular,
|
|
||||||
marginRight: theme.spacing(4),
|
|
||||||
fontFamily: [
|
|
||||||
'-apple-system',
|
|
||||||
'BlinkMacSystemFont',
|
|
||||||
'"Segoe UI"',
|
|
||||||
'Roboto',
|
|
||||||
'"Helvetica Neue"',
|
|
||||||
'Arial',
|
|
||||||
'sans-serif',
|
|
||||||
'"Apple Color Emoji"',
|
|
||||||
'"Segoe UI Emoji"',
|
|
||||||
'"Segoe UI Symbol"',
|
|
||||||
].join(','),
|
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
color: theme.palette.secondary,
|
backgroundColor: '#fcf2e8',
|
||||||
|
color: theme.palette.primary.main,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
'&$selected': {
|
'&$selected': {
|
||||||
color: theme.palette.secondary,
|
|
||||||
fontWeight: theme.typography.fontWeightMedium,
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
},
|
},
|
||||||
'&:focus': {
|
|
||||||
color: theme.palette.secondary,
|
|
||||||
},
|
},
|
||||||
|
textColorInherit: {
|
||||||
|
opacity: 0.5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiTabs: {
|
MuiTabs: {
|
||||||
@@ -101,7 +87,7 @@ const componentsOverrides = (theme: Theme) => ({
|
|||||||
borderBottom: 'none',
|
borderBottom: 'none',
|
||||||
},
|
},
|
||||||
indicator: {
|
indicator: {
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: 'transparent',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user