diff --git a/frontend/src/pages/TrackPage/TrackComments.tsx b/frontend/src/pages/TrackPage/TrackComments.tsx index 09b2b95..abbb93f 100644 --- a/frontend/src/pages/TrackPage/TrackComments.tsx +++ b/frontend/src/pages/TrackPage/TrackComments.tsx @@ -22,7 +22,7 @@ function CommentForm({onSubmit}) { export default function TrackComments({comments, onSubmit, onDelete, login, hideLoader}) { return ( - + <>
Comments @@ -63,6 +63,6 @@ export default function TrackComments({comments, onSubmit, onDelete, login, hide {login && comments != null && } - + ) } diff --git a/frontend/src/pages/TrackPage/TrackDetails.tsx b/frontend/src/pages/TrackPage/TrackDetails.tsx index bbaec4c..7285902 100644 --- a/frontend/src/pages/TrackPage/TrackDetails.tsx +++ b/frontend/src/pages/TrackPage/TrackDetails.tsx @@ -10,7 +10,7 @@ function formatDuration(seconds) { export default function TrackDetails({track, isAuthor}) { return ( - + {track.public != null && isAuthor && ( Visibility diff --git a/frontend/src/pages/TrackPage/TrackPage.module.less b/frontend/src/pages/TrackPage/TrackPage.module.less index 65716cd..fab699f 100644 --- a/frontend/src/pages/TrackPage/TrackPage.module.less +++ b/frontend/src/pages/TrackPage/TrackPage.module.less @@ -12,7 +12,6 @@ top: 16px; right: 16px; max-height: calc(100% - 32px); - overflow: auto; } } diff --git a/frontend/src/pages/TrackPage/index.tsx b/frontend/src/pages/TrackPage/index.tsx index 5004b8e..d411e89 100644 --- a/frontend/src/pages/TrackPage/index.tsx +++ b/frontend/src/pages/TrackPage/index.tsx @@ -1,43 +1,91 @@ -import React from 'react' -import {connect} from 'react-redux' -import {List, Dropdown, Checkbox, Segment, Dimmer, Grid, Loader, Header, Message, Confirm} from 'semantic-ui-react' -import {useParams, useHistory} from 'react-router-dom' -import {concat, combineLatest, of, from, Subject} from 'rxjs' -import {pluck, distinctUntilChanged, map, switchMap, startWith, catchError} from 'rxjs/operators' -import {useObservable} from 'rxjs-hooks' -import Markdown from 'react-markdown' +import React from "react"; +import { connect } from "react-redux"; +import { + List, + Dropdown, + Checkbox, + Segment, + Dimmer, + Grid, + Loader, + Header, + Message, + Confirm, + Container, +} from "semantic-ui-react"; +import { useParams, useHistory } from "react-router-dom"; +import { concat, combineLatest, of, from, Subject } from "rxjs"; +import { + pluck, + distinctUntilChanged, + map, + switchMap, + startWith, + catchError, +} from "rxjs/operators"; +import { useObservable } from "rxjs-hooks"; +import Markdown from "react-markdown"; -import api from 'api' -import {Page} from 'components' -import type {Track, TrackData, TrackComment} from 'types' -import {trackLayer, trackLayerRaw} from '../../mapstyles' +import api from "api"; +import { Page } from "components"; +import type { Track, TrackData, TrackComment } from "types"; +import { trackLayer, trackLayerRaw } from "../../mapstyles"; -import TrackActions from './TrackActions' -import TrackComments from './TrackComments' -import TrackDetails from './TrackDetails' -import TrackMap from './TrackMap' +import TrackActions from "./TrackActions"; +import TrackComments from "./TrackComments"; +import TrackDetails from "./TrackDetails"; +import TrackMap from "./TrackMap"; -import styles from './TrackPage.module.less' +import styles from "./TrackPage.module.less"; function useTriggerSubject() { - const subject$ = React.useMemo(() => new Subject(), []) - const trigger = React.useCallback(() => subject$.next(null), [subject$]) - return [trigger, subject$] + const subject$ = React.useMemo(() => new Subject(), []); + const trigger = React.useCallback(() => subject$.next(null), [subject$]); + return [trigger, subject$]; } -function TrackMapSettings({showTrack, setShowTrack, pointsMode, setPointsMode, side, setSide}) { +function TrackMapSettings({ + showTrack, + setShowTrack, + pointsMode, + setPointsMode, + side, + setSide, +}) { return ( <>
Map settings
- setShowTrack(d.checked)} /> Show track -
- + setShowTrack(d.checked)} + />{" "} + Show track +
+ GPS track
- + Snapped to road
@@ -48,9 +96,17 @@ function TrackMapSettings({showTrack, setShowTrack, pointsMode, setPointsMode, s value={pointsMode} onChange={(e, d) => setPointsMode(d.value)} options={[ - {key: 'none', value: 'none', text: 'None'}, - {key: 'overtakingEvents', value: 'overtakingEvents', text: 'Confirmed'}, - {key: 'measurements', value: 'measurements', text: 'All measurements'}, + { key: "none", value: "none", text: "None" }, + { + key: "overtakingEvents", + value: "overtakingEvents", + text: "Confirmed", + }, + { + key: "measurements", + value: "measurements", + text: "All measurements", + }, ]} /> @@ -61,204 +117,242 @@ function TrackMapSettings({showTrack, setShowTrack, pointsMode, setPointsMode, s value={side} onChange={(e, d) => setSide(d.value)} options={[ - {key: 'overtaker', value: 'overtaker', text: 'Overtaker (Left)'}, - {key: 'stationary', value: 'stationary', text: 'Stationary (Right)'}, + { + key: "overtaker", + value: "overtaker", + text: "Overtaker (Left)", + }, + { + key: "stationary", + value: "stationary", + text: "Stationary (Right)", + }, ]} /> - ) + ); } -const TrackPage = connect((state) => ({login: state.login}))(function TrackPage({login}) { - const {slug} = useParams() +const TrackPage = connect((state) => ({ login: state.login }))( + function TrackPage({ login }) { + const { slug } = useParams(); - const [reloadComments, reloadComments$] = useTriggerSubject() - const history = useHistory() + const [reloadComments, reloadComments$] = useTriggerSubject(); + const history = useHistory(); - const data: { - track: null | Track - trackData: null | TrackData - comments: null | TrackComment[] - } | null = useObservable( - (_$, args$) => { - const slug$ = args$.pipe(pluck(0), distinctUntilChanged()) - const track$ = slug$.pipe( - map((slug) => `/tracks/${slug}`), - switchMap((url) => - concat( - of(null), + const data: { + track: null | Track; + trackData: null | TrackData; + comments: null | TrackComment[]; + } | null = useObservable( + (_$, args$) => { + const slug$ = args$.pipe(pluck(0), distinctUntilChanged()); + const track$ = slug$.pipe( + map((slug) => `/tracks/${slug}`), + switchMap((url) => + concat( + of(null), + from(api.get(url)).pipe( + catchError(() => { + history.replace("/tracks"); + }) + ) + ) + ), + pluck("track") + ); + + const trackData$ = slug$.pipe( + map((slug) => `/tracks/${slug}/data`), + switchMap((url) => + concat( + of(undefined), + from(api.get(url)).pipe( + catchError(() => { + return of(null); + }) + ) + ) + ), + startWith(undefined) // show track infos before track data is loaded + ); + + const comments$ = concat(of(null), reloadComments$).pipe( + switchMap(() => slug$), + map((slug) => `/tracks/${slug}/comments`), + switchMap((url) => from(api.get(url)).pipe( catchError(() => { - history.replace('/tracks') + return of(null); }) ) - ) - ), - pluck('track') - ) + ), + pluck("comments"), + startWith(null) // show track infos before comments are loaded + ); - const trackData$ = slug$.pipe( - map((slug) => `/tracks/${slug}/data`), - switchMap((url) => - concat( - of(undefined), - from(api.get(url)).pipe( - catchError(() => { - return of(null) - }) - ) - ) - ), - startWith(undefined) // show track infos before track data is loaded - ) + return combineLatest([track$, trackData$, comments$]).pipe( + map(([track, trackData, comments]) => ({ + track, + trackData, + comments, + })) + ); + }, + null, + [slug] + ); - const comments$ = concat(of(null), reloadComments$).pipe( - switchMap(() => slug$), - map((slug) => `/tracks/${slug}/comments`), - switchMap((url) => - from(api.get(url)).pipe( - catchError(() => { - return of(null) - }) - ) - ), - pluck('comments'), - startWith(null) // show track infos before comments are loaded - ) + const onSubmitComment = React.useCallback( + async ({ body }) => { + await api.post(`/tracks/${slug}/comments`, { + body: { comment: { body } }, + }); + reloadComments(); + }, + [slug, reloadComments] + ); - return combineLatest([track$, trackData$, comments$]).pipe( - map(([track, trackData, comments]) => ({track, trackData, comments})) - ) - }, - null, - [slug] - ) + const onDeleteComment = React.useCallback( + async (id) => { + await api.delete(`/tracks/${slug}/comments/${id}`); + reloadComments(); + }, + [slug, reloadComments] + ); - const onSubmitComment = React.useCallback( - async ({body}) => { - await api.post(`/tracks/${slug}/comments`, { - body: {comment: {body}}, - }) - reloadComments() - }, - [slug, reloadComments] - ) - - const onDeleteComment = React.useCallback( - async (id) => { - await api.delete(`/tracks/${slug}/comments/${id}`) - reloadComments() - }, - [slug, reloadComments] - ) - - const [downloadError, setDownloadError] = React.useState(null) - const hideDownloadError = React.useCallback(() => setDownloadError(null), [setDownloadError]) - const onDownload = React.useCallback( - async (filename) => { - try { - await api.downloadFile(`/tracks/${slug}/download/${filename}`) - } catch (err) { - if (/Failed to fetch/.test(String(err))) { - setDownloadError( - 'The track probably has not been imported correctly or recently enough. Please ask your administrator for assistance.' - ) - } else { - setDownloadError(String(err)) + const [downloadError, setDownloadError] = React.useState(null); + const hideDownloadError = React.useCallback( + () => setDownloadError(null), + [setDownloadError] + ); + const onDownload = React.useCallback( + async (filename) => { + try { + await api.downloadFile(`/tracks/${slug}/download/${filename}`); + } catch (err) { + if (/Failed to fetch/.test(String(err))) { + setDownloadError( + "The track probably has not been imported correctly or recently enough. Please ask your administrator for assistance." + ); + } else { + setDownloadError(String(err)); + } } - } - }, - [slug] - ) + }, + [slug] + ); - const isAuthor = login?.username === data?.track?.author?.username + const isAuthor = login?.username === data?.track?.author?.username; - const {track, trackData, comments} = data || {} + const { track, trackData, comments } = data || {}; - const loading = track == null || trackData === undefined - const processing = ['processing', 'queued', 'created'].includes(track?.processingStatus) - const error = track?.processingStatus === 'error' + const loading = track == null || trackData === undefined; + const processing = ["processing", "queued", "created"].includes( + track?.processingStatus + ); + const error = track?.processingStatus === "error"; - const [showTrack, setShowTrack] = React.useState(true) - const [pointsMode, setPointsMode] = React.useState('overtakingEvents') // none|overtakingEvents|measurements - const [side, setSide] = React.useState('overtaker') // overtaker|stationary + const [showTrack, setShowTrack] = React.useState(true); + const [pointsMode, setPointsMode] = React.useState("overtakingEvents"); // none|overtakingEvents|measurements + const [side, setSide] = React.useState("overtaker"); // overtaker|stationary - const title = track ? track.title || 'Unnamed track' : null - return ( - - - - - - -
- {processing && ( - - Track data is still being processed, please reload page in a while. - - )} - - {error && ( - - - The processing of this track failed, please ask your site administrator for help in debugging the - issue. - - - )} - - + const title = track ? track.title || "Unnamed track" : null; + return ( + + {track && ( + +
+
{title}
+
+ +
+
+ +
+ +
+
+ )} +
+
+ + + + + +
+ + + + + {processing && ( + + + Track data is still being processed, please reload page in + a while. + + + )} + + {error && ( + + + The processing of this track failed, please ask your site + administrator for help in debugging the issue. + + + )} +
+
+ + + {track?.description && ( <> -
{title}
- - +
+ Description +
+ {track.description} )} -
-
-
- } - > - - - - - {track?.description && ( - -
- Description -
- {track.description} -
- )} - -
- - - -
-
+ + + + } + > + + + ); + } +); - {/*
{JSON.stringify(data, null, 2)}
*/} - - ) -}) - -export default TrackPage +export default TrackPage;