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