feat: improve upload flow (#240)

* feat: separate flow for folder and file uploads

* feat: add basic index document detection

* feat(wip): separate preview step

* fix: fix kb and mb units

* feat: add post upload summary, add some styling

* feat: upload flow

* fix: change element order and add conditional rendering

* refactor: remove unused variables for now

* fix: put back stamp creation to stamp page

* refactor: rework postage stamps and grid

* feat: add website and folder icons

* feat: add asset preview to download flow, add file icon

* feat: add basic design to postage stamp selection dialog

* feat: add web icon, shorten stamp in preview

* feat: extract swarm hash in download flow

* fix: extract swarmbutton and solve icon hover and focus color

* fix: always show buy button on stamp page

* refactor: downgrade

* refactor: speed up icon transition

* style: improve download buttons

* style: change [back to upload] icon

* style: add spacing before swarm gateway text

* style: post upload summary spacing

* refactor: drop verticalspacing and use box

* refactor: merge icons to one component

* refactor: use conditions instead of weird assignment

* docs: explain filter(x => x)

* refactor: generalize capacity

* refactor: avoid passing arrow functions

* refactor: get rid of PaperGridContainer and Container

* fix: fix hover color for postage stamps

* feat: add disabled and loading state to buttons

* fix: make drag and drop work for websites

* feat: handle folders and non existing hashes

* fix: provide empty default value to select to avoid console warning

* style: remove body2 font variants

* fix: remove typo

* feat: disable folder upload, add website upload

* fix: disable showPreviews to avoid flickering

* feat(temp): remove folder upload

* fix: remove stuck focus on buttons even after rendering different buttons

* style: merge hover and focus styles, fix safari text wrap issue

* style: remove dropbox outline in safari
This commit is contained in:
Cafe137
2021-11-25 09:54:03 +01:00
committed by GitHub
parent 82cf6d9c01
commit 635621b04a
31 changed files with 1187 additions and 183 deletions
+5 -3
View File
@@ -6,7 +6,7 @@ import { ReactElement } from 'react'
const LIMIT = 100_000_000 // 100 megabytes
interface Props {
file: File
files: File[]
}
const useStyles = makeStyles((theme: Theme) =>
@@ -22,14 +22,16 @@ const useStyles = makeStyles((theme: Theme) =>
export default function UploadSizeAlert(props: Props): ReactElement | null {
const classes = useStyles()
const aboveLimit = props.file.size >= LIMIT
const totalSize = props.files.reduce((previous, current) => previous + current.size, 0)
const aboveLimit = totalSize >= LIMIT
return (
<Collapse in={aboveLimit}>
<div className={classes.root}>
<Alert severity="warning">
<AlertTitle>Warning</AlertTitle>
The file you are trying to upload is above the recommended size. The chunks may not be synchronised properly
The files you are trying to upload are above the recommended size. The chunks may not be synchronised properly
over the network.
</Alert>
</div>
+22
View File
@@ -0,0 +1,22 @@
import { ReactElement } from 'react'
interface Props {
width: string
usage: number
}
export function Capacity({ width, usage }: Props): ReactElement {
const integerUsage = Math.round(usage * 100)
const used = integerUsage + '%'
const free = 100 - 2 - integerUsage + '%'
return (
<div style={{ display: 'flex', alignItems: 'center', height: '100%', width }}>
<div style={{ display: 'flex', height: '4px', width: '100%' }}>
<div style={{ width: used, background: '#dd7200' }} />
<div style={{ width: '2%' }} />
<div style={{ width: free, background: '#c9c9c9' }} />
</div>
</div>
)
}
+59
View File
@@ -0,0 +1,59 @@
import { Collapse, ListItem } from '@material-ui/core'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { ExpandLess, ExpandMore } from '@material-ui/icons'
import { ReactElement, ReactNode, useState } from 'react'
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,
},
contentLevel0: {
marginTop: theme.spacing(1),
},
contentLevel12: {
marginTop: theme.spacing(0.25),
},
infoText: {
color: '#c9c9c9',
},
}),
)
interface Props {
children: ReactNode
expandable: ReactNode
defaultOpen?: boolean
}
export default function ExpandableElement({ children, expandable, defaultOpen }: Props): ReactElement | null {
const classes = useStyles()
const [open, setOpen] = useState<boolean>(Boolean(defaultOpen))
const handleClick = () => {
setOpen(!open)
}
return (
<div className={`${classes.root} ${classes.rootLevel2}`}>
<ListItem button onClick={handleClick} className={classes.header}>
{children}
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<div className={classes.contentLevel12}>{expandable}</div>
</Collapse>
</div>
)
}
+10 -7
View File
@@ -1,6 +1,6 @@
import { ReactElement, ReactNode } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Grid } from '@material-ui/core'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { ReactElement, ReactNode } from 'react'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -22,11 +22,14 @@ export default function ExpandableListItemActions({ children }: Props): ReactEle
if (Array.isArray(children)) {
return (
<Grid container direction="row">
{children.map((a, i) => (
<Grid key={i} className={classes.action}>
{a}
</Grid>
))}
{children
// Exclude falsy values to allow conditional rendering
.filter(x => x)
.map((a, i) => (
<Grid key={i} className={classes.action}>
{a}
</Grid>
))}
</Grid>
)
}
+10 -5
View File
@@ -1,9 +1,8 @@
import { ReactElement, ChangeEvent, useState } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Button, Grid, IconButton, InputBase, ListItem, Typography } from '@material-ui/core'
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 { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { ChangeEvent, ReactElement, useState } from 'react'
import { Check, Edit, Minus, RotateCcw } from 'react-feather'
import ExpandableListItemActions from './ExpandableListItemActions'
import ExpandableListItemNote from './ExpandableListItemNote'
@@ -55,6 +54,7 @@ interface Props {
confirmLabelDisabled?: boolean
onChange?: (value: string) => void
onConfirm: (value: string) => void
mapperFn?: (value: string) => string
}
export default function ExpandableListItemKey({
@@ -67,12 +67,17 @@ export default function ExpandableListItemKey({
expandedOnly,
helperText,
placeholder,
mapperFn,
}: Props): ReactElement | null {
const classes = useStyles()
const [open, setOpen] = useState(Boolean(expandedOnly))
const [inputValue, setInputValue] = useState<string>(value || '')
const toggleOpen = () => setOpen(!open)
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
if (mapperFn) {
e.target.value = mapperFn(e.target.value)
}
setInputValue(e.target.value)
if (onChange) onChange(e.target.value)
+4 -4
View File
@@ -1,9 +1,9 @@
import { ReactElement, useState } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Grid, IconButton, ListItem, Tooltip, Typography } from '@material-ui/core'
import Collapse from '@material-ui/core/Collapse'
import { ListItem, Typography, Grid, IconButton, Tooltip } from '@material-ui/core'
import { Eye, Minus } from 'react-feather'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { ReactElement, useState } from 'react'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import { Eye, Minus } from 'react-feather'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
+81
View File
@@ -0,0 +1,81 @@
import { Grid, IconButton, ListItem, Tooltip, Typography } from '@material-ui/core'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import { OpenInNewSharp } from '@material-ui/icons'
import { ReactElement, useState } from 'react'
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}`,
},
openLinkIcon: {
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),
},
copyValue: {
cursor: 'pointer',
padding: theme.spacing(1),
borderRadius: 0,
'&:hover': {
backgroundColor: '#fcf2e8',
color: theme.palette.primary.main,
},
},
}),
)
interface Props {
label: string
value: string
}
export default function ExpandableListItemLink({ label, value }: Props): ReactElement | null {
const classes = useStyles()
const [copied, setCopied] = useState(false)
const tooltipClickHandler = () => setCopied(true)
const tooltipCloseHandler = () => setCopied(false)
return (
<ListItem className={classes.header}>
<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>
<span className={classes.copyValue}>
<Tooltip title={copied ? 'Copied' : 'Copy'} placement="top" arrow onClose={tooltipCloseHandler}>
<CopyToClipboard text={value}>
<span onClick={tooltipClickHandler}>{value.slice(0, 19)}...</span>
</CopyToClipboard>
</Tooltip>
</span>
<IconButton size="small" className={classes.openLinkIcon}>
<OpenInNewSharp onClick={() => window.open(value)} strokeWidth={1} />
</IconButton>
</div>
</Typography>
</Grid>
</Grid>
</ListItem>
)
}
+30
View File
@@ -0,0 +1,30 @@
import { createStyles, makeStyles } from '@material-ui/core'
import { ReactElement } from 'react'
const useStyles = makeStyles(() =>
createStyles({
image: {
width: '100%',
height: '100%',
objectFit: 'cover',
},
}),
)
interface Props {
alt: string
src: string | undefined
maxHeight?: string
maxWidth?: string
}
export function FitImage(props: Props): ReactElement {
const classes = useStyles()
const inlineStyles: Record<string, string> = {}
props.maxHeight && (inlineStyles.maxHeight = props.maxHeight)
props.maxWidth && (inlineStyles.maxWidth = props.maxWidth)
return <img className={classes.image} alt={props.alt} src={props.src} style={inlineStyles} />
}
+31
View File
@@ -0,0 +1,31 @@
import { createStyles, makeStyles } from '@material-ui/core'
import { ReactElement } from 'react'
interface Props {
children: ReactElement | ReactElement[]
}
const useStyles = makeStyles(() =>
createStyles({
wrapper: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '175px',
height: '175px',
background: `repeating-linear-gradient(
45deg,
#efefef,
#efefef 4px,
#ffffff 4px,
#ffffff 8px
)`,
},
}),
)
export function StripedWrapper({ children }: Props): ReactElement {
const classes = useStyles()
return <div className={classes.wrapper}>{children}</div>
}
+66
View File
@@ -0,0 +1,66 @@
import { Button, CircularProgress, createStyles, makeStyles } from '@material-ui/core'
import React, { ReactElement } from 'react'
import { IconProps } from 'react-feather'
interface Props {
onClick: () => void
iconType: React.ComponentType<IconProps>
children: string
className?: string
disabled?: boolean
loading?: boolean
}
const useStyles = makeStyles(() =>
createStyles({
button: {
position: 'relative',
whiteSpace: 'nowrap',
'&:hover, &:focus': {
'& svg': {
stroke: '#fff',
transition: '0.1s',
},
},
},
spinnerWrapper: {
position: 'absolute',
left: '50%',
top: '50%',
width: '40px',
height: '40px',
transform: 'translate(-50%, -50%)',
},
}),
)
export function SwarmButton({ children, onClick, iconType, className, disabled, loading }: Props): ReactElement {
const classes = useStyles()
const icon = React.createElement(iconType, {
size: '1.25rem',
color: disabled ? 'rgba(0, 0, 0, 0.26)' : '#dd7700',
})
const classNames = className ? [className, classes.button].join(' ') : classes.button
return (
<Button
className={classNames}
onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
onClick()
event.currentTarget.blur()
}}
variant="contained"
startIcon={icon}
disabled={disabled}
>
{children}
{loading && (
<div className={classes.spinnerWrapper}>
<CircularProgress />
</div>
)}
</Button>
)
}