Reorder Trackpage

This commit is contained in:
Paul Bienkowski 2022-07-24 19:32:48 +02:00
parent fe7d7ce274
commit ab6cc6f6d0
4 changed files with 297 additions and 204 deletions

View file

@ -22,7 +22,7 @@ function CommentForm({onSubmit}) {
export default function TrackComments({comments, onSubmit, onDelete, login, hideLoader}) {
return (
<Segment basic>
<>
<Comment.Group>
<Header as="h2" dividing>
Comments
@ -63,6 +63,6 @@ export default function TrackComments({comments, onSubmit, onDelete, login, hide
{login && comments != null && <CommentForm onSubmit={onSubmit} />}
</Comment.Group>
</Segment>
</>
)
}

View file

@ -10,7 +10,7 @@ function formatDuration(seconds) {
export default function TrackDetails({track, isAuthor}) {
return (
<List>
<List horizontal relaxed>
{track.public != null && isAuthor && (
<List.Item>
<List.Header>Visibility</List.Header>

View file

@ -12,7 +12,6 @@
top: 16px;
right: 16px;
max-height: calc(100% - 32px);
overflow: auto;
}
}

View file

@ -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 (
<>
<Header as="h4">Map settings</Header>
<List>
<List.Item>
<Checkbox checked={showTrack} onChange={(e, d) => setShowTrack(d.checked)} /> Show track
<div style={{marginTop: 8}}>
<span style={{borderTop: '3px dashed ' + trackLayerRaw.paint['line-color'], height: 0, width: 24, display: 'inline-block', verticalAlign: 'middle', marginRight: 4}} />
<Checkbox
checked={showTrack}
onChange={(e, d) => setShowTrack(d.checked)}
/>{" "}
Show track
<div style={{ marginTop: 8 }}>
<span
style={{
borderTop: "3px dashed " + trackLayerRaw.paint["line-color"],
height: 0,
width: 24,
display: "inline-block",
verticalAlign: "middle",
marginRight: 4,
}}
/>
GPS track
</div>
<div>
<span style={{borderTop: '6px solid ' + trackLayerRaw.paint['line-color'], height: 6, width: 24, display: 'inline-block', verticalAlign: 'middle', marginRight: 4}} />
<span
style={{
borderTop: "6px solid " + trackLayerRaw.paint["line-color"],
height: 6,
width: 24,
display: "inline-block",
verticalAlign: "middle",
marginRight: 4,
}}
/>
Snapped to road
</div>
</List.Item>
@ -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",
},
]}
/>
</List.Item>
@ -61,29 +117,38 @@ 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)",
},
]}
/>
</List.Item>
</List>
</>
)
);
}
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[]
track: null | Track;
trackData: null | TrackData;
comments: null | TrackComment[];
} | null = useObservable(
(_$, args$) => {
const slug$ = args$.pipe(pluck(0), distinctUntilChanged())
const slug$ = args$.pipe(pluck(0), distinctUntilChanged());
const track$ = slug$.pipe(
map((slug) => `/tracks/${slug}`),
switchMap((url) =>
@ -91,13 +156,13 @@ const TrackPage = connect((state) => ({login: state.login}))(function TrackPage(
of(null),
from(api.get(url)).pipe(
catchError(() => {
history.replace('/tracks')
history.replace("/tracks");
})
)
)
),
pluck('track')
)
pluck("track")
);
const trackData$ = slug$.pipe(
map((slug) => `/tracks/${slug}/data`),
@ -106,13 +171,13 @@ const TrackPage = connect((state) => ({login: state.login}))(function TrackPage(
of(undefined),
from(api.get(url)).pipe(
catchError(() => {
return of(null)
return of(null);
})
)
)
),
startWith(undefined) // show track infos before track data is loaded
)
);
const comments$ = concat(of(null), reloadComments$).pipe(
switchMap(() => slug$),
@ -120,109 +185,162 @@ const TrackPage = connect((state) => ({login: state.login}))(function TrackPage(
switchMap((url) =>
from(api.get(url)).pipe(
catchError(() => {
return of(null)
return of(null);
})
)
),
pluck('comments'),
pluck("comments"),
startWith(null) // show track infos before comments are loaded
)
);
return combineLatest([track$, trackData$, comments$]).pipe(
map(([track, trackData, comments]) => ({track, trackData, comments}))
)
map(([track, trackData, comments]) => ({
track,
trackData,
comments,
}))
);
},
null,
[slug]
)
);
const onSubmitComment = React.useCallback(
async ({body}) => {
async ({ body }) => {
await api.post(`/tracks/${slug}/comments`, {
body: {comment: {body}},
})
reloadComments()
body: { comment: { body } },
});
reloadComments();
},
[slug, reloadComments]
)
);
const onDeleteComment = React.useCallback(
async (id) => {
await api.delete(`/tracks/${slug}/comments/${id}`)
reloadComments()
await api.delete(`/tracks/${slug}/comments/${id}`);
reloadComments();
},
[slug, reloadComments]
)
);
const [downloadError, setDownloadError] = React.useState(null)
const hideDownloadError = React.useCallback(() => setDownloadError(null), [setDownloadError])
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}`)
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.'
)
"The track probably has not been imported correctly or recently enough. Please ask your administrator for assistance."
);
} else {
setDownloadError(String(err))
setDownloadError(String(err));
}
}
},
[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
const title = track ? track.title || "Unnamed track" : null;
return (
<Page
title={title}
stage={
<>
<Container>
{track && (
<Segment basic>
<div style={{display: 'flex', alignItems: 'baseline', marginBlockStart: 32, marginBlockEnd: 16}}>
<Header as="h1">{title}</Header>
<div style={{marginLeft: 'auto'}}>
<TrackActions {...{ isAuthor, onDownload, slug }} />
</div>
</div>
<div style={{marginBlockEnd: 16}}>
<TrackDetails {...{ track, isAuthor }} />
</div>
</Segment>
)}
</Container>
<div className={styles.stage}>
<Loader active={loading} />
<Dimmer.Dimmable blurring dimmed={loading}>
<TrackMap {...{track, trackData, pointsMode, side, showTrack}} style={{height: '80vh'}} />
<TrackMap
{...{ track, trackData, pointsMode, side, showTrack }}
style={{ height: "80vh" }}
/>
</Dimmer.Dimmable>
<div className={styles.details}>
<Segment>
<TrackMapSettings
{...{
showTrack,
setShowTrack,
pointsMode,
setPointsMode,
side,
setSide,
}}
/>
</Segment>
{processing && (
<Message warning>
<Message.Content>Track data is still being processed, please reload page in a while.</Message.Content>
<Message.Content>
Track data is still being processed, please reload page in
a while.
</Message.Content>
</Message>
)}
{error && (
<Message error>
<Message.Content>
The processing of this track failed, please ask your site administrator for help in debugging the
issue.
The processing of this track failed, please ask your site
administrator for help in debugging the issue.
</Message.Content>
</Message>
)}
</div>
</div>
<Segment>
{track && (
<Container>
{track?.description && (
<>
<Header as="h1">{title}</Header>
<TrackDetails {...{track, isAuthor}} />
<TrackActions {...{isAuthor, onDownload, slug}} />
<Header as="h2" dividing>
Description
</Header>
<Markdown>{track.description}</Markdown>
</>
)}
</Segment>
</div>
</div>
<TrackComments
{...{ hideLoader: loading, comments, login }}
onSubmit={onSubmitComment}
onDelete={onDeleteComment}
/>
</Container>
</>
}
>
<Confirm
@ -232,33 +350,9 @@ const TrackPage = connect((state) => ({login: state.login}))(function TrackPage(
header="Download failed"
content={String(downloadError)}
/>
<Grid stackable>
<Grid.Row>
<Grid.Column width={12}>
{track?.description && (
<Segment basic>
<Header as="h2" dividing>
Description
</Header>
<Markdown>{track.description}</Markdown>
</Segment>
)}
<TrackComments
{...{hideLoader: loading, comments, login}}
onSubmit={onSubmitComment}
onDelete={onDeleteComment}
/>
</Grid.Column>
<Grid.Column width={4}>
<TrackMapSettings {...{showTrack, setShowTrack, pointsMode, setPointsMode, side, setSide}} />
</Grid.Column>
</Grid.Row>
</Grid>
{/* <pre>{JSON.stringify(data, null, 2)}</pre> */}
</Page>
)
})
);
}
);
export default TrackPage
export default TrackPage;