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 (
+ <>
+
+
+ {labelRefState && (
+
+ {({draggingOverFrame, draggingOverTarget, onDragOver, onDragLeave, onDrop, onClick}) => (
+
+
+
+ Drop file{multiple ? 's' : ''} here or click to select {multiple ? 'them' : 'one'} for upload
+
+
+
+ Upload file{multiple ? 's' : ''}
+
+
+ )}
+
+ )}
+
+ >
+ )
+}
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'}
+
+
+
+
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(true)}>Delete
+ setConfirmDelete(true)}>
+ Delete
+
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}
-
-
- {labelRefState && (
-
- {({draggingOverFrame, draggingOverTarget, onDragOver, onDragLeave, onDrop, onClick}) => (
-
-
-
- Drop files here or click to select them for upload
-
-
-
- Upload files
-
-
- )}
-
- )}
-
+
)
}