diff --git a/frontend/src/components/FileUploadField.tsx b/frontend/src/components/FileUploadField.tsx new file mode 100644 index 0000000..36c8f68 --- /dev/null +++ b/frontend/src/components/FileUploadField.tsx @@ -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 ( + <> + + + + ) +} diff --git a/frontend/src/components/index.js b/frontend/src/components/index.js index 533d682..638194f 100644 --- a/frontend/src/components/index.js +++ b/frontend/src/components/index.js @@ -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' diff --git a/frontend/src/pages/TrackEditor.tsx b/frontend/src/pages/TrackEditor.tsx index db4dbc9..62e0962 100644 --- a/frontend/src/pages/TrackEditor.tsx +++ b/frontend/src/pages/TrackEditor.tsx @@ -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 ( + <> +

Replace track data

+ {!file ? ( + + ) : result ? ( + + Upload complete. Show track + + ) : ( + + )} + + ) +} + 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 -

Edit {track ? (track.title || 'Unnamed track') : 'track'}

+

Edit {track ? track.title || 'Unnamed track' : 'track'}

@@ -136,13 +159,19 @@ const TrackEditor = connect((state) => ({login: state.login}))(function TrackEdi
+ + + +

Danger zone

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.

- + setConfirmDelete(false)} onConfirm={onDelete} />
diff --git a/frontend/src/pages/UploadPage.tsx b/frontend/src/pages/UploadPage.tsx index 77aab2e..762f665 100644 --- a/frontend/src/pages/UploadPage.tsx +++ b/frontend/src/pages/UploadPage.tsx @@ -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 } -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 ( - {progress < 1 ? `Uploading ${(progress * 100).toFixed(0)}%` : 'Processing...'} + {' '} + {progress < 1 ? `Uploading ${(progress * 100).toFixed(0)}%` : 'Processing...'} ) } @@ -99,9 +106,6 @@ type FileEntry = { } export default function UploadPage() { - const labelRef = React.useRef() - const [labelRefState, setLabelRefState] = React.useState() - const [files, setFiles] = React.useState([]) 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 ( {files.length ? ( @@ -192,39 +176,7 @@ export default function UploadPage() { ) : null} - - + ) }