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": "#F66", "-1": "#FBB", "0": "#FFF", "1": "#BFB", "2": "#6F6", }; /** * @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
(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:
" + options.join("
")); break; case "disconnected": this.writeCenter("Kobler til..."); break; case "connected": this.writeCenter(` (Enter) Start/Pause
(Esc) Stopp `); break; case "started": const calorieDiff = this.calorieDiff(); if (calorieDiff < 0) { this.writeCenter(` ${calorieDiff} kcal! `); } 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(` ${pad(minutes)} : ${pad(seconds)}
${calories} kcal
${distance.toFixed(1)} km
${rpm} rpm
KPM: ${this.currentCpm.toFixed(1)}
`); 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 += `${minutes}  ${calories}`; lastDiff = diff; }); if (lines.length > 0) { this.writeBottomRight(` ${lines}
`); } 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}) => { });