|
|
if (typeof Overlay === "undefined") { var Overlay = class { workout = null; workoutState = null; workoutStatus = null; display = false; milestones = []; program = null; currentCpm = null; bikes = []; programs = []; workouts = []; bike = null; setup = null;
colors = { "-2": "#F44", "-1": "#FAA", "0": "#FFF", "1": "#AFA", "2": "#4F4", };
/** * @param {HTMLElement} mBody * @param {HTMLElement} mTopLeft * @param {HTMLElement} mCenter * @param {HTMLElement} mBottomRight */ constructor(mBody, mTopLeft, mCenter, mBottomRight) { this.mBody = mBody; this.mTopLeft = mTopLeft; this.mCenter = mCenter; this.mBottomRight = mBottomRight;
this.initStyle(); this.hide(); }
initStyle() { this.applyDefaultStyle(this.mTopLeft); this.applyDefaultStyle(this.mCenter); this.applyDefaultStyle(this.mBottomRight);
this.mTopLeft.id = "overlay-mTopLeft"; this.mTopLeft.style.left = 0; this.mTopLeft.style.top = "25%";
this.mCenter.id = "overlay-mCenter"; this.mCenter.style.left = "50%"; this.mCenter.style.top = "50%"; this.mCenter.style.transform = "translate(-50%, -50%)"; this.mCenter.style.backgroundColor = "rgba(0, 0, 0, 0.75)"; this.mCenter.style.fontSize = "4vmax";
this.mBottomRight.id = "overlay-mBottomRight"; this.mBottomRight.style.right = 0; this.mBottomRight.style.bottom = 0; this.mBottomRight.style.fontSize = "2vmax"; }
applyDefaultStyle(element) { this.mBody.append(element);
element.style.position = "fixed"; element.style.zIndex = 99999999; element.style.color = "#FFF"; element.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; element.style.padding = "0.25em 0.5ch"; element.style.fontSize = "3vmax"; element.style.lineHeight = "1.33em"; }
hide() { this.mTopLeft.style.display = "none"; this.mCenter.style.display = "none"; this.mBottomRight.style.display = "none";
this.display = false; }
show() { this.mTopLeft.style.display = "block"; this.mCenter.style.display = "block"; this.mBottomRight.style.display = "block";
this.display = true; }
async setUp() { this.writeCenter("Loading...");
this.bikes = await get("/bike"); this.programs = await get("/program"); this.workouts = await get("/workout?active=true");
this.writeCenter(null); this.updateCenterState(); }
async updateCenterState() { const state = this.getState();
switch (state) { case "down": this.writeCenter(`
(1) Ny økt<br/> (2) Fortsett forrige økt `);
break; case "bike": // TODO: Add this??
if (this.bikes.length === 1) { this.setup = {state: "program", bike: this.bikes[0]}; this.updateCenterState(); } else { this.writeCenter("Sykkelvalg ikke støttet"); } break; case "program": const options = this.programs.map((p, i) => `(${i + 1}) ${p.name}`)
this.writeCenter("Velg programm:<br/>" + options.join("<br/>")); break; case "disconnected": this.writeCenter("Kobler til..."); break; case "connected": this.writeCenter(`
(Enter) Start/Pause<br/> (Esc) Stopp `);
break; case "started": const calorieDiff = this.calorieDiff(); if (calorieDiff < 0) { this.writeCenter(`
<big style="color: ${this.colors[-1]};"><big><b>${calorieDiff}</b></big> kcal!</big> `);
} else { this.writeCenter(null); } break; default: this.writeCenter(`Ugyldig tilstand: ${state}`); }
this.updateKeyBindings(); }
updateKeyBindings() { const dis = this;
this.mBody.onkeyup = function (event) { const state = dis.getState();
if (state === "program") { const i = parseInt(event.key, 10); if (!isNaN(i) && typeof dis.programs[i - 1] !== "undefined") { dis.bike = {...dis.setup.bike}; dis.startWorkout(dis.bike, dis.programs[i - 1]); } }
let handled = false; switch (event.key) { case "1": if (state === "down") { dis.setup = {state: "bike"}; handled = true; } break; case "2": if (state === "down") { const last = dis.workouts[dis.workouts.length - 1]; console.log(last); if (last !== undefined) { dis.resumeWorkout(last); }
handled = true; } break; case "Enter": if (state === "connected") { dis.start(); handled = true; } else if (state === "started") { dis.pause(); handled = true; } break; case "Escape": if (state === "connected") { dis.stop(); handled = true; } break; }
if (handled) { dis.updateCenterState(); event.preventDefault(); } } }
async startWorkout(bike, program) { this.bike = bike; this.program = program; this.milestones = [];
const bikeId = bike.id; const programId = program.id;
this.workout = await post("/workout", {bikeId, programId}); this.updateCenterState();
this.workout = await post(`/workout/${this.workout.id}/connect`); this.updateCenterState();
this.setup = null; this.webSocket(); }
async resumeWorkout(workout) { this.bike = workout.bike; this.program = workout.program; this.workout = workout;
this.setup = null; this.webSocket(); }
async start() { this.workout = await post(`/workout/${this.workout.id}/start`); this.updateCenterState(); }
async pause() { this.workout = await post(`/workout/${this.workout.id}/pause`); this.updateCenterState(); }
async stop() { await post(`/workout/${this.workout.id}/stop`);
this.workout = null; this.workouts = await post(`/workout?active=true`); this.updateCenterState(); }
async webSocket() { let dis = this;
this.socket = new WebSocket(url(`/workout/${this.workout.id}/subscribe`, "ws"));
this.socket.onmessage = function ({data, timeStamp}) { let body = JSON.parse(data);
if (typeof body.workout !== "undefined") { dis.workout = {...body.workout, ...dis.workout} }
if (typeof body.workoutStatusBackfill !== "undefined") { body.workoutStatusBackfill.forEach(wsbf => dis.updateWorkoutStatus(wsbf)); }
if (typeof body.workoutStatus !== "undefined") { dis.updateWorkoutStatus(body.workoutStatus) } } }
updateWorkoutStatus(newState = null) { if (newState !== null) { this.workoutStatus = newState; }
if (this.workoutStatus === null) { this.writeTopLeft(""); return; }
const {minutes, seconds, calories, distance, rpm} = this.workoutStatus;
const cpmState = this.checkCpm(); const color = this.colors[cpmState];
this.writeTopLeft(`
<b><big>${pad(minutes)}</big></b> : <b><big>${pad(seconds)}</big></b><br/> <b style="color: ${color}">${calories}</b> <small>kcal</small><br/> <b>${distance.toFixed(1)}</b> <small>km</small><br/> <b>${rpm}</b> <small>rpm</small><br/> KPM: <b>${this.currentCpm.toFixed(1)}</b><br/> `);
if (seconds === 0 && minutes > 0) { this.updateMilestones(minutes, calories); }
this.updateCenterState(); }
checkCpm() { const {calories, minutes, seconds} = this.workoutStatus;
this.currentCpm = calories * 60 / ((minutes * 60) + seconds); if (this.currentCpm === Infinity) { this.currentCpm = 0; }
const program = this.program; if (program === null || this.currentCpm === 0) { return 0; }
if (this.calorieDiff() < 0) { return -1; } else { return 1; } }
updateMilestones(minutes, calories) { const expected = this.expectedCalories(minutes, 0); const diff = calories - expected;
this.milestones.push({minutes, calories, diff});
if (minutes % 5 === 0) { this.milestones = this.milestones.filter(m => m.minutes % 5 === 0) }
let lines = ""; let lastDiff = 0; this.milestones.sort((i, j) => j.minutes - i.minutes).forEach(({minutes, calories, diff}) => { let color = this.colors["0"]; if (diff < 0) { color = diff < lastDiff ? this.colors["-2"] : this.colors["-1"]; } else { color = diff > lastDiff ? this.colors["2"] : this.colors["1"]; }
lines += `<tr style="text-align: right; color: ${color}"><td>${minutes} </td><td> ${calories}</td></tr>`; });
if (lines.length > 0) { this.writeBottomRight(`
<table> ${lines} </table> `);
} else { this.writeBottomRight(null); } }
writeTopLeft(message) { this.mTopLeft.innerHTML = message;
if (message === null || !this.display) { this.mTopLeft.style.display = "none"; } else { this.mTopLeft.style.display = "block"; } }
writeCenter(message) { this.mCenter.innerHTML = message;
if (message === null || !this.display) { this.mCenter.style.display = "none"; } else { this.mCenter.style.display = "block"; } }
writeBottomRight(message) { this.mBottomRight.innerHTML = message;
if (message === null || !this.display) { this.mBottomRight.style.display = "none"; } else { this.mBottomRight.style.display = "block"; } }
calorieDiff() { if (this.workoutStatus == null) { return 0; }
const {minutes, seconds, calories} = this.workoutStatus;
const expected = this.expectedCalories(minutes, seconds); return calories - expected; }
expectedCalories(minutes, seconds) { const {warmupMin, warmupCpm, cpm} = this.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)))); }
// Post-warmup
const trainedMinutes = Math.max(0, minutes - warmupMin); const trainedSeconds = minutes >= warmupMin ? seconds : 0; const postWarmup = Math.round((cpm * (trainedMinutes + (trainedSeconds / 60))));
// Sum
return Math.round(preWarmup + postWarmup); }
getState() { return (this.workout || this.setup || {state: "down"}).state; } }; }
function pad(number) { return number >= 10 ? `${number}` : `0${number}`; }
function url(path, prefix = "http") { return `${prefix}://127.0.0.1:9999/api${path}`; }
function get(path) { return fetch(url(path), { method: "GET", }).then(r => r.json()); }
function post(path, data = {}) { return fetch(url(path), { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data) }).then(r => r.json()); }
const overlay = new Overlay( document.body, document.createElement("div"), document.createElement("div"), document.createElement("div"));
overlay.show(); overlay.setUp();
chrome.storage.sync.get('color', ({color}) => { });
|