Translate TrackEditor
This commit is contained in:
parent
6f7c8d54f2
commit
f158414f24
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import {connect} from 'react-redux'
|
||||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Divider,
|
||||
Message,
|
||||
|
@ -14,189 +14,201 @@ import {
|
|||
TextArea,
|
||||
Checkbox,
|
||||
Header,
|
||||
} 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'
|
||||
import {findInput} from 'utils'
|
||||
import {useForm, Controller} from 'react-hook-form'
|
||||
} 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";
|
||||
import { findInput } from "utils";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { useTranslation, Trans as Translate } from "react-i18next";
|
||||
import Markdown from "react-markdown";
|
||||
|
||||
import api from 'api'
|
||||
import {Page, FileUploadField} from 'components'
|
||||
import type {Track} from 'types'
|
||||
import api from "api";
|
||||
import { Page, FileUploadField } from "components";
|
||||
import type { Track } from "types";
|
||||
|
||||
import {FileUploadStatus} from 'pages/UploadPage'
|
||||
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])
|
||||
function ReplaceTrackData({ slug }) {
|
||||
const { t } = useTranslation();
|
||||
const [file, setFile] = React.useState(null);
|
||||
const [result, setResult] = React.useState(null);
|
||||
const onComplete = React.useCallback((_id, r) => setResult(r), [setResult]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header as="h2">Replace track data</Header>
|
||||
<Header as="h2">{t("TrackEditor.replaceTrackData")}</Header>
|
||||
{!file ? (
|
||||
<FileUploadField onSelect={setFile} />
|
||||
) : result ? (
|
||||
<Message>
|
||||
Upload complete. <Link to={`/tracks/${slug}`}>Show track</Link>
|
||||
<Translate i18nKey="TrackEditor.uploadComplete">
|
||||
Upload complete. <Link to={`/tracks/${slug}`}>Show track</Link>
|
||||
</Translate>
|
||||
</Message>
|
||||
) : (
|
||||
<FileUploadStatus {...{file, onComplete, slug}} />
|
||||
<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()
|
||||
const {slug} = useParams()
|
||||
const history = useHistory()
|
||||
const TrackEditor = connect((state) => ({ login: state.login }))(
|
||||
function TrackEditor({ login }) {
|
||||
const { t } = useTranslation();
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const { register, control, handleSubmit } = useForm();
|
||||
const { slug } = useParams();
|
||||
const history = useHistory();
|
||||
|
||||
const track: null | Track = useObservable(
|
||||
(_$, args$) => {
|
||||
const slug$ = args$.pipe(pluck(0), distinctUntilChanged())
|
||||
return slug$.pipe(
|
||||
map((slug) => `/tracks/${slug}`),
|
||||
switchMap((url) => concat(of(null), from(api.get(url)))),
|
||||
pluck('track')
|
||||
)
|
||||
},
|
||||
null,
|
||||
[slug]
|
||||
)
|
||||
const track: null | Track = useObservable(
|
||||
(_$, args$) => {
|
||||
const slug$ = args$.pipe(pluck(0), distinctUntilChanged());
|
||||
return slug$.pipe(
|
||||
map((slug) => `/tracks/${slug}`),
|
||||
switchMap((url) => concat(of(null), from(api.get(url)))),
|
||||
pluck("track")
|
||||
);
|
||||
},
|
||||
null,
|
||||
[slug]
|
||||
);
|
||||
|
||||
const loading = busy || track == null
|
||||
const isAuthor = login?.username === track?.author?.username
|
||||
const loading = busy || track == null;
|
||||
const isAuthor = login?.username === track?.author?.username;
|
||||
|
||||
// Navigate to track detials if we are not the author
|
||||
React.useEffect(() => {
|
||||
if (!login || (track && !isAuthor)) {
|
||||
history.replace(`/tracks/${slug}`)
|
||||
}
|
||||
}, [slug, login, track, isAuthor, history])
|
||||
// Navigate to track detials if we are not the author
|
||||
React.useEffect(() => {
|
||||
if (!login || (track && !isAuthor)) {
|
||||
history.replace(`/tracks/${slug}`);
|
||||
}
|
||||
}, [slug, login, track, isAuthor, history]);
|
||||
|
||||
const onSubmit = React.useMemo(
|
||||
() =>
|
||||
handleSubmit(async (values) => {
|
||||
setBusy(true)
|
||||
const onSubmit = React.useMemo(
|
||||
() =>
|
||||
handleSubmit(async (values) => {
|
||||
setBusy(true);
|
||||
|
||||
try {
|
||||
await api.put(`/tracks/${slug}`, {body: {track: _.pickBy(values, (v) => typeof v !== 'undefined')}})
|
||||
history.push(`/tracks/${slug}`)
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}),
|
||||
[slug, handleSubmit, history]
|
||||
)
|
||||
try {
|
||||
await api.put(`/tracks/${slug}`, {
|
||||
body: {
|
||||
track: _.pickBy(values, (v) => typeof v !== "undefined"),
|
||||
},
|
||||
});
|
||||
history.push(`/tracks/${slug}`);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}),
|
||||
[slug, handleSubmit, history]
|
||||
);
|
||||
|
||||
const [confirmDelete, setConfirmDelete] = React.useState(false)
|
||||
const onDelete = React.useCallback(async () => {
|
||||
setBusy(true)
|
||||
const [confirmDelete, setConfirmDelete] = React.useState(false);
|
||||
const onDelete = React.useCallback(async () => {
|
||||
setBusy(true);
|
||||
|
||||
try {
|
||||
await api.delete(`/tracks/${slug}`)
|
||||
history.push('/tracks')
|
||||
} finally {
|
||||
setConfirmDelete(false)
|
||||
setBusy(false)
|
||||
}
|
||||
}, [setBusy, setConfirmDelete, slug, history])
|
||||
try {
|
||||
await api.delete(`/tracks/${slug}`);
|
||||
history.push("/tracks");
|
||||
} finally {
|
||||
setConfirmDelete(false);
|
||||
setBusy(false);
|
||||
}
|
||||
}, [setBusy, setConfirmDelete, slug, history]);
|
||||
|
||||
const title = `Edit ${track ? track.title || 'Unnamed track' : 'track'}`
|
||||
return (
|
||||
<Page title={title}>
|
||||
<Grid centered relaxed divided stackable>
|
||||
<Grid.Row>
|
||||
<Grid.Column width={10}>
|
||||
<Header as="h2">{title}</Header>
|
||||
<Form loading={loading} key={track?.slug} onSubmit={onSubmit}>
|
||||
<Ref innerRef={findInput(register)}>
|
||||
<Form.Input label="Title" name="title" defaultValue={track?.title} style={{fontSize: '120%'}} />
|
||||
</Ref>
|
||||
const trackTitle: string = track?.title || t("general.unnamedTrack");
|
||||
const title = t("TrackEditor.title", { trackTitle });
|
||||
|
||||
<Form.Field>
|
||||
<label>Description</label>
|
||||
<Ref innerRef={register}>
|
||||
<TextArea name="description" rows={4} defaultValue={track?.description} />
|
||||
return (
|
||||
<Page title={title}>
|
||||
<Grid centered relaxed divided stackable>
|
||||
<Grid.Row>
|
||||
<Grid.Column width={10}>
|
||||
<Header as="h2">{title}</Header>
|
||||
<Form loading={loading} key={track?.slug} onSubmit={onSubmit}>
|
||||
<Ref innerRef={findInput(register)}>
|
||||
<Form.Input
|
||||
label="Title"
|
||||
name="title"
|
||||
defaultValue={track?.title}
|
||||
style={{ fontSize: "120%" }}
|
||||
/>
|
||||
</Ref>
|
||||
</Form.Field>
|
||||
|
||||
<Form.Field>
|
||||
<label>Track visibility</label>
|
||||
<Controller
|
||||
name="public"
|
||||
control={control}
|
||||
defaultValue={track?.public}
|
||||
render={(props) => (
|
||||
<Checkbox
|
||||
name="public"
|
||||
label="Make track public (in track list and details page, events always visible anonymously)"
|
||||
checked={props.value}
|
||||
onChange={(_, {checked}) => props.onChange(checked)}
|
||||
<Form.Field>
|
||||
<label>{t("TrackEditor.description.label")}</label>
|
||||
<Ref innerRef={register}>
|
||||
<TextArea
|
||||
name="description"
|
||||
rows={4}
|
||||
defaultValue={track?.description}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Ref>
|
||||
</Form.Field>
|
||||
|
||||
<Popup
|
||||
wide="very"
|
||||
content={
|
||||
<>
|
||||
<p>
|
||||
The overtaking events from all tracks are always public in an anonymized form.
|
||||
This option is about publishing the FULL TRACK (where you were cycling) for
|
||||
everyone to see together with your username.
|
||||
</p>
|
||||
<p>
|
||||
Checking this box allows all users to see your full track. For your own privacy and security,
|
||||
make sure to only publish tracks in this way that do not let others deduce where you live, work,
|
||||
or frequently stay. Your recording device might have useful privacy settings to not record
|
||||
geolocation data near those places.
|
||||
</p>
|
||||
<p>
|
||||
In the future, this site will allow you to redact privacy sensitive data in tracks, both
|
||||
manually and automatically. Until then, you will have to rely on the features of your recording
|
||||
device, or manually redact your files before upload.
|
||||
</p>
|
||||
<p>
|
||||
After checking this box, your data essentially becomes public. You understand that we cannot
|
||||
control who potentially downloads this data and and keeps a copy, even if you delete it from
|
||||
your account or anonymize it later.
|
||||
</p>
|
||||
<p>
|
||||
<b>Use at your own risk.</b>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
trigger={<Icon name="warning sign" style={{marginLeft: 8}} color="orange" />}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Button type="submit">Save</Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
<Grid.Column width={6}>
|
||||
<ReplaceTrackData slug={slug} />
|
||||
<Form.Field>
|
||||
<label>
|
||||
{t("TrackEditor.visibility.label")}
|
||||
<Popup
|
||||
wide="very"
|
||||
content={
|
||||
<Markdown>
|
||||
{t("TrackEditor.visibility.description")}
|
||||
</Markdown>
|
||||
}
|
||||
trigger={
|
||||
<Icon
|
||||
name="warning sign"
|
||||
style={{ marginLeft: 8 }}
|
||||
color="orange"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<Divider />
|
||||
<Controller
|
||||
name="public"
|
||||
control={control}
|
||||
defaultValue={track?.public}
|
||||
render={(props) => (
|
||||
<Checkbox
|
||||
name="public"
|
||||
label={t("TrackEditor.visibility.checkboxLabel")}
|
||||
checked={props.value}
|
||||
onChange={(_, { checked }) => props.onChange(checked)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Button type="submit">{t("general.save")}</Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
<Grid.Column width={6}>
|
||||
<ReplaceTrackData slug={slug} />
|
||||
|
||||
<Header as="h2">Danger zone</Header>
|
||||
<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>
|
||||
<Confirm open={confirmDelete} onCancel={() => setConfirmDelete(false)} onConfirm={onDelete} />
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
</Page>
|
||||
)
|
||||
})
|
||||
<Divider />
|
||||
|
||||
export default TrackEditor
|
||||
<Header as="h2">{t("TrackEditor.dangerZone.title")}</Header>
|
||||
<Markdown>{t("TrackEditor.dangerZone.description")}</Markdown>
|
||||
|
||||
<Button color="red" onClick={() => setConfirmDelete(true)}>
|
||||
{t("general.delete")}
|
||||
</Button>
|
||||
<Confirm
|
||||
open={confirmDelete}
|
||||
onCancel={() => setConfirmDelete(false)}
|
||||
onConfirm={onDelete}
|
||||
content={t("TrackEditor.dangerZone.confirmDelete")}
|
||||
confirmButton={t("general.delete")}
|
||||
cancelButton={t("general.cancel")}
|
||||
/>
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default TrackEditor;
|
||||
|
|
|
@ -355,6 +355,7 @@ const TrackPage = connect((state) => ({ login: state.login }))(
|
|||
onConfirm={hideDownloadError}
|
||||
header={t("TrackPage.downloadFailed")}
|
||||
content={String(downloadError)}
|
||||
confirmButton={t('general.ok')}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@ import _ from 'lodash'
|
|||
import {useTranslation, Trans as Translate} from 'react-i18next'
|
||||
|
||||
import type {Track} from 'types'
|
||||
import {Avatar, Page, StripMarkdown, FormattedDate} from 'components'
|
||||
import {Avatar, Page, StripMarkdown, FormattedDate, Visibility} from 'components'
|
||||
import api from 'api'
|
||||
import {useQueryParam} from 'query'
|
||||
|
||||
|
|
|
@ -9,6 +9,11 @@ general:
|
|||
edit: Bearbeiten
|
||||
save: Speichern
|
||||
delete: Löschen
|
||||
ok: Okay
|
||||
cancel: Abbrechen
|
||||
confirm: Bist du sicher?
|
||||
copied: Kopiert
|
||||
copyError: Kopieren fehlgeschlagen
|
||||
|
||||
App:
|
||||
footer:
|
||||
|
@ -269,3 +274,50 @@ TrackPage:
|
|||
title: Kommentare
|
||||
post: Kommentar abschicken
|
||||
empty: Bisher hat niemand diese Fahrt kommentiert.
|
||||
|
||||
TrackEditor:
|
||||
title: "{{trackTitle}} bearbeiten"
|
||||
replaceTrackData: Fahrtdaten ersetzen
|
||||
uploadComplete: Upload vollständig. <1>Fahrt anzeigen</1>
|
||||
|
||||
dangerZone:
|
||||
title: Gefahrenbereich
|
||||
description: |
|
||||
Du kannst diese Fahrt aus deinem Konto und damit von diesem Portal
|
||||
entfernen. Wir können jedoch nicht garantieren, dass die daraus
|
||||
extrahierten, anonymisierten und u. U. weiter veröffentlichten Daten
|
||||
ebenfalls entfernt werden, weder in dieser Plattform, noch in Kopien
|
||||
anderer Parteien.
|
||||
confirmDelete: Bist du sicher, dass du diese Fahrt löschen möchtest?
|
||||
|
||||
description:
|
||||
label: Beschreibung
|
||||
visibility:
|
||||
label: Sichtbarkeit der Fahrt
|
||||
checkboxLabel: Fahrt öffentlich machen (in der Fahrtenliste und Detailseite, anonyme Überholungen sind immer sichtbar)
|
||||
description: |
|
||||
Die extrahierten Überholungen aller Fahrten sind immer öffentlich in
|
||||
anonymisierter Form. Diese Option betrifft die Veröffentlichung der
|
||||
*gesamten* Fahrtdaten, also wann und wo du gefahren bist, zusammen mit
|
||||
deinem erkennbaren Kontonamen.
|
||||
|
||||
Wenn du diese Box aktivierst können alle deine ganze Fahrt ansehen. Um
|
||||
deine eigene Sicherheit und Privatsphäre zu schützen stelle nur solche
|
||||
Fahrten öffentlich, anhand derer niemand deinen Wohnort, Arbeitsplatz,
|
||||
oder regelmäßige Aufenthaltsorte erkennen kann. Dein Aufnahmegerät stellt
|
||||
eventuell nützliche Einstellungen zur Verfügung, um die GPS-Daten in der
|
||||
Nähe solcher Orte überhaupt nicht aufzuzeichnen.
|
||||
|
||||
In der Zukunft wird diese Seite auch Werkzeuge zur Redaktion deiner
|
||||
sensiblen Daten, sowohl manuell als auch automatisch, bereitstellen. Bis
|
||||
dahin musst du die Möglichkeiten in deinem Aufnahmegerät nutzen, oder von
|
||||
Hand vor dem Hochladen der Originaldateien Anpassungen vornehmen.
|
||||
|
||||
Deine Daten werden mit Aktivieren dieser Funktion veröffentlicht. Wir
|
||||
weisen dich darauf hin, dass die Betreiber:innen dieser Plattform dann
|
||||
keine Kontrolle über mögliche Kopien dieser Daten haben, die andere davon
|
||||
anfertigen. Selbst wenn du später deine Daten auf dieser Plattform
|
||||
löschst oder anonymisierst können Kopien des Originals bei Dritten
|
||||
vorhanden bleiben.
|
||||
|
||||
**Nutze diese Funktion mit Bedacht und auf dein eigenes Risiko.**
|
||||
|
|
|
@ -13,7 +13,9 @@ general:
|
|||
edit: Edit
|
||||
save: Save
|
||||
delete: Delete
|
||||
|
||||
ok: OK
|
||||
cancel: Cancel
|
||||
confirm: Are you sure?
|
||||
copied: Copied.
|
||||
copyError: Failed to copy.
|
||||
|
||||
|
@ -275,3 +277,45 @@ TrackPage:
|
|||
title: Comments
|
||||
post: Post comment
|
||||
empty: Nobody commented... yet
|
||||
|
||||
TrackEditor:
|
||||
title: Edit {{trackTitle}}
|
||||
replaceTrackData: Replace track data
|
||||
uploadComplete: Upload complete. <1>Show track</1>
|
||||
|
||||
dangerZone:
|
||||
title: Danger zone
|
||||
description: |
|
||||
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.
|
||||
confirmDelete: Are you sure you want to delete this track?
|
||||
|
||||
description:
|
||||
label: Description
|
||||
visibility:
|
||||
label: Track visibility
|
||||
checkboxLabel: Make track public (in track list and details page, events always visible anonymously)
|
||||
description: |
|
||||
The overtaking events from all tracks are always public in an anonymized
|
||||
form. This option is about publishing the *full* track (where you were
|
||||
cycling) for everyone to see together with your username.
|
||||
|
||||
Checking this box allows all users to see your full track. For your own
|
||||
privacy and security, make sure to only publish tracks in this way that
|
||||
do not let others deduce where you live, work, or frequently stay. Your
|
||||
recording device might have useful privacy settings to not record
|
||||
geolocation data near those places.
|
||||
|
||||
In the future, this site will allow you to redact privacy sensitive data
|
||||
in tracks, both manually and automatically. Until then, you will have to
|
||||
rely on the features of your recording device, or manually redact your
|
||||
files before upload.
|
||||
|
||||
After checking this box, your data essentially becomes public. You
|
||||
understand that we cannot control who potentially downloads this data and
|
||||
and keeps a copy, even if you delete it from your account or anonymize it
|
||||
later.
|
||||
|
||||
**Use at your own risk.**
|
||||
|
|
Loading…
Reference in a new issue