diff --git a/app/actions/race.js b/app/actions/race.js
index 513ea98..f54808f 100644
--- a/app/actions/race.js
+++ b/app/actions/race.js
@@ -62,24 +62,27 @@ export function restartRace(id) {
};
}
-export function endOngoingRace(race) {
+export function endOngoingRace(race, bikeTicks) {
return {
type: END_ONGOING_RACE,
- race
+ race,
+ bikeTicks
};
}
-export function finishRace(race) {
+export function finishRace(race, bikeTicks) {
return {
type: FINISH_ONGOING_RACE,
- race
+ race,
+ bikeTicks
};
}
-export function finishRacer(bikeIndex) {
+export function finishRacer(bikeIndex, bikeTicks) {
return {
type: FINISH_RACER,
bikeIndex,
+ bikeTicks,
timestamp: moment()
};
}
diff --git a/app/components/race-results/RacerDisplay.js b/app/components/race-results/RacerDisplay.js
index b3b48a4..dee9b94 100644
--- a/app/components/race-results/RacerDisplay.js
+++ b/app/components/race-results/RacerDisplay.js
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styles from './RacerDisplay.css';
import OrdinalNumber from './OrdinalNumber';
+import { getDistance, raceDuration, getSpeed } from '../../selectors';
const placementClass = (place) => {
switch (place) {
@@ -34,7 +35,17 @@ export default class RacerDisplay extends Component {
};
render() {
- const { bikeIndex, bike, racer = {}, race, className } = this.props;
+ const {
+ bikeIndex,
+ bike,
+ racer = {},
+ race,
+ className
+ } = this.props;
+ const speed = getSpeed(
+ getDistance(race, bike, race.results[bikeIndex].bikeTicks),
+ raceDuration(race, bikeIndex)
+ );
const place = styles[placementClass(race.results[bikeIndex].place)];
return (
@@ -47,6 +58,16 @@ export default class RacerDisplay extends Component {
+
+ {race.measurementSystem === 'metric' ?
+ `${speed.toFixed(1)} KPH` : `${speed.toFixed(1)} MPH`}
+
);
diff --git a/app/components/race-results/index.js b/app/components/race-results/index.js
index 7bde805..feab30d 100644
--- a/app/components/race-results/index.js
+++ b/app/components/race-results/index.js
@@ -36,6 +36,7 @@ export default class RaceResults extends Component {
{Object.keys(race.bikeRacerMap).map((_, i) => (
{
- if (!this.props.race.startTime) {
- this.falseStart(x);
- return;
- }
- const tick2complete = this.state.ticksToCompleteByBike[x];
- if (tick2complete < this.state.bikeTicks[x] + 1) { return; }
- const nextBikeTicks = [...this.state.bikeTicks];
- const next = nextBikeTicks[x] += 1;
- this.setState({ bikeTicks: nextBikeTicks });
- if (tick2complete === next) { this.props.finishRacer(x); }
- });
+ // eslint-disable-next-line react/no-did-mount-set-state
+ this.setState({
+ subscriber: global.j5$
+ .subscribe((x) => {
+ if (!this.props.race.startTime) {
+ this.falseStart(x);
+ return;
+ }
+ const tick2complete = this.state.ticksToCompleteByBike[x];
+ if (tick2complete < this.state.bikeTicks[x] + 1) {
+ return;
+ }
+ const nextBikeTicks = [...this.state.bikeTicks];
+ const next = nextBikeTicks[x] += 1;
+ this.setState({ bikeTicks: nextBikeTicks });
+ if (tick2complete === next) {
+ this.props.finishRacer(x, next);
+ }
+ })
+ });
}
componentWillUpdate(nextP) {
// is the race complete?
if (every(nextP.race.results, x => has(x, 'place'))) {
- nextP.finishRace(nextP.race);
+ nextP.finishRace(nextP.race, this.state.bikeTicks);
}
}
componentWillUnmount() {
if (this.interval) { clearInterval(this.interval); }
+ this.state.subscriber.unsubscribe();
}
falseStart(bikeIndex) {
@@ -152,7 +160,7 @@ export default class Race extends Component {
diff --git a/app/containers/RacePage.js b/app/containers/RacePage.js
index 61c7251..a4436cf 100644
--- a/app/containers/RacePage.js
+++ b/app/containers/RacePage.js
@@ -23,12 +23,12 @@ function mapDispatchToProps(dispatch) {
...RaceActions,
goBack
}, dispatch),
- finishRace: (race) => {
- dispatch(RaceActions.finishRace(race));
+ finishRace: (race, bikeTicks) => {
+ dispatch(RaceActions.finishRace(race, bikeTicks));
dispatch(push(`/race-results/${race.id}`));
},
- callRace: (race) => {
- dispatch(RaceActions.endOngoingRace(race));
+ callRace: (race, bikeTicks) => {
+ dispatch(RaceActions.endOngoingRace(race, bikeTicks));
dispatch(push(`/race-results/${race.id}`));
}
};
diff --git a/app/reducers/races.js b/app/reducers/races.js
index b977d86..72aba50 100644
--- a/app/reducers/races.js
+++ b/app/reducers/races.js
@@ -126,13 +126,16 @@ export default function races(state = [], action) {
return race;
}
const nextRace = { ...race };
- nextRace.results[action.bikeIndex] = getPlace(nextRace);
-
+ nextRace.results[action.bikeIndex] = {
+ ...getPlace(nextRace),
+ bikeTicks: action.bikeTicks
+ };
return nextRace;
}
return race;
});
case FINISH_ONGOING_RACE:
+ // todo add bike ticks and placement for time trial race
return state.map((race) => {
if (race.current) {
return {
@@ -148,9 +151,10 @@ export default function races(state = [], action) {
return state.map((race) => {
if (race.id === action.race.id) {
const results = [...action.race.results];
+ const now = moment();
results.forEach((_, i) => {
if (!action.race.results[i]) {
- results[i] = { place: -1 };
+ results[i] = { place: -1, bikeTicks: action.bikeTicks[i], finishTime: now };
}
});
return {
diff --git a/app/selectors/index.js b/app/selectors/index.js
index dcd49eb..3bd8f8f 100644
--- a/app/selectors/index.js
+++ b/app/selectors/index.js
@@ -67,12 +67,14 @@ export const getTicksToComplete = (race, bike) =>
Math.ceil(race.raceDistance /
(race.measurementSystem === 'metric' ? 1000 : 5280) / getCircumference(race, bike));
+export const raceDuration = (race, bikeIndex, now = moment()) => moment.duration(
+ (race.results[bikeIndex] ? race.results[bikeIndex].finishTime : now)
+ .diff(race.startTime, 'milliseconds')
+);
+
export const getRaceDuration = createSelector(
[getRace, getBikeIndex, () => moment()],
- (race, bikeIndex, now) => moment.duration(
- (race.results[bikeIndex] ? race.results[bikeIndex].finishTime : now)
- .diff(race.startTime, 'milliseconds')
- )
+ raceDuration
);
// milliseconds in an hour multiplied by the milliseconds in race