Stian Aune
5 years ago
4 changed files with 446 additions and 52 deletions
-
9background.js
-
459contentScript.js
-
3manifest.json
-
7popup.js
@ -1,38 +1,443 @@ |
|||
function applyDefaultStyle(element) { |
|||
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.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; |
|||
|
|||
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} |
|||
} |
|||
|
|||
let mCalorieCount = document.createElement("div"); |
|||
mCalorieCount.id = "ykonsole-calorie-count"; |
|||
applyDefaultStyle(mCalorieCount); |
|||
mCalorieCount.style.left = 0; |
|||
mCalorieCount.style.top = 0; |
|||
mCalorieCount.innerHTML = `
|
|||
<b>25</b>:<b>23</b> |
|||
<br/> |
|||
<b>883</b> kal |
|||
`;
|
|||
document.body.append(mCalorieCount); |
|||
|
|||
let mResults = document.createElement("div"); |
|||
mResults.id = "ykonsole-result"; |
|||
applyDefaultStyle(mResults); |
|||
mResults.style.right = 0; |
|||
mResults.style.bottom = 0; |
|||
mResults.style.textAlign = "right"; |
|||
mResults.innerHTML = `
|
|||
5 161<br/> |
|||
10 332<br/> |
|||
15 490<br/> |
|||
20 712<br/> |
|||
25 875<br/> |
|||
`;
|
|||
document.body.append(mResults); |
|||
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) { |
|||
this.milestones.push({minutes, calories}); |
|||
|
|||
if (minutes % 5 === 0) { |
|||
this.milestones = this.milestones.filter(m => m.minutes % 5 === 0) |
|||
} |
|||
|
|||
let lines = ""; |
|||
this.milestones.sort((i, j) => j.minutes - i.minutes).forEach(({minutes, calories}) => { |
|||
lines += `<tr style="text-align: right"><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() { |
|||
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 = warmedUpMinutes >= warmupMin ? 0 : seconds; |
|||
preWarmup = Math.round((warmupCpm * (warmedUpMinutes + (warmedUpSeconds / 60)))); |
|||
} |
|||
|
|||
// Post-warmup
|
|||
const trainedMinutes = minutes - warmupMin; |
|||
const postWarmup = Math.round((cpm * (trainedMinutes + (seconds / 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}) => { |
|||
}); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue