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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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} />
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user