);
diff --git a/my-bois/src/components/Score.css b/my-bois/src/components/Score.css
index 68eb5ae..87fb749 100644
--- a/my-bois/src/components/Score.css
+++ b/my-bois/src/components/Score.css
@@ -9,4 +9,12 @@
.Timer-number {
font-size: 125%;
font-weight: 800;
+}
+
+.Timer-cooldown {
+ color: #0BF;
+}
+
+.Timer-awaits-cooldown {
+ color: #8FF;
}
\ No newline at end of file
diff --git a/my-bois/src/components/Score.jsx b/my-bois/src/components/Score.jsx
index f8d576d..755d40c 100644
--- a/my-bois/src/components/Score.jsx
+++ b/my-bois/src/components/Score.jsx
@@ -5,7 +5,7 @@ import "./Score.css";
const Score = ({value, suffix = null, color = COLOR_NEUTRAL}) => (
);
-export const Timer = ({minutes, seconds}) => {
+export const Timer = ({minutes, seconds, cooldownMin}) => {
function pad(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 (
-
+
{pad(minutes)}
{" : "}
{pad(seconds)}
@@ -33,7 +44,7 @@ export const CalorieScore = ({calories, diff, prevDiff}) => (
);
-export const DistanceScore = ({distance}) => ;
+export const LevelScore = ({level}) => ;
export const RpmScore = ({rpm}) => ;
diff --git a/my-bois/src/helpers/color.test.js b/my-bois/src/helpers/color.test.js
new file mode 100644
index 0000000..4a43ad2
--- /dev/null
+++ b/my-bois/src/helpers/color.test.js
@@ -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);
+ });
+});
\ No newline at end of file
diff --git a/my-bois/src/helpers/diff.js b/my-bois/src/helpers/diff.js
index cdbee76..3575312 100644
--- a/my-bois/src/helpers/diff.js
+++ b/my-bois/src/helpers/diff.js
@@ -2,24 +2,39 @@ export function diffString(diff) {
return diff < 0 ? `${diff}` : `+${diff}`
}
-export default function calculateDiff(program, minutes, seconds, calories) {
- const {warmupMin, warmupCpm, cpm} = program;
-
- let preWarmup = 0;
- if (warmupMin > 0) {
- // Pre-warmup
- const warmedUpMinutes = Math.min(minutes, warmupMin);
- const warmedUpSeconds = minutes >= warmupMin ? 0 : seconds;
- preWarmup = Math.round((warmupCpm * (warmedUpMinutes + (warmedUpSeconds / 60))));
- }
+export default function calculateDiff({program, cooldownMin = null, minutes, seconds, calories}) {
+ const {warmupMin, warmupCpm, cpm, cooldownCpm} = program;
+ const actualWarmup = cooldownMin !== null && cooldownMin > 0
+ ? Math.min(warmupMin, cooldownMin)
+ : warmupMin;
+
+ // Minutes in each section
+ const minWarmup = calculateMins(0, actualWarmup, minutes, seconds);
+ const minMain = calculateMins(actualWarmup, cooldownMin, minutes, seconds);
+ const minCooldown = calculateMins(cooldownMin, null, minutes, seconds);
+
+ // 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;
- // Post-warmup
- const trainedMinutes = Math.max(0, minutes - warmupMin);
- const trainedSeconds = minutes >= warmupMin ? seconds : 0;
- const postWarmup = Math.round((cpm * (trainedMinutes + (trainedSeconds / 60))));
+ if (minMinutes === null || minMinutes < 0) {
+ return 0.0;
+ }
- // Sum
- const target = Math.round(preWarmup + postWarmup);
+ if (minutes >= minMinutes) {
+ if (maxMinutes !== null && minutes >= maxMinutes) {
+ return maxMinutes - minMinutes;
+ }
- return calories - target;
+ return (minutes - minMinutes) + fraction;
+ } else {
+ return 0.0;
+ }
}
\ No newline at end of file
diff --git a/my-bois/src/helpers/diff.test.js b/my-bois/src/helpers/diff.test.js
new file mode 100644
index 0000000..a929c28
--- /dev/null
+++ b/my-bois/src/helpers/diff.test.js
@@ -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);
+ });
+});
diff --git a/my-bois/src/hooks/milestones.js b/my-bois/src/hooks/milestones.js
index d7d77aa..08a12c2 100644
--- a/my-bois/src/hooks/milestones.js
+++ b/my-bois/src/hooks/milestones.js
@@ -11,19 +11,21 @@ function reducer(state, {type, payload}) {
switch (type) {
case "measure":
let {prevDiff, prevLongDiff, milestones} = state;
- const {minutes, seconds, calories, program} = payload;
+ const {minutes, seconds, calories, program, cooldownMin} = payload;
if (minutes === 0 || seconds !== 0) {
return state;
}
const isFive = minutes % 5 === 0;
- const diff = calculateDiff(program, minutes, seconds, calories);
- let newMilestones = milestones.filter(m => m.minutes !== minutes);
- newMilestones.push({
- minutes, seconds, calories, diff,
- prevDiff: isFive ? prevLongDiff : prevDiff
- });
+ const diff = calculateDiff({program, minutes, seconds, calories, cooldownMin});
+ let newMilestones = [...milestones];
+ if (newMilestones.find(m => m.minutes === minutes) === void(0)) {
+ newMilestones.push({
+ minutes, seconds, calories, diff,
+ prevDiff: isFive ? prevLongDiff : prevDiff
+ });
+ }
if (isFive) {
newMilestones = newMilestones.filter(m => m.minutes % 5 === 0);
diff --git a/my-bois/src/hooks/net.js b/my-bois/src/hooks/net.js
index 858d321..01002fe 100644
--- a/my-bois/src/hooks/net.js
+++ b/my-bois/src/hooks/net.js
@@ -33,6 +33,10 @@ export async function pauseWorkout(workout) {
return await post(`/workout/${workout.id}/pause`);
}
+export async function updateCooldownMins(workout, cooldownMin) {
+ return await put(`/workout/${workout.id}`, {cooldownMin});
+}
+
export function openWebsocket(workout) {
return new WebSocket(url(`/workout/${workout.id}/subscribe`, "ws"));
}
@@ -53,6 +57,16 @@ function post(path, data = {}) {
}).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") {
return `${prefix}://127.0.0.1:9999/api${path}`;
}
\ No newline at end of file
diff --git a/my-bois/src/hooks/options.js b/my-bois/src/hooks/options.js
new file mode 100644
index 0000000..b1d387a
--- /dev/null
+++ b/my-bois/src/hooks/options.js
@@ -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};
+}
\ No newline at end of file