diff --git a/src/components/ExpandableListItemInput.tsx b/src/components/ExpandableListItemInput.tsx index 3e0bf95..66f94bc 100644 --- a/src/components/ExpandableListItemInput.tsx +++ b/src/components/ExpandableListItemInput.tsx @@ -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 Collapse from '@material-ui/core/Collapse' import { ListItem, Typography, Grid, IconButton, InputBase, Button } from '@material-ui/core' import { Edit, Minus, RotateCcw, Check } from 'react-feather' import ExpandableListItemActions from './ExpandableListItemActions' +import ExpandableListItemNote from './ExpandableListItemNote' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -33,44 +34,80 @@ const useStyles = makeStyles((theme: Theme) => keyMargin: { 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 { label: string - value: string + value?: string + placeholder?: string + helperText?: string + expandedOnly?: boolean + confirmLabel?: string + confirmLabelDisabled?: boolean + onChange?: (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 [open, setOpen] = useState(false) - const [inputValue, setInputValue] = useState(value) + const [open, setOpen] = useState(Boolean(expandedOnly)) + const [inputValue, setInputValue] = useState(value || '') const toggleOpen = () => setOpen(!open) + const handleChange = (e: ChangeEvent) => { + setInputValue(e.target.value) + + if (onChange) onChange(e.target.value) + } return ( <> - {label && {label}} + {label && ( + + {label} + + )}
{!open && value} - - {open ? ( - - ) : ( - - )} - + {!expandedOnly && ( + + {open ? ( + + ) : ( + + )} + + )}
setInputValue(e.target.value)} + placeholder={placeholder} + onChange={handleChange} fullWidth className={classes.content} autoFocus @@ -79,20 +116,25 @@ export default function ExpandableListItemKey({ label, value, onConfirm }: Props
+ {helperText && {helperText}} diff --git a/src/components/PeerDetail.tsx b/src/components/PeerDetail.tsx deleted file mode 100644 index a685399..0000000 --- a/src/components/PeerDetail.tsx +++ /dev/null @@ -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 ( - - {truncStringPortion(peerId, characterLength, characterLength)} - - ) -} diff --git a/src/components/TabsContainer.tsx b/src/components/TabsContainer.tsx index 5ed873c..ab618ba 100644 --- a/src/components/TabsContainer.tsx +++ b/src/components/TabsContainer.tsx @@ -1,9 +1,6 @@ import React, { ReactElement, ReactNode } from 'react' -import { makeStyles } from '@material-ui/core/styles' -import Tabs from '@material-ui/core/Tabs' -import Tab from '@material-ui/core/Tab' -import Typography from '@material-ui/core/Typography' -import Box from '@material-ui/core/Box' +import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' +import { Tab, Tabs } from '@material-ui/core' interface TabPanelProps { children?: ReactNode @@ -16,24 +13,25 @@ function TabPanel(props: TabPanelProps) { return ( ) } -const useStyles = makeStyles(() => ({ - root: { - flexGrow: 1, - }, -})) +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + flexGrow: 1, + }, + content: { + marginTop: theme.spacing(2), + }, + }), +) interface TabsValues { component: ReactNode - label: string + label: ReactNode } interface Props { @@ -55,16 +53,18 @@ export default function SimpleTabs({ values, index, indexChanged }: Props): Reac return (
- + {values.map(({ label }, idx) => ( ))} - {values.map(({ component }, idx) => ( - - {component} - - ))} +
+ {values.map(({ component }, idx) => ( + + {component} + + ))} +
) } diff --git a/src/pages/files/Download.tsx b/src/pages/files/Download.tsx index afc4622..c2994be 100644 --- a/src/pages/files/Download.tsx +++ b/src/pages/files/Download.tsx @@ -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(null) + const [referenceError, setReferenceError] = useState(undefined) - const handleReferenceChange = (e: React.ChangeEvent) => { - 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 ( - <> - - - - - - - {referenceError && {referenceError.message}} - + window.open(`${apiUrl}/bzz/${value}`, '_blank')} + onChange={validateChange} + helperText={referenceError} + confirmLabel={'Download'} + confirmLabelDisabled={Boolean(referenceError)} + placeholder="e.g. 31fb0362b1a42536134c86bc58b97ac0244e5c6630beec3e27c2d1cecb38c605" + expandedOnly + /> ) } diff --git a/src/pages/files/SelectStamp.tsx b/src/pages/files/SelectStamp.tsx index ee8d954..7fe369d 100644 --- a/src/pages/files/SelectStamp.tsx +++ b/src/pages/files/SelectStamp.tsx @@ -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 (
- - + {stamps.map(stamp => ( {stamp.usageText} - + {stamp.batchID.substr(0, 8)}[…] ))} diff --git a/src/pages/files/Upload.tsx b/src/pages/files/Upload.tsx index 4c6db71..25d0752 100644 --- a/src/pages/files/Upload.tsx +++ b/src/pages/files/Upload.tsx @@ -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(null) const [uploadReference, setUploadReference] = useState('') @@ -22,10 +33,14 @@ export default function Files(): ReactElement { const [selectedStamp, setSelectedStamp] = useState(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 ( -
-
- -
- {selectedStamp && ( -
- - with Postage Stamp{' '} - {selectedStamp.usageText}} - label={} - deleteIcon={} - onDelete={() => {} /* eslint-disable-line*/} - variant="outlined" - /> - - -
- )} - {!selectedStamp && } - - {file && } - {isUploadingFile && ( - - - - )} - {uploadReference && ( -
- {uploadReference} - -
- )} -
+ <> + +
+ {/* We have file and can upload display stamp selection */} + {file && !isUploadingFile && !uploadReference && ( + <> + + 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). + + {selectedStamp && ( + + Upload with Postage Stamp{' '} + {selectedStamp.usageText}} + label={{selectedStamp.batchID.substr(0, 8)}[…]} + deleteIcon={} + onDelete={() => {} /* eslint-disable-line*/} + variant="outlined" + /> + + } + value={} + /> + )} + {!selectedStamp && ( + + + + )} + + )} + + {/* We have file and can upload display upload button */} + {file && !uploadReference && ( + <> + + + {isUploadingFile && ( + + + + )} + + + + )} + + {/* File has already been uploaded */} + {uploadReference && ( + <> + + + + + + )}
-
+ ) } diff --git a/src/pages/files/index.tsx b/src/pages/files/index.tsx index ef6a095..4564bbd 100644 --- a/src/pages/files/index.tsx +++ b/src/pages/files/index.tsx @@ -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 return ( - - , - }, - { - label: 'upload', - component: , - }, - ]} - /> - + , + }, + { + label: 'upload', + component: , + }, + ]} + /> ) } diff --git a/src/pages/stamps/index.tsx b/src/pages/stamps/index.tsx index ec6cd38..f341c36 100644 --- a/src/pages/stamps/index.tsx +++ b/src/pages/stamps/index.tsx @@ -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 + useEffect(() => { start() diff --git a/src/theme.tsx b/src/theme.tsx index 8578b81..d2524f1 100644 --- a/src/theme.tsx +++ b/src/theme.tsx @@ -68,32 +68,18 @@ const componentsOverrides = (theme: Theme) => ({ }, MuiTab: { root: { - backgroundColor: 'transparent', - 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(','), + backgroundColor: theme.palette.background.paper, '&:hover': { - color: theme.palette.secondary, + backgroundColor: '#fcf2e8', + color: theme.palette.primary.main, opacity: 1, }, '&$selected': { - color: theme.palette.secondary, fontWeight: theme.typography.fontWeightMedium, }, - '&:focus': { - color: theme.palette.secondary, - }, + }, + textColorInherit: { + opacity: 0.5, }, }, MuiTabs: { @@ -101,7 +87,7 @@ const componentsOverrides = (theme: Theme) => ({ borderBottom: 'none', }, indicator: { - backgroundColor: theme.palette.primary.main, + backgroundColor: 'transparent', }, }, })