Translate UploadPage

This commit is contained in:
Paul Bienkowski 2022-07-24 18:07:47 +02:00
parent a85379418e
commit 2cff606092
4 changed files with 183 additions and 90 deletions

View file

@ -1,29 +1,31 @@
import React from 'react'
import {Icon, Segment, Header, Button} from 'semantic-ui-react'
import React from "react";
import { Icon, Segment, Header, Button } from "semantic-ui-react";
import { useTranslation } from "react-i18next";
import {FileDrop} from 'components'
import { FileDrop } from "components";
export default function FileUploadField({onSelect: onSelect_, multiple}) {
const labelRef = React.useRef()
const [labelRefState, setLabelRefState] = React.useState()
export default function FileUploadField({ onSelect: onSelect_, multiple }) {
const { t } = useTranslation();
const labelRef = React.useRef();
const [labelRefState, setLabelRefState] = React.useState();
const onSelect = multiple ? onSelect_ : (files) => onSelect_(files?.[0])
const onSelect = multiple ? onSelect_ : (files) => onSelect_(files?.[0]);
React.useLayoutEffect(
() => {
setLabelRefState(labelRef.current)
setLabelRefState(labelRef.current);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[labelRef.current]
)
);
function onChangeField(e) {
e.preventDefault?.()
e.preventDefault?.();
if (e.target.files && e.target.files.length) {
onSelect(e.target.files)
onSelect(e.target.files);
}
e.target.value = '' // reset the form field for uploading again
e.target.value = ""; // reset the form field for uploading again
}
return (
@ -31,7 +33,14 @@ export default function FileUploadField({onSelect: onSelect_, multiple}) {
<input
type="file"
id="upload-field"
style={{width: 0, height: 0, position: 'fixed', left: -1000, top: -1000, opacity: 0.001}}
style={{
width: 0,
height: 0,
position: "fixed",
left: -1000,
top: -1000,
opacity: 0.001,
}}
multiple={multiple}
accept=".csv"
onChange={onChangeField}
@ -39,28 +48,40 @@ export default function FileUploadField({onSelect: onSelect_, multiple}) {
<label htmlFor="upload-field" ref={labelRef}>
{labelRefState && (
<FileDrop onDrop={onSelect} frame={labelRefState}>
{({draggingOverFrame, draggingOverTarget, onDragOver, onDragLeave, onDrop, onClick}) => (
{({
draggingOverFrame,
draggingOverTarget,
onDragOver,
onDragLeave,
onDrop,
onClick,
}) => (
<Segment
placeholder
{...{onDragOver, onDragLeave, onDrop}}
{...{ onDragOver, onDragLeave, onDrop }}
style={{
background: draggingOverTarget || draggingOverFrame ? '#E0E0EE' : null,
transition: 'background 0.2s',
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
{multiple
? t("FileUploadField.dropOrClickMultiple")
: t("FileUploadField.dropOrClick")}
</Header>
<Button primary as="span">
Upload file{multiple ? 's' : ''}
{multiple
? t("FileUploadField.uploadFiles")
: t("FileUploadField.uploadFile")}
</Button>
</Segment>
)}
</FileDrop>
)}
</label>
</>
)
</>
);
}

View file

@ -1,45 +1,46 @@
import _ from 'lodash'
import React from 'react'
import {List, Loader, Table, Icon} from 'semantic-ui-react'
import {Link} from 'react-router-dom'
import _ from "lodash";
import React from "react";
import { List, Loader, Table, Icon } from "semantic-ui-react";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import {FileUploadField, Page} from 'components'
import type {Track} from 'types'
import api from 'api'
import configPromise from 'config'
import { FileUploadField, Page } from "components";
import type { Track } from "types";
import api from "api";
import configPromise from "config";
function isSameFile(a: File, b: File) {
return a.name === b.name && a.size === b.size
return a.name === b.name && a.size === b.size;
}
function formatFileSize(bytes: number) {
if (bytes < 1024) {
return `${bytes} bytes`
return `${bytes} bytes`;
}
bytes /= 1024
bytes /= 1024;
if (bytes < 1024) {
return `${bytes.toFixed(1)} KiB`
return `${bytes.toFixed(1)} KiB`;
}
bytes /= 1024
bytes /= 1024;
if (bytes < 1024) {
return `${bytes.toFixed(1)} MiB`
return `${bytes.toFixed(1)} MiB`;
}
bytes /= 1024
return `${bytes.toFixed(1)} GiB`
bytes /= 1024;
return `${bytes.toFixed(1)} GiB`;
}
type FileUploadResult =
| {
track: Track
track: Track;
}
| {
errors: Record<string, string>
}
errors: Record<string, string>;
};
export function FileUploadStatus({
id,
@ -47,108 +48,127 @@ export function FileUploadStatus({
onComplete,
slug,
}: {
id: string
file: File
onComplete: (id: string, result: FileUploadResult) => void
slug?: string
id: string;
file: File;
onComplete: (id: string, result: FileUploadResult) => void;
slug?: string;
}) {
const [progress, setProgress] = React.useState(0)
const [progress, setProgress] = React.useState(0);
React.useEffect(
() => {
let xhr
let xhr;
async function _work() {
const formData = new FormData()
formData.append('body', file)
const formData = new FormData();
formData.append("body", file);
xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr = new XMLHttpRequest();
xhr.withCredentials = true;
const onProgress = (e) => {
const progress = (e.loaded || 0) / (e.total || 1)
setProgress(progress)
}
const progress = (e.loaded || 0) / (e.total || 1);
setProgress(progress);
};
const onLoad = (e) => {
onComplete(id, xhr.response)
}
onComplete(id, xhr.response);
};
xhr.responseType = 'json'
xhr.onload = onLoad
xhr.upload.onprogress = onProgress
xhr.responseType = "json";
xhr.onload = onLoad;
xhr.upload.onprogress = onProgress;
const config = await configPromise
const config = await configPromise;
if (slug) {
xhr.open('PUT', `${config.apiUrl}/tracks/${slug}`)
xhr.open("PUT", `${config.apiUrl}/tracks/${slug}`);
} else {
xhr.open('POST', `${config.apiUrl}/tracks`)
xhr.open("POST", `${config.apiUrl}/tracks`);
}
// const accessToken = await api.getValidAccessToken()
// xhr.setRequestHeader('Authorization', accessToken)
xhr.send(formData)
xhr.send(formData);
}
_work()
return () => xhr.abort()
_work();
return () => xhr.abort();
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[file]
)
);
const { t } = useTranslation();
return (
<span>
<Loader inline size="mini" active />{' '}
{progress < 1 ? `Uploading ${(progress * 100).toFixed(0)}%` : 'Processing...'}
<Loader inline size="mini" active />{" "}
{progress < 1
? t("UploadPage.uploadProgress", {
progress: (progress * 100).toFixed(0),
})
: t("UploadPage.processing")}
</span>
)
);
}
type FileEntry = {
id: string
file?: File | null
size: number
name: string
result?: FileUploadResult
}
id: string;
file?: File | null;
size: number;
name: string;
result?: FileUploadResult;
};
export default function UploadPage() {
const [files, setFiles] = React.useState<FileEntry[]>([])
const [files, setFiles] = React.useState<FileEntry[]>([]);
const onCompleteFileUpload = React.useCallback(
(id, result) => {
setFiles((files) => files.map((file) => (file.id === id ? {...file, result, file: null} : file)))
setFiles((files) =>
files.map((file) =>
file.id === id ? { ...file, result, file: null } : file
)
);
},
[setFiles]
)
);
function onSelectFiles(fileList) {
const newFiles = Array.from(fileList).map((file) => ({
id: 'file-' + String(Math.floor(Math.random() * 1000000)),
id: "file-" + String(Math.floor(Math.random() * 1000000)),
file,
name: file.name,
size: file.size,
}))
setFiles(files.filter((a) => !newFiles.some((b) => isSameFile(a, b))).concat(newFiles))
}));
setFiles(
files
.filter((a) => !newFiles.some((b) => isSameFile(a, b)))
.concat(newFiles)
);
}
const { t } = useTranslation();
return (
<Page title="Upload">
{files.length ? (
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Filename</Table.HeaderCell>
<Table.HeaderCell>Size</Table.HeaderCell>
<Table.HeaderCell>Status / Title</Table.HeaderCell>
<Table.HeaderCell>
{t("UploadPage.table.filename")}
</Table.HeaderCell>
<Table.HeaderCell>{t("UploadPage.table.size")}</Table.HeaderCell>
<Table.HeaderCell>
{t("UploadPage.table.statusTitle")}
</Table.HeaderCell>
<Table.HeaderCell colSpan={2}></Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{files.map(({id, name, size, file, result}) => (
{files.map(({ id, name, size, file, result }) => (
<Table.Row key={id}>
<Table.Cell>
<Icon name="file" />
@ -159,7 +179,9 @@ export default function UploadPage() {
{result?.errors ? (
<List>
{_.sortBy(Object.entries(result.errors))
.filter(([field, message]) => typeof message === 'string')
.filter(
([field, message]) => typeof message === "string"
)
.map(([field, message]) => (
<List.Item key={field}>
<List.Icon name="warning sign" color="red" />
@ -169,15 +191,29 @@ export default function UploadPage() {
</List>
) : result ? (
<>
<Icon name="check" /> {result.track?.title || 'Unnamed track'}
<Icon name="check" />{" "}
{result.track?.title || t("general.unnamedTrack")}
</>
) : (
<FileUploadStatus {...{id, file}} onComplete={onCompleteFileUpload} />
<FileUploadStatus
{...{ id, file }}
onComplete={onCompleteFileUpload}
/>
)}
</Table.Cell>
<Table.Cell>{result?.track ? <Link to={`/tracks/${result.track.slug}`}>Show</Link> : null}</Table.Cell>
<Table.Cell>
{result?.track ? <Link to={`/tracks/${result.track.slug}/edit`}>Edit</Link> : null}
{result?.track ? (
<Link to={`/tracks/${result.track.slug}`}>
{t("general.show")}
</Link>
) : null}
</Table.Cell>
<Table.Cell>
{result?.track ? (
<Link to={`/tracks/${result.track.slug}/edit`}>
{t("general.edit")}
</Link>
) : null}
</Table.Cell>
</Table.Row>
))}
@ -187,5 +223,5 @@ export default function UploadPage() {
<FileUploadField onSelect={onSelectFiles} multiple />
</Page>
)
);
}

View file

@ -3,6 +3,8 @@ general:
unnamedTrack: Unbenannte Fahrt
public: Öffentlich
private: Privat
show: Anzeigen
edit: Bearbeiten
App:
footer:
@ -87,3 +89,19 @@ ExportPage:
shapefile: Shapefile (ZIP)
boundingBox:
label: Geografischer Bereich
UploadPage:
uploadProgress: Lade hoch {progress}%
processing: Verarbeiten...
table:
filename: Dateiname
size: Größe
statusTitle: Status / Titel
FileUploadField:
dropOrClick: Datei hierher ziehen oder klicken, um eine zum Hochladen auszuwählen
dropOrClickMultiple: Dateien hierher ziehen oder klicken, um Dateien zum Hochladen auszuwählen
uploadFile: Datei hochladen
uploadFiles: Dateien hochladen

View file

@ -7,6 +7,8 @@ general:
unnamedTrack: Unnamed track
public: Public
private: Private
show: Show
edit: Edit
App:
footer:
@ -92,3 +94,19 @@ ExportPage:
shapefile: Shapefile (ZIP)
boundingBox:
label: Bounding Box
UploadPage:
uploadProgress: Uploading {progress}%
processing: Processing...
table:
filename: Filename
size: Size
statusTitle: Status / Title
FileUploadField:
dropOrClick: Drop file here or click to select one for upload
dropOrClickMultiple: Drop files here or click to select them for upload
uploadFile: Upload file
uploadFiles: Upload files