Translate TrackEditor

This commit is contained in:
Paul Bienkowski 2022-07-24 22:12:38 +02:00
parent 6f7c8d54f2
commit f158414f24
5 changed files with 268 additions and 159 deletions

View file

@ -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,99 +14,112 @@ 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>
<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())
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')
)
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}`)
history.replace(`/tracks/${slug}`);
}
}, [slug, login, track, isAuthor, history])
}, [slug, login, track, isAuthor, history]);
const onSubmit = React.useMemo(
() =>
handleSubmit(async (values) => {
setBusy(true)
setBusy(true);
try {
await api.put(`/tracks/${slug}`, {body: {track: _.pickBy(values, (v) => typeof v !== 'undefined')}})
history.push(`/tracks/${slug}`)
await api.put(`/tracks/${slug}`, {
body: {
track: _.pickBy(values, (v) => typeof v !== "undefined"),
},
});
history.push(`/tracks/${slug}`);
} finally {
setBusy(false)
setBusy(false);
}
}),
[slug, handleSubmit, history]
)
);
const [confirmDelete, setConfirmDelete] = React.useState(false)
const [confirmDelete, setConfirmDelete] = React.useState(false);
const onDelete = React.useCallback(async () => {
setBusy(true)
setBusy(true);
try {
await api.delete(`/tracks/${slug}`)
history.push('/tracks')
await api.delete(`/tracks/${slug}`);
history.push("/tracks");
} finally {
setConfirmDelete(false)
setBusy(false)
setConfirmDelete(false);
setBusy(false);
}
}, [setBusy, setConfirmDelete, slug, history])
}, [setBusy, setConfirmDelete, slug, history]);
const trackTitle: string = track?.title || t("general.unnamedTrack");
const title = t("TrackEditor.title", { trackTitle });
const title = `Edit ${track ? track.title || 'Unnamed track' : 'track'}`
return (
<Page title={title}>
<Grid centered relaxed divided stackable>
@ -115,18 +128,45 @@ const TrackEditor = connect((state) => ({login: state.login}))(function TrackEdi
<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%'}} />
<Form.Input
label="Title"
name="title"
defaultValue={track?.title}
style={{ fontSize: "120%" }}
/>
</Ref>
<Form.Field>
<label>Description</label>
<label>{t("TrackEditor.description.label")}</label>
<Ref innerRef={register}>
<TextArea name="description" rows={4} defaultValue={track?.description} />
<TextArea
name="description"
rows={4}
defaultValue={track?.description}
/>
</Ref>
</Form.Field>
<Form.Field>
<label>Track visibility</label>
<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>
<Controller
name="public"
control={control}
@ -134,47 +174,14 @@ const TrackEditor = connect((state) => ({login: state.login}))(function TrackEdi
render={(props) => (
<Checkbox
name="public"
label="Make track public (in track list and details page, events always visible anonymously)"
label={t("TrackEditor.visibility.checkboxLabel")}
checked={props.value}
onChange={(_, {checked}) => props.onChange(checked)}
onChange={(_, { checked }) => props.onChange(checked)}
/>
)}
/>
<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>
<Button type="submit">{t("general.save")}</Button>
</Form>
</Grid.Column>
<Grid.Column width={6}>
@ -182,21 +189,26 @@ const TrackEditor = connect((state) => ({login: state.login}))(function TrackEdi
<Divider />
<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>
<Header as="h2">{t("TrackEditor.dangerZone.title")}</Header>
<Markdown>{t("TrackEditor.dangerZone.description")}</Markdown>
<Button color="red" onClick={() => setConfirmDelete(true)}>
Delete
{t("general.delete")}
</Button>
<Confirm open={confirmDelete} onCancel={() => setConfirmDelete(false)} onConfirm={onDelete} />
<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
export default TrackEditor;

View file

@ -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>
);

View file

@ -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'

View file

@ -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.**

View file

@ -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.**