adds cooldown, fixed stats bug and fixes middle layout
This commit is contained in:
Generated
+29
@@ -1288,6 +1288,16 @@
|
|||||||
"loader-utils": "^1.2.3"
|
"loader-utils": "^1.2.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@testing-library/react-hooks": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-1OB6Ksvlk6BCJA1xpj8/WWz0XVd1qRcgqdaFAq+xeC6l61Ucj0P6QpA5u+Db/x9gU4DCX8ziR5b66Mlfg0M2RA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.5.4",
|
||||||
|
"@types/testing-library__react-hooks": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/babel__core": {
|
"@types/babel__core": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz",
|
||||||
@@ -1378,11 +1388,30 @@
|
|||||||
"csstype": "^2.2.0"
|
"csstype": "^2.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-test-renderer": {
|
||||||
|
"version": "16.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.1.tgz",
|
||||||
|
"integrity": "sha512-nCXQokZN1jp+QkoDNmDZwoWpKY8HDczqevIDO4Uv9/s9rbGPbSpy8Uaxa5ixHKkcm/Wt0Y9C3wCxZivh4Al+rQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/stack-utils": {
|
"@types/stack-utils": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||||
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
|
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
|
||||||
},
|
},
|
||||||
|
"@types/testing-library__react-hooks": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-QJc1sgH9DD6jbfybzugnP0sY8wPzzIq8sHDBuThzCr2ZEbyHIaAvN9ytx/tHzcWL5MqmeZJqiUm/GsythaGx3g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-test-renderer": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/yargs": {
|
"@types/yargs": {
|
||||||
"version": "13.0.3",
|
"version": "13.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz",
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
"workbox-webpack-plugin": "4.3.1"
|
"workbox-webpack-plugin": "4.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@testing-library/react-hooks": "^3.2.1",
|
||||||
"@types/react": "^16.9.11"
|
"@types/react": "^16.9.11"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import React, {useContext, useEffect, useState} from 'react';
|
|||||||
|
|
||||||
import "./Bois.css";
|
import "./Bois.css";
|
||||||
import {StatusContext} from "./Contexts";
|
import {StatusContext} from "./Contexts";
|
||||||
import {CalorieScore, CpmScore, DistanceScore, RpmScore, Timer} from "./Score";
|
import {CalorieScore, CpmScore, LevelScore, RpmScore, Timer} from "./Score";
|
||||||
import calculateDiff from "../helpers/diff";
|
import calculateDiff from "../helpers/diff";
|
||||||
import useKey from "../hooks/useKey";
|
import useKey from "../hooks/useKey";
|
||||||
import {Milestones} from "./Milestones";
|
import {Milestones} from "./Milestones";
|
||||||
import {Info, InfoTable, StateFilter, Warning} from "./Misc";
|
import {Info, InfoTable, StateFilter, Differ} from "./Misc";
|
||||||
|
|
||||||
const Boi = ({type, children}) => {
|
const Boi = ({type, children}) => {
|
||||||
return (
|
return (
|
||||||
@@ -17,22 +17,23 @@ const Boi = ({type, children}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const LeftBoi = () => {
|
export const LeftBoi = () => {
|
||||||
const {prevLongDiff, workoutStatus, program, hidden} = useContext(StatusContext);
|
const {prevLongDiff, workout, workoutStatus, program, hidden} = useContext(StatusContext);
|
||||||
if (workoutStatus === null || program === null || hidden) {
|
if (workoutStatus === null || workout === null || program === null || hidden) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {minutes, seconds, calories, distance, rpm} = workoutStatus;
|
const {cooldownMin} = workout;
|
||||||
const diff = calculateDiff(program, minutes, seconds, calories);
|
const {minutes, seconds, calories, level, rpm} = workoutStatus;
|
||||||
|
const diff = calculateDiff({program, cooldownMin, minutes, seconds, calories});
|
||||||
const cpm = calories / (minutes + (seconds / 60));
|
const cpm = calories / (minutes + (seconds / 60));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Boi type="left">
|
<Boi type="left">
|
||||||
<Timer minutes={minutes} seconds={seconds}/>
|
<Timer minutes={minutes} seconds={seconds} cooldownMin={cooldownMin}/>
|
||||||
<CalorieScore calories={calories} diff={diff} prevDiff={prevLongDiff}/>
|
<CalorieScore calories={calories} diff={diff} prevDiff={prevLongDiff}/>
|
||||||
<DistanceScore distance={distance}/>
|
|
||||||
<RpmScore rpm={rpm}/>
|
<RpmScore rpm={rpm}/>
|
||||||
<CpmScore cpm={cpm}/>
|
<CpmScore cpm={cpm}/>
|
||||||
|
<LevelScore level={level}/>
|
||||||
</Boi>
|
</Boi>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -42,6 +43,9 @@ export const CentreBoi = () => {
|
|||||||
state, program, setProgram, bike, setBike, bikes, programs,
|
state, program, setProgram, bike, setBike, bikes, programs,
|
||||||
start, pause, stop, create, workoutStatus,
|
start, pause, stop, create, workoutStatus,
|
||||||
hidden, setHidden,
|
hidden, setHidden,
|
||||||
|
workout,
|
||||||
|
prevLongDiff,
|
||||||
|
toggleCooldown,
|
||||||
} = useContext(StatusContext);
|
} = useContext(StatusContext);
|
||||||
const [options, setOptions] = useState(null);
|
const [options, setOptions] = useState(null);
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
@@ -115,6 +119,12 @@ export const CentreBoi = () => {
|
|||||||
|
|
||||||
useKey(["H", "h"], () => showHide());
|
useKey(["H", "h"], () => showHide());
|
||||||
|
|
||||||
|
useKey("*", () => {
|
||||||
|
if (state === "started") {
|
||||||
|
toggleCooldown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function showHide() {
|
function showHide() {
|
||||||
setHidden(!hidden);
|
setHidden(!hidden);
|
||||||
}
|
}
|
||||||
@@ -124,13 +134,14 @@ export const CentreBoi = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state === "started" && workoutStatus !== null) {
|
if (state === "started" && workoutStatus !== null) {
|
||||||
|
const {cooldownMin} = workout;
|
||||||
const {minutes, seconds, calories} = workoutStatus;
|
const {minutes, seconds, calories} = workoutStatus;
|
||||||
const diff = calculateDiff(program, minutes, seconds, calories);
|
const diff = calculateDiff({program, cooldownMin, minutes, seconds, calories});
|
||||||
|
|
||||||
if (diff < 0) {
|
if (diff < 0 || (minutes > 0 && seconds === 0)) {
|
||||||
return (
|
return (
|
||||||
<Boi type="centre">
|
<Boi type="centre">
|
||||||
<Warning>{diff}</Warning>
|
<Differ diff={diff} prevDiff={prevLongDiff} />
|
||||||
</Boi>
|
</Boi>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -147,15 +158,13 @@ export const CentreBoi = () => {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options[current] === void(0)) {
|
if (options[current] === void (0)) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return options[current].name;
|
return options[current].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(state);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Boi type="centre">
|
<Boi type="centre">
|
||||||
<StateFilter current={state} required="stopped">
|
<StateFilter current={state} required="stopped">
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import React, {createContext, useEffect, useState} from "react";
|
import React, {createContext, useCallback, useEffect, useState} from "react";
|
||||||
import {
|
import {
|
||||||
connectWorkout,
|
connectWorkout,
|
||||||
createNewWorkout,
|
createNewWorkout,
|
||||||
fetchActiveWorkouts, fetchBikes, fetchPrograms,
|
fetchActiveWorkouts,
|
||||||
openWebsocket, pauseWorkout,
|
openWebsocket,
|
||||||
|
pauseWorkout,
|
||||||
startWorkout,
|
startWorkout,
|
||||||
stopWorkout
|
stopWorkout, updateCooldownMins
|
||||||
} from "../hooks/net";
|
} from "../hooks/net";
|
||||||
import useMilestones from "../hooks/milestones";
|
import useMilestones from "../hooks/milestones";
|
||||||
|
import useOptions from "../hooks/options";
|
||||||
|
|
||||||
export const StatusContext = createContext({
|
export const StatusContext = createContext({
|
||||||
bike: null,
|
bike: null,
|
||||||
@@ -28,10 +30,9 @@ export const StatusContext = createContext({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const StatusContextProvider = ({children}) => {
|
export const StatusContextProvider = ({children}) => {
|
||||||
|
const {bikes, programs} = useOptions();
|
||||||
const [bike, setBike] = useState(null);
|
const [bike, setBike] = useState(null);
|
||||||
const [program, setProgram] = useState(null);
|
const [program, setProgram] = useState(null);
|
||||||
const [bikes, setBikes] = useState(null);
|
|
||||||
const [programs, setPrograms] = useState(null);
|
|
||||||
const [workout, setWorkout] = useState(null);
|
const [workout, setWorkout] = useState(null);
|
||||||
const [workoutStatus, setWorkoutStatus] = useState(null);
|
const [workoutStatus, setWorkoutStatus] = useState(null);
|
||||||
const [state, setState] = useState("offline");
|
const [state, setState] = useState("offline");
|
||||||
@@ -39,21 +40,24 @@ export const StatusContextProvider = ({children}) => {
|
|||||||
const [socket, setSocket] = useState(null);
|
const [socket, setSocket] = useState(null);
|
||||||
const [hidden, setHidden] = useState(false);
|
const [hidden, setHidden] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const toggleCooldown = useCallback(async () => {
|
||||||
if (programs === null) {
|
if (workout === null || workoutStatus === null) {
|
||||||
fetchPrograms().then(newPrograms => setPrograms(newPrograms));
|
return;
|
||||||
}
|
}
|
||||||
}, [programs]);
|
|
||||||
|
const {cooldownMin} = workout;
|
||||||
|
const {minutes} = workoutStatus;
|
||||||
|
|
||||||
|
if (cooldownMin === -1) {
|
||||||
|
setWorkout(await updateCooldownMins(workout, minutes + 1));
|
||||||
|
} else if (minutes < cooldownMin) {
|
||||||
|
setWorkout(await updateCooldownMins(workout, -1));
|
||||||
|
}
|
||||||
|
}, [workout, workoutStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bikes === null) {
|
if (bikes !== null && bikes.length === 1) {
|
||||||
fetchBikes().then(newBikes => {
|
setBike(bikes[0]);
|
||||||
setBikes(newBikes);
|
|
||||||
|
|
||||||
if (newBikes.length === 1) {
|
|
||||||
setBike(newBikes[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [bikes]);
|
}, [bikes]);
|
||||||
|
|
||||||
@@ -94,15 +98,17 @@ export const StatusContextProvider = ({children}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof body.workoutStatusBackfill !== "undefined") {
|
if (typeof body.workoutStatusBackfill !== "undefined") {
|
||||||
|
const cooldownMin = workout.cooldownMin;
|
||||||
body.workoutStatusBackfill.forEach(wsbf => {
|
body.workoutStatusBackfill.forEach(wsbf => {
|
||||||
setWorkoutStatus(wsbf);
|
setWorkoutStatus(wsbf);
|
||||||
msDispatch({type: "measure", payload: {...wsbf, program}});
|
msDispatch({type: "measure", payload: {...wsbf, program, cooldownMin}});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof body.workoutStatus !== "undefined") {
|
if (typeof body.workoutStatus !== "undefined") {
|
||||||
|
const cooldownMin = workout.cooldownMin;
|
||||||
setWorkoutStatus(body.workoutStatus);
|
setWorkoutStatus(body.workoutStatus);
|
||||||
msDispatch({type: "measure", payload: {...body.workoutStatus, program}});
|
msDispatch({type: "measure", payload: {...body.workoutStatus, program, cooldownMin}});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -162,6 +168,7 @@ export const StatusContextProvider = ({children}) => {
|
|||||||
programs, bikes,
|
programs, bikes,
|
||||||
prevDiff, prevLongDiff,
|
prevDiff, prevLongDiff,
|
||||||
hidden, setHidden,
|
hidden, setHidden,
|
||||||
|
toggleCooldown,
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</StatusContext.Provider>
|
</StatusContext.Provider>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.Warning {
|
.Warning {
|
||||||
margin-top: 1em;
|
margin-top: 0;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 150%;
|
font-size: 150%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {COLOR_VERY_BAD} from "../helpers/color";
|
import {COLOR_VERY_BAD, colorByDiff} from "../helpers/color";
|
||||||
|
|
||||||
import "./Misc.css";
|
import "./Misc.css";
|
||||||
|
import {diffString} from "../helpers/diff";
|
||||||
|
|
||||||
const Filter = ({bool, children}) => bool ? <>{children}</> : null;
|
const Filter = ({bool, children}) => bool ? <>{children}</> : null;
|
||||||
|
|
||||||
@@ -9,9 +10,9 @@ export const StateFilter = ({current, required, children}) => (
|
|||||||
<Filter bool={current === required}>{children}</Filter>
|
<Filter bool={current === required}>{children}</Filter>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Warning = ({children}) => (
|
export const Differ = ({diff, prevDiff}) => (
|
||||||
<div className="Warning" style={{color: COLOR_VERY_BAD}}>
|
<div className="Warning" style={{color: colorByDiff(diff, prevDiff)}}>
|
||||||
{children}
|
{diffString(diff)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -10,3 +10,11 @@
|
|||||||
font-size: 125%;
|
font-size: 125%;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Timer-cooldown {
|
||||||
|
color: #0BF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timer-awaits-cooldown {
|
||||||
|
color: #8FF;
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import "./Score.css";
|
|||||||
|
|
||||||
const Score = ({value, suffix = null, color = COLOR_NEUTRAL}) => (
|
const Score = ({value, suffix = null, color = COLOR_NEUTRAL}) => (
|
||||||
<div className="Score">
|
<div className="Score">
|
||||||
<span className="Score-value" style={{color}}>{value}</span>
|
<span className="Score-value" style={{color}}>{!isNaN(value) ? value : 0}</span>
|
||||||
{suffix !== null && (
|
{suffix !== null && (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
@@ -15,13 +15,24 @@ const Score = ({value, suffix = null, color = COLOR_NEUTRAL}) => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Timer = ({minutes, seconds}) => {
|
export const Timer = ({minutes, seconds, cooldownMin}) => {
|
||||||
function pad(number) {
|
function pad(number) {
|
||||||
return number >= 10 ? `${number}` : `0${number}`;
|
return number >= 10 ? `${number}` : `0${number}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasCooldown = cooldownMin >= 0 && cooldownMin !== void(0);
|
||||||
|
const isCooldown = hasCooldown && minutes >= cooldownMin;
|
||||||
|
const awaitsCooldown = hasCooldown && !isCooldown;
|
||||||
|
|
||||||
|
const classes = ["Timer"];
|
||||||
|
if (isCooldown) {
|
||||||
|
classes.push("Timer-cooldown");
|
||||||
|
} else if (awaitsCooldown) {
|
||||||
|
classes.push("Timer-awaits-cooldown");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Timer">
|
<div className={classes.join(" ")}>
|
||||||
<span className="Timer-number">{pad(minutes)}</span>
|
<span className="Timer-number">{pad(minutes)}</span>
|
||||||
{" : "}
|
{" : "}
|
||||||
<span className="Timer-number">{pad(seconds)}</span>
|
<span className="Timer-number">{pad(seconds)}</span>
|
||||||
@@ -33,7 +44,7 @@ export const CalorieScore = ({calories, diff, prevDiff}) => (
|
|||||||
<Score value={calories} color={colorByDiff(diff, prevDiff)} suffix="kal"/>
|
<Score value={calories} color={colorByDiff(diff, prevDiff)} suffix="kal"/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const DistanceScore = ({distance}) => <Score value={distance.toFixed(1)} suffix="km"/>;
|
export const LevelScore = ({level}) => <Score value={level} suffix="lvl"/>;
|
||||||
|
|
||||||
export const RpmScore = ({rpm}) => <Score value={rpm} suffix="rpm"/>;
|
export const RpmScore = ({rpm}) => <Score value={rpm} suffix="rpm"/>;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import {COLOR_BAD, COLOR_GOOD, COLOR_VERY_BAD, COLOR_VERY_GOOD, colorByDiff} from "./color";
|
||||||
|
|
||||||
|
describe("colorByDiff", function () {
|
||||||
|
it("should show dark red for worse bad", () => {
|
||||||
|
expect(colorByDiff(-5, -2)).toBe(COLOR_VERY_BAD);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show bright red for equal bad", () => {
|
||||||
|
expect(colorByDiff(-11, -11)).toBe(COLOR_BAD);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show bright red for better bad", () => {
|
||||||
|
expect(colorByDiff(-1, -7)).toBe(COLOR_BAD);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show dark green for better good", () => {
|
||||||
|
expect(colorByDiff(33, 25)).toBe(COLOR_VERY_GOOD);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show bright green for equal good", () => {
|
||||||
|
expect(colorByDiff(10, 10)).toBe(COLOR_GOOD);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show bright green for worse good", () => {
|
||||||
|
expect(colorByDiff(5, 7)).toBe(COLOR_GOOD);
|
||||||
|
});
|
||||||
|
});
|
||||||
+31
-16
@@ -2,24 +2,39 @@ export function diffString(diff) {
|
|||||||
return diff < 0 ? `${diff}` : `+${diff}`
|
return diff < 0 ? `${diff}` : `+${diff}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function calculateDiff(program, minutes, seconds, calories) {
|
export default function calculateDiff({program, cooldownMin = null, minutes, seconds, calories}) {
|
||||||
const {warmupMin, warmupCpm, cpm} = program;
|
const {warmupMin, warmupCpm, cpm, cooldownCpm} = program;
|
||||||
|
const actualWarmup = cooldownMin !== null && cooldownMin > 0
|
||||||
|
? Math.min(warmupMin, cooldownMin)
|
||||||
|
: warmupMin;
|
||||||
|
|
||||||
let preWarmup = 0;
|
// Minutes in each section
|
||||||
if (warmupMin > 0) {
|
const minWarmup = calculateMins(0, actualWarmup, minutes, seconds);
|
||||||
// Pre-warmup
|
const minMain = calculateMins(actualWarmup, cooldownMin, minutes, seconds);
|
||||||
const warmedUpMinutes = Math.min(minutes, warmupMin);
|
const minCooldown = calculateMins(cooldownMin, null, minutes, seconds);
|
||||||
const warmedUpSeconds = minutes >= warmupMin ? 0 : seconds;
|
|
||||||
preWarmup = Math.round((warmupCpm * (warmedUpMinutes + (warmedUpSeconds / 60))));
|
// Expected calories in each section
|
||||||
|
const calWarmup = minWarmup * warmupCpm;
|
||||||
|
const calMain = minMain * cpm;
|
||||||
|
const calCooldown = minCooldown * cooldownCpm;
|
||||||
|
|
||||||
|
return Math.round(calories - (calWarmup + calMain + calCooldown));
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateMins(minMinutes, maxMinutes, minutes, seconds) {
|
||||||
|
const fraction = seconds / 60;
|
||||||
|
|
||||||
|
if (minMinutes === null || minMinutes < 0) {
|
||||||
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post-warmup
|
if (minutes >= minMinutes) {
|
||||||
const trainedMinutes = Math.max(0, minutes - warmupMin);
|
if (maxMinutes !== null && minutes >= maxMinutes) {
|
||||||
const trainedSeconds = minutes >= warmupMin ? seconds : 0;
|
return maxMinutes - minMinutes;
|
||||||
const postWarmup = Math.round((cpm * (trainedMinutes + (trainedSeconds / 60))));
|
}
|
||||||
|
|
||||||
// Sum
|
return (minutes - minMinutes) + fraction;
|
||||||
const target = Math.round(preWarmup + postWarmup);
|
} else {
|
||||||
|
return 0.0;
|
||||||
return calories - target;
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import calculateDiff, {diffString} from "./diff";
|
||||||
|
|
||||||
|
const programWithoutWarmup = {
|
||||||
|
warmupMin: 0,
|
||||||
|
warmupCpm: 0,
|
||||||
|
cpm: 30,
|
||||||
|
cooldownCpm: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
const programWithWarmup = {
|
||||||
|
warmupMin: 10,
|
||||||
|
warmupCpm: 25,
|
||||||
|
cpm: 30,
|
||||||
|
cooldownCpm: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('diffString', () => {
|
||||||
|
it("should show a plus before zero", () => {
|
||||||
|
expect(diffString(0)).toBe("+0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show a plus before a positive number", () => {
|
||||||
|
expect(diffString(44)).toBe("+44");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show a minus when negatives", () => {
|
||||||
|
expect(diffString(-12)).toBe("-12");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("calculateDiff", () => {
|
||||||
|
it("should return zero at start", () => {
|
||||||
|
const diff = calculateDiff({
|
||||||
|
program: programWithoutWarmup,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0,
|
||||||
|
calories: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(diff).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should expect 300 calories after 0' / 10'", () => {
|
||||||
|
const diff = calculateDiff({
|
||||||
|
program: programWithoutWarmup,
|
||||||
|
minutes: 10,
|
||||||
|
seconds: 0,
|
||||||
|
calories: 305,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(diff).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should expect 865 calories after 10' / 20'50", () => {
|
||||||
|
const diff = calculateDiff({
|
||||||
|
program: programWithWarmup,
|
||||||
|
minutes: 30,
|
||||||
|
seconds: 50,
|
||||||
|
calories: 250 + 600 + 25 - 17,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Aim: 875
|
||||||
|
expect(diff).toBe(-17);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should expect 755 calories after 10' / 15' / 7'45", () => {
|
||||||
|
const diff = calculateDiff({
|
||||||
|
program: programWithWarmup,
|
||||||
|
cooldownMin: 25,
|
||||||
|
minutes: 32,
|
||||||
|
seconds: 45,
|
||||||
|
calories: 250 + 450 + 140 + 15,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(diff).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should expect 325 calories after 5' (half) / 0' / 10'", () => {
|
||||||
|
const diff = calculateDiff({
|
||||||
|
program: programWithWarmup,
|
||||||
|
cooldownMin: 5,
|
||||||
|
minutes: 15,
|
||||||
|
seconds: 0,
|
||||||
|
calories: 325
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(diff).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11,19 +11,21 @@ function reducer(state, {type, payload}) {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case "measure":
|
case "measure":
|
||||||
let {prevDiff, prevLongDiff, milestones} = state;
|
let {prevDiff, prevLongDiff, milestones} = state;
|
||||||
const {minutes, seconds, calories, program} = payload;
|
const {minutes, seconds, calories, program, cooldownMin} = payload;
|
||||||
|
|
||||||
if (minutes === 0 || seconds !== 0) {
|
if (minutes === 0 || seconds !== 0) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFive = minutes % 5 === 0;
|
const isFive = minutes % 5 === 0;
|
||||||
const diff = calculateDiff(program, minutes, seconds, calories);
|
const diff = calculateDiff({program, minutes, seconds, calories, cooldownMin});
|
||||||
let newMilestones = milestones.filter(m => m.minutes !== minutes);
|
let newMilestones = [...milestones];
|
||||||
newMilestones.push({
|
if (newMilestones.find(m => m.minutes === minutes) === void(0)) {
|
||||||
minutes, seconds, calories, diff,
|
newMilestones.push({
|
||||||
prevDiff: isFive ? prevLongDiff : prevDiff
|
minutes, seconds, calories, diff,
|
||||||
});
|
prevDiff: isFive ? prevLongDiff : prevDiff
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (isFive) {
|
if (isFive) {
|
||||||
newMilestones = newMilestones.filter(m => m.minutes % 5 === 0);
|
newMilestones = newMilestones.filter(m => m.minutes % 5 === 0);
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ export async function pauseWorkout(workout) {
|
|||||||
return await post(`/workout/${workout.id}/pause`);
|
return await post(`/workout/${workout.id}/pause`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateCooldownMins(workout, cooldownMin) {
|
||||||
|
return await put(`/workout/${workout.id}`, {cooldownMin});
|
||||||
|
}
|
||||||
|
|
||||||
export function openWebsocket(workout) {
|
export function openWebsocket(workout) {
|
||||||
return new WebSocket(url(`/workout/${workout.id}/subscribe`, "ws"));
|
return new WebSocket(url(`/workout/${workout.id}/subscribe`, "ws"));
|
||||||
}
|
}
|
||||||
@@ -53,6 +57,16 @@ function post(path, data = {}) {
|
|||||||
}).then(r => r.json());
|
}).then(r => r.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function put(path, data = {}) {
|
||||||
|
return fetch(url(path), {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
}).then(r => r.json());
|
||||||
|
}
|
||||||
|
|
||||||
function url(path, prefix = "http") {
|
function url(path, prefix = "http") {
|
||||||
return `${prefix}://127.0.0.1:9999/api${path}`;
|
return `${prefix}://127.0.0.1:9999/api${path}`;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {fetchBikes, fetchPrograms} from "./net";
|
||||||
|
|
||||||
|
export default function useOptions() {
|
||||||
|
const [bikes, setBikes] = useState(null);
|
||||||
|
const [programs, setPrograms] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (programs === null) {
|
||||||
|
fetchPrograms().then(newPrograms => setPrograms(newPrograms));
|
||||||
|
}
|
||||||
|
}, [programs]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (bikes === null) {
|
||||||
|
fetchBikes().then(newBikes => {
|
||||||
|
setBikes(newBikes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [bikes]);
|
||||||
|
|
||||||
|
return {bikes, programs};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user