frontend: Replace tracks with new file through upload in edit page

This commit is contained in:
Paul Bienkowski 2021-02-26 23:10:10 +01:00
parent 40882549f7
commit cc4679d048
4 changed files with 112 additions and 66 deletions

View file

@ -0,0 +1,64 @@
import React from 'react'
import {Icon, Segment, Header, Button} from 'semantic-ui-react'
import {FileDrop} from 'components'
export default function FileUploadField({onSelect: onSelect_, multiple}) {
const labelRef = React.useRef()
const [labelRefState, setLabelRefState] = React.useState()
const onSelect = multiple ? onSelect_ : (files) => onSelect_(files?.[0])
React.useLayoutEffect(
() => {
setLabelRefState(labelRef.current)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[labelRef.current]
)
function onChangeField(e) {
if (e.target.files && e.target.files.length) {
onSelect(e.target.files)
}
e.target.value = '' // reset the form field for uploading again
}
return (
<>
<input
type="file"
id="upload-field"
style={{width: 0, height: 0, position: 'fixed', left: -1000, top: -1000, opacity: 0.001}}
multiple={multiple}
accept=".csv"
onChange={onChangeField}
/>
<label htmlFor="upload-field" ref={labelRef}>
{labelRefState && (
<FileDrop onDrop={onSelect} frame={labelRefState}>
{({draggingOverFrame, draggingOverTarget, onDragOver, onDragLeave, onDrop, onClick}) => (
<Segment
placeholder
{...{onDragOver, onDragLeave, onDrop}}
style={{
background: draggingOverTarget || draggingOverFrame ? '#E0E0EE' : null,
transition: 'background 0.2s',
}}
>
<Header icon>
<Icon name="cloud upload" />
Drop file{multiple ? 's' : ''} here or click to select {multiple ? 'them' : 'one'} for upload
</Header>
<Button primary as="span">
Upload file{multiple ? 's' : ''}
</Button>
</Segment>
)}
</FileDrop>
)}
</label>
</>
)
}

View file

@ -1,4 +1,5 @@
export {default as FileDrop} from './FileDrop'
export {default as FileUploadField} from './FileUploadField'
export {default as FormattedDate} from './FormattedDate'
export {default as LoginButton} from './LoginButton'
export {default as Map} from './Map'

View file

@ -1,8 +1,8 @@
import React from 'react'
import _ from 'lodash'
import {connect} from 'react-redux'
import {Confirm, Grid, Button, Icon, Popup, Form, Ref, TextArea, Checkbox} from 'semantic-ui-react'
import {useHistory, useParams} from 'react-router-dom'
import {Divider, Message, Confirm, Grid, Button, Icon, Popup, Form, Ref, TextArea, Checkbox} from 'semantic-ui-react'
import {useHistory, useParams, Link} from 'react-router-dom'
import {concat, of, from} from 'rxjs'
import {pluck, distinctUntilChanged, map, switchMap} from 'rxjs/operators'
import {useObservable} from 'rxjs-hooks'
@ -10,9 +10,32 @@ import {findInput} from 'utils'
import {useForm, Controller} from 'react-hook-form'
import api from 'api'
import {Page} from 'components'
import {Page, FileUploadField} from 'components'
import type {Track} from 'types'
import {FileUploadStatus} from 'pages/UploadPage'
function ReplaceTrackData({slug}) {
const [file, setFile] = React.useState(null)
const [result, setResult] = React.useState(null)
const onComplete = React.useCallback((_id, r) => setResult(r), [setResult])
return (
<>
<h2>Replace track data</h2>
{!file ? (
<FileUploadField onSelect={setFile} />
) : result ? (
<Message>
Upload complete. <Link to={`/tracks/${slug}`}>Show track</Link>
</Message>
) : (
<FileUploadStatus {...{file, onComplete, slug}} />
)}
</>
)
}
const TrackEditor = connect((state) => ({login: state.login}))(function TrackEditor({login}) {
const [busy, setBusy] = React.useState(false)
const {register, control, handleSubmit} = useForm()
@ -75,7 +98,7 @@ const TrackEditor = connect((state) => ({login: state.login}))(function TrackEdi
<Grid centered relaxed divided>
<Grid.Row>
<Grid.Column width={10}>
<h2>Edit {track ? (track.title || 'Unnamed track') : 'track'}</h2>
<h2>Edit {track ? track.title || 'Unnamed track' : 'track'}</h2>
<Form loading={loading} key={track?.slug} onSubmit={onSubmit}>
<Ref innerRef={findInput(register)}>
<Form.Input label="Title" name="title" defaultValue={track?.title} style={{fontSize: '120%'}} />
@ -136,13 +159,19 @@ const TrackEditor = connect((state) => ({login: state.login}))(function TrackEdi
</Form>
</Grid.Column>
<Grid.Column width={6}>
<ReplaceTrackData slug={slug} />
<Divider />
<h2>Danger zone</h2>
<p>
You can remove this track from your account and the portal if you like. However, if at any point you have
published this track, we cannot guarantee that there are no versions of it in the public data repository,
or any copy thereof.
</p>
<Button color="red" onClick={() => setConfirmDelete(true)}>Delete</Button>
<Button color="red" onClick={() => setConfirmDelete(true)}>
Delete
</Button>
<Confirm open={confirmDelete} onCancel={() => setConfirmDelete(false)} onConfirm={onDelete} />
</Grid.Column>
</Grid.Row>

View file

@ -1,9 +1,9 @@
import _ from 'lodash'
import React from 'react'
import {List, Loader, Table, Icon, Segment, Header, Button} from 'semantic-ui-react'
import {List, Loader, Table, Icon} from 'semantic-ui-react'
import {Link} from 'react-router-dom'
import {FileDrop, Page} from 'components'
import {FileUploadField, Page} from 'components'
import type {Track} from 'types'
import api from 'api'
@ -40,14 +40,16 @@ type FileUploadResult =
errors: Record<string, string>
}
function FileUploadStatus({
export function FileUploadStatus({
id,
file,
onComplete,
slug,
}: {
id: string
file: File
onComplete: (result: FileUploadResult) => void
slug?: string
}) {
const [progress, setProgress] = React.useState(0)
@ -70,7 +72,11 @@ function FileUploadStatus({
xhr.responseType = 'json'
xhr.onload = onLoad
xhr.upload.onprogress = onProgress
xhr.open('POST', '/api/tracks')
if (slug) {
xhr.open('PUT', `/api/tracks/${slug}`)
} else {
xhr.open('POST', '/api/tracks')
}
api.getValidAccessToken().then((accessToken) => {
xhr.setRequestHeader('Authorization', accessToken)
@ -85,7 +91,8 @@ function FileUploadStatus({
return (
<span>
<Loader inline size="mini" active /> {progress < 1 ? `Uploading ${(progress * 100).toFixed(0)}%` : 'Processing...'}
<Loader inline size="mini" active />{' '}
{progress < 1 ? `Uploading ${(progress * 100).toFixed(0)}%` : 'Processing...'}
</span>
)
}
@ -99,9 +106,6 @@ type FileEntry = {
}
export default function UploadPage() {
const labelRef = React.useRef()
const [labelRefState, setLabelRefState] = React.useState()
const [files, setFiles] = React.useState<FileEntry[]>([])
const onCompleteFileUpload = React.useCallback(
@ -111,14 +115,6 @@ export default function UploadPage() {
[setFiles]
)
React.useLayoutEffect(
() => {
setLabelRefState(labelRef.current)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[labelRef.current]
)
function onSelectFiles(fileList) {
const newFiles = Array.from(fileList).map((file) => ({
id: 'file-' + String(Math.floor(Math.random() * 1000000)),
@ -129,18 +125,6 @@ export default function UploadPage() {
setFiles(files.filter((a) => !newFiles.some((b) => isSameFile(a, b))).concat(newFiles))
}
function onChangeField(e) {
if (e.target.files && e.target.files.length) {
onSelectFiles(e.target.files)
}
e.target.value = '' // reset the form field for uploading again
}
async function onDeleteTrack(slug: string) {
await api.delete(`/tracks/${slug}`)
setFiles((files) => files.filter((t) => t.result?.track?.slug !== slug))
}
return (
<Page>
{files.length ? (
@ -192,39 +176,7 @@ export default function UploadPage() {
</Table>
) : null}
<input
type="file"
id="upload-field"
style={{width: 0, height: 0, position: 'fixed', left: -1000, top: -1000, opacity: 0.001}}
multiple
accept=".csv"
onChange={onChangeField}
/>
<label htmlFor="upload-field" ref={labelRef}>
{labelRefState && (
<FileDrop onDrop={onSelectFiles} frame={labelRefState}>
{({draggingOverFrame, draggingOverTarget, onDragOver, onDragLeave, onDrop, onClick}) => (
<Segment
placeholder
{...{onDragOver, onDragLeave, onDrop}}
style={{
background: draggingOverTarget || draggingOverFrame ? '#E0E0EE' : null,
transition: 'background 0.2s',
}}
>
<Header icon>
<Icon name="cloud upload" />
Drop files here or click to select them for upload
</Header>
<Button primary as="span">
Upload files
</Button>
</Segment>
)}
</FileDrop>
)}
</label>
<FileUploadField onSelect={onSelectFiles} multiple />
</Page>
)
}