diff --git a/index.js b/index.js
index feb881a..f1366d2 100644
--- a/index.js
+++ b/index.js
@@ -9,12 +9,12 @@ import { FileHandler } from './file.js';
import { StopWatch } from './workout.js';
import { WakeLock } from './lock.js';
import { workouts } from './workouts/workouts.js';
-import { Storage } from './storage.js';
import { ControllableConnectionView,
HrbConnectionView,
ControllableSettingsView,
HrbSettingsView,
DataScreen,
+ DataBar,
GraphHr,
GraphPower,
GraphWorkout,
@@ -31,31 +31,33 @@ import { DeviceController,
WorkoutController,
Screen,
Vibrate } from './controllers.js';
+import { IDB, Storage } from './storage.js';
import { DataMock } from './test/mock.js';
-
'use strict';
-function start() {
+
+async function start() {
let hrb = new Hrb({name: 'hrb'});
let flux = new Controllable({name: 'controllable'});
let watch = new StopWatch();
let lock = new WakeLock();
- ControllableConnectionView({dom: dom.controllableConnectionScreen});
- HrbConnectionView({dom: dom.hrbConnectionScreen});
+ ControllableConnectionView();
+ HrbConnectionView();
- ControllableConnectionView({dom: dom.controllableSettings});
- HrbConnectionView({dom: dom.hrbSettings});
+ ControllableConnectionView();
+ HrbConnectionView();
- ControllableSettingsView({dom: dom.controllableSettings, name: 'controllable'});
- HrbSettingsView({dom: dom.hrbSettings, name: 'hrb'});
+ ControllableSettingsView({name: 'controllable'});
+ HrbSettingsView({name: 'hrb'});
- DataScreen({dom: dom.datascreen});
- GraphPower({dom: dom.graphPower});
+ DataScreen();
+ // DataBar();
+ GraphPower();
GraphWorkout({dom: dom.graphWorkout});
- WatchView({dom: dom.watch});
+ WatchView();
ControlView({dom: dom.controls});
LoadWorkoutView({dom: dom.file});
WorkoutsView({dom: dom.workouts, workouts: workouts});
@@ -68,7 +70,7 @@ function start() {
WorkoutController();
Screen();
- // Session();
+
let storage = new Storage();
xf.dispatch('app:start');
diff --git a/lock.js b/lock.js
index ff02bdb..9e4f87c 100644
--- a/lock.js
+++ b/lock.js
@@ -1,3 +1,5 @@
+import { xf } from './xf.js';
+
class WakeLock {
constructor(args) {
this.lock = undefined;
@@ -14,6 +16,10 @@ class WakeLock {
self.lockScreen();
document.addEventListener('visibilitychange', self.onVisibilityChange.bind(self));
+
+ window.addEventListener('beforeunload', e => {
+ xf.dispatch('lock:beforeunload');
+ });
}
checkVisibility() {
let isVisible = false;
@@ -43,6 +49,7 @@ class WakeLock {
lock.addEventListener('release', e => {
self.isLocked = false;
+ xf.dispatch('lock:release');
console.log(`Wake lock released.`);
});
}
diff --git a/session.js b/session.js
new file mode 100644
index 0000000..e66c9fa
--- /dev/null
+++ b/session.js
@@ -0,0 +1,76 @@
+import { xf } from './xf.js';
+import { first, second, third, last } from './functions.js';
+
+class Session {
+ constructor(args) {
+ this.idb = args.idb || {};
+ this.data = {};
+ this.init();
+ }
+ init() {
+ let self = this;
+
+ // xf.reg('db:elapsed', elapsed => self.data.elapsed);
+
+ // xf.sub('lock:release', e => {
+ // // console.log(db);
+ // });
+
+ // xf.sub('lock:beforeunload', e => {
+ // // backupSession(idb, data);
+ // });
+ }
+ async restore(idb) {
+ let self = this;
+ let sessions = await self.idb.getAll(self.idb.db, 'session');
+ // let sessions = await idb.getAll(idb.db, 'session');
+ let session = last(sessions);
+
+ if(session.hasOwnProperty('elapsed')) {
+ if(session.elapsed > 0) {
+ // restore db state
+ xf.dispatch(`session:restore`, session);
+ // restore components state
+ xf.dispatch(`session:watchRestore`, session);
+ console.log('dispatch session:restore');
+ } else {
+ console.log('dispatch session:clear');
+ self.clear(self.idb);
+ }
+ }
+ console.log(`sessions`);
+ console.log(sessions);
+ }
+ async backup(idb, db) {
+ let self = this;
+ let session = self.dbToSession(db);
+ idb.put(idb.db, 'session', session);
+ }
+ async clear(idb) {
+ let self = this;
+ idb.clearEntries(idb.db, 'session');
+ }
+
+ dbToSession(db) {
+ let session = {
+ id: 0,
+ elapsed: db.elapsed,
+ lapTime: db.lapTime,
+ stepTime: db.stepTime,
+ targetPwr: db.targetPwr,
+ records: db.records,
+ workoutStepIndex: db.workoutStepIndex,
+ workoutIntervalIndex: db.workoutIntervalIndex,
+ };
+
+ console.log(session);
+ return session;
+ }
+ // sessionToDb(db, session) {
+ // // db.records = session.records;
+ // db.elapsed = session.elapsed;
+ // // db.target = session.target;
+ // }
+}
+
+export { Session };
diff --git a/storage.js b/storage.js
index 7dc45ae..241a8d6 100644
--- a/storage.js
+++ b/storage.js
@@ -1,5 +1,9 @@
import { xf } from './xf.js';
+const types = {
+ transaction: ['readonly', 'readwrite', 'versionchange'],
+};
+
class IDB {
constructor() {
this.db = undefined;
@@ -7,97 +11,124 @@ class IDB {
}
init() {
let self = this;
- xf.sub('idb:open:success', e => {
- console.log(`idb:open:success ${e.detail.data}`);
- self.db = e.detail.data;
+ xf.sub('idb:open-success', idb => {
+ console.log(`idb:open-success`);
+ self.db = idb;
});
- xf.sub('idb:delete:success', e => {
- console.log(`idb:delete:success ${e.detail.data}`);
- self.db = e.detail.data;
- });
- xf.sub('idb:create:success', e => {
- console.log(`idb:create:success ${e.detail.data}`);
- // self.db = e.detail.data;
+ xf.sub('idb:open-error', e => {
+ console.warn(`idb:open-error`);
});
}
- open(name, version) {
+ open(name, version, storeName = '') {
let self = this;
+ console.log(`idb:open ${name}:${storeName} ...`);
let openReq = window.indexedDB.open(name, version);
- openReq.onupgradeneeded = function(e) {
- let idb = openReq.result;
- switch(e.oldVersion) {
- case 0: self.createStore(idb, name);
+ return new Promise((resolve, reject) => {
+ openReq.onupgradeneeded = function(e) {
+ let idb = openReq.result;
+ switch(e.oldVersion) {
+ case 0: self.createStore(idb, storeName);
case 1: self.update(idb);
- }
- };
- openReq.onerror = function() {
- console.error(`idb open error: ${openReq.error}`);
- xf.dispatch('idb:open:error');
- };
- openReq.onsuccess = function() {
- let idb = openReq.result;
- xf.dispatch('idb:open:success', idb);
- };
- }
- delete(name) {
- let deleteReq = indexedDB.deleteDatabase(name);
- deleteReq.onerror = function() {
- console.error(`idb delete error: ${deleteReq.error}`);
- };
- deleteReq.onsuccess = function() {
- let res = deleteReq.result;
- xf.dispatch('idb:delete:success', res);
- };
+ }
+ };
+ openReq.onerror = function() {
+ console.error(`idb open error: ${openReq.error}`);
+ xf.dispatch('idb:open-error');
+ return reject(openReq.error);
+ };
+ openReq.onsuccess = function() {
+ let idb = openReq.result;
+ xf.dispatch('idb:open-success', idb);
+ return resolve(openReq.result);
+ };
+ });
+ }
+ delete(idb, name) {
+ let self = this;
+ let deleteReq = idb.deleteDatabase(name);
+
+ return self.promisify(deleteReq).then(res => {
+ console.log(`idb delete ${name} success`);
+ return res;
+ }).catch(err => {
+ console.error(`idb delete ${name} error: ${err}`);
+ return {};
+ });
}
update(idb) {
- xf.dispatch('idb:update:success', idb);
+ let self = this;
+ xf.dispatch('idb:update-success', idb);
return idb;
}
createStore(idb, name) {
+ let self = this;
if (!idb.objectStoreNames.contains(name)) {
idb.createObjectStore(name, {keyPath: 'id'});
- xf.dispatch('idb:create:success', idb);
+ xf.dispatch('idb:create-success', idb);
} else {
console.error(`idb trying to create store with existing name: ${name}`);
}
}
- add(idb, storeName, item, type = 'readonly') {
- let transaction = idb.transaction(storeName, type);
- let store = transaction.objectStore(storeName);
- let addReq = store.add(item);
- addReq.onsuccess = function() {
- let res = addReq.result;
- console.log(`idb add success: ${res}`);
- };
- addReq.onerror = function() {
- let err = addReq.error;
- console.error(`idb add error: ${err}, , store: ${storeName} item: ${item}`);
- };
- transaction.oncomplete = function() {
- let res = transaction;
- console.log(`idb transaction complete: ${res}`);
- };
- }
- put(idb, storeName, item, type = 'readonly') {
+ add(idb, storeName, item) {
+ let self = this;
+ return self.transaction(idb, storeName, 'add', item, 'readwrite');
+ }
+ put(idb, storeName, item) {
+ let self = this;
+ return self.transaction(idb, storeName, 'put', item, 'readwrite');
+ }
+ get(idb, storeName, key) {
+ let self = this;
+ return self.transaction(idb, storeName, 'get', key, 'readonly');
+ }
+ getAll(idb, storeName) {
+ let self = this;
+ return self.transaction(idb, storeName, 'getAll', undefined, 'readonly');
+ }
+ deleteEntry(idb, storeName, id) {
+ let self = this;
+ return self.transaction(idb, storeName, 'delete', id, 'readwrite');
+ }
+ clearEntries(idb, storeName) {
+ let self = this;
+ return self.transaction(idb, storeName, 'clear', undefined, 'readwrite');
+ }
+ transaction(idb, storeName, method, param = undefined, type = 'readonly') {
+ let self = this;
let transaction = idb.transaction(storeName, type);
let store = transaction.objectStore(storeName);
- let addReq = store.add(item);
- addReq.onsuccess = function() {
- let res = addReq.result;
- console.log(`idb put success: ${res}`);
- };
- addReq.onerror = function() {
- let err = addReq.error;
- console.error(`idb put error: ${err}, , store: ${storeName} item: ${item}`);
- };
+ let req;
+ // console.log(`${storeName}: ${method}`);
+ // console.log(transaction);
+ // console.log(store);
+
+ if(param === undefined) {
+ req = store[method]();
+ } else {
+ req = store[method](param);
+ }
+
+ return self.promisify(req).then(res => {
+ console.log(`idb ${method} ${storeName} success`);
+ return res;
+ }).catch(err => {
+ console.error(`idb ${method} ${storeName} error: ${err}`);
+ return [];
+ });
+ }
+ promisify(request) {
+ return new Promise((resolve, reject) => {
+ request.onsuccess = function(event) {
+ return resolve(request.result);
+ };
+ request.onerror = function(event) {
+ return reject(request.error);
+ };
+ });
}
}
-let types = {
- transaction: ['readonly', 'readwrite', 'versionchange'],
-};
-
class Storage {
constructor(){
this.init();
@@ -120,16 +151,14 @@ class Storage {
xf.dispatch('storage:ftp', parseInt(ftp));
xf.dispatch('storage:weight', parseInt(weight));
- xf.sub('ui:ftp', e => {
- self.setFtp(parseInt(e.detail.data));
+ xf.sub('ui:ftp', ftp => {
+ self.setFtp(parseInt(ftp));
});
- xf.sub('ui:weight', e => {
- self.setWeight(parseInt(e.detail.data));
+ xf.sub('ui:weight', weight => {
+ self.setWeight(parseInt(weight));
});
}
- open() {
- }
setFtp(ftp) {
if(isNaN(ftp) || ftp > 600 || ftp < 30) {
console.warn(`Trying to enter Invalid FTP value in Storage: ${ftp}`);
@@ -171,4 +200,4 @@ class Storage {
}
}
-export { Storage };
+export { Storage, IDB };
diff --git a/test/mock.js b/test/mock.js
index 4b864c5..cef0b42 100644
--- a/test/mock.js
+++ b/test/mock.js
@@ -4,6 +4,7 @@ import { avgOfArray,
sum,
mps,
kph,
+ rand,
first,
last,
round,
@@ -12,11 +13,17 @@ import { avgOfArray,
function DataMock(args) {
let count = 0;
let interval = null;
+ let power = 0;
+
+ xf.sub('db:targetPwr', pwr => {
+ power = pwr;
+ });
xf.sub('watch:started', e => {
interval = setInterval(function() {
- let power = (count % 60) < 30 ? 100 : 300;
+ // let power = (count % 60) < 30 ? 100 : 300;
+
let hr = (count % 60) < 30 ? 120 : 160;
let cadence = (count % 60) < 30 ? 75 : 90;
let speed = (count % 60) < 30 ? 27.0 : 39.0;
@@ -26,13 +33,14 @@ function DataMock(args) {
xf.dispatch('device:hr', hr);
}
if(args.pwr) {
- xf.dispatch('device:pwr', power);
+ xf.dispatch('device:pwr', power + rand(-10, 10));
xf.dispatch('device:cad', cadence);
xf.dispatch('device:spd', speed);
// xf.dispatch('device:dist', distance);
}
+ console.log(`mock ${power}`);
count += 1;
- }, 1000);
+ }, 700);
});
xf.sub('watch:stopped', e => {
diff --git a/views.js b/views.js
index 66e59bf..dbc6c75 100644
--- a/views.js
+++ b/views.js
@@ -1,4 +1,5 @@
import { xf } from './xf.js';
+import { q } from './q.js';
import { avgOfArray,
hrToColor,
powerToZone,
@@ -8,7 +9,11 @@ import { avgOfArray,
import { parseZwo, intervalsToGraph } from './parser.js';
function ControllableConnectionView(args) {
- let dom = args.dom;
+ let dom = {
+ switchBtn: q.get('#controllable-connection-btn'),
+ indicator: q.get('#controllable-connection-btn .indicator'),
+ };
+
xf.sub('pointerup', e => xf.dispatch('ui:controllableSwitch'), dom.switchBtn);
xf.sub('controllable:connected', e => {
@@ -23,7 +28,11 @@ function ControllableConnectionView(args) {
}
function HrbConnectionView(args) {
- let dom = args.dom;
+ let dom = {
+ switchBtn: q.get('#hrb-connection-btn'),
+ indicator: q.get('#hrb-connection-btn .indicator'),
+ };
+
xf.sub('pointerup', e => xf.dispatch('ui:hrbSwitch'), dom.switchBtn);
xf.sub('hrb:connected', e => {
@@ -38,37 +47,87 @@ function HrbConnectionView(args) {
}
function DataScreen(args) {
- let dom = args.dom;
- xf.sub('db:hr', e => {
- let hr = e.detail.data.hr;
+ let dom = {
+ time: q.get('#time'),
+ interval: q.get('#interval-time'),
+ targetPwr: q.get('#target-power'),
+ power: q.get('#power'),
+ cadence: q.get('#cadence'),
+ speed: q.get('#speed'),
+ distance: q.get('#distance'),
+ heartRate: q.get('#heart-rate')
+ };
+
+ xf.sub('db:hr', hr => {
dom.heartRate.textContent = `${hr}`;
});
- xf.sub('db:pwr', e => {
- let pwr = e.detail.data.pwr;
+ xf.sub('db:pwr', pwr => {
dom.power.textContent = `${pwr}`;
});
- xf.sub('db:distance', e => {
- let dis = e.detail.data.distance;
- dom.distance.textContent = `${metersToDistance(dis)}`;
+ xf.sub('db:distance', distance => {
+ dom.distance.textContent = `${metersToDistance(distance)}`;
});
- xf.sub('db:vspd', e => {
- let vspd = e.detail.data.vspd;
+ xf.sub('db:vspd', vspd => {
dom.speed.textContent = `${vspd.toFixed(1)}`;
});
- xf.sub('db:spd', e => {
- let spd = e.detail.data.spd;
+ xf.sub('db:spd', spd => {
dom.speed.textContent = `${spd.toFixed(1)}`;
});
- xf.sub('db:cad', e => {
- let cad = e.detail.data.cad;
+ xf.sub('db:cad', cad => {
+ dom.cadence.textContent = `${cad}`;
+ });
+ xf.sub('db:elapsed', elapsed => {
+ dom.time.textContent = secondsToHms(elapsed);
+ });
+ xf.sub('db:lapTime', lapTime => {
+ if(!Number.isInteger(lapTime)) {
+ lapTime = 0;
+ }
+ if(lapTime < 0) {
+ lapTime = 0;
+ }
+ dom.interval.textContent = secondsToHms(lapTime, true);
+ });
+ xf.sub('db:targetPwr', targetPwr => {
+ dom.targetPwr.textContent = targetPwr;
+ });
+}
+
+function DataBar(args) {
+ let dom = {
+ time: q.get('#data-bar-time'),
+ interval: q.get('#data-bar-interval-time'),
+ targetPwr: q.get('#data-bar-target-power'),
+ power: q.get('#data-bar-power'),
+ cadence: q.get('#data-bar-cadence'),
+ heartRate: q.get('#data-bar-heart-rate'),
+ progress: q.get('#data-bar-progress-cont'),
+ };
+ let ftp = 250;
+
+ xf.sub('db:hr', hr => {
+ // let hr = e.detail.data.hr;
+ dom.heartRate.textContent = `${hr}`;
+ });
+ xf.sub('db:pwr', pwr => {
+ // let pwr = e.detail.data.pwr;
+ dom.power.textContent = `${pwr}`;
+ dom.progress.insertAdjacentHTML('beforeend', `
`);
+ });
+ xf.sub('db:vspd', vspd => {
+ // let vspd = e.detail.data.vspd;
+ dom.speed.textContent = `${vspd.toFixed(1)}`;
+ });
+ xf.sub('db:cad', cad => {
+ // let cad = e.detail.data.cad;
dom.cadence.textContent = `${cad}`;
});
- xf.sub('db:elapsed', e => {
- let elapsed = e.detail.data.elapsed;
+ xf.sub('db:elapsed', elapsed => {
+ // let elapsed = e.detail.data.elapsed;
dom.time.textContent = secondsToHms(elapsed);
});
- xf.sub('db:lapTime', e => {
- let lapTime = e.detail.data.lapTime;
+ xf.sub('db:lapTime', lapTime => {
+ // let lapTime = e.detail.data.lapTime;
if(!Number.isInteger(lapTime)) {
lapTime = 0;
}
@@ -77,74 +136,117 @@ function DataScreen(args) {
}
dom.interval.textContent = secondsToHms(lapTime, true);
});
- xf.sub('db:targetPwr', e => {
- dom.targetPwr.textContent = e.detail.data.targetPwr;
+ xf.sub('db:targetPwr', targetPwr => {
+ // dom.targetPwr.textContent = e.detail.data.targetPwr;
+ dom.targetPwr.textContent = targetPwr;
});
}
function ControllableSettingsView(args) {
- let dom = args.dom;
let name = args.name || 'controllable';
+ let dom = {
+ switchBtn: q.get('#controllable-settings-btn'),
+ indicator: q.get('#controllable-settings-btn .indicator'),
+ name: q.get('#controllable-settings-name'),
+ manufacturer: q.get('#controllable-settings-manufacturer'),
+ model: q.get('#controllable-settings-model'),
+ firmware: q.get('#controllable-settings-firmware'),
+ power: q.get('#controllable-settings-power'),
+ cadence: q.get('#controllable-settings-cadence'),
+ speed: q.get('#controllable-settings-speed'),
+ };
- xf.sub('db:pwr', e => {
- let power = e.detail.data.pwr;
- dom.power.textContent = `${power}`;
+ xf.sub('db:pwr', pwr => {
+ // let power = e.detail.data.pwr;
+ dom.power.textContent = `${pwr}`;
});
- xf.sub('db:cad', e => {
- let cadence = e.detail.data.cad;
- dom.cadence.textContent = `${cadence}`;
+ xf.sub('db:cad', cad => {
+ // let cadence = e.detail.data.cad;
+ dom.cadence.textContent = `${cad}`;
});
- xf.sub('db:spd', e => {
- let speed = e.detail.data.spd;
- dom.speed.textContent = `${speed}`;
+ xf.sub('db:spd', spd => {
+ // let speed = e.detail.data.spd;
+ dom.speed.textContent = `${spd}`;
});
- xf.sub(`${name}:info`, e => {
- console.log(e.detail.data);
- dom.name.textContent = `${e.detail.data.name}`;
- dom.model.textContent = `${e.detail.data.modelNumberString}`;
- dom.manufacturer.textContent = `${e.detail.data.manufacturerNameString}`;
- dom.firmware.textContent = `${e.detail.data.firmwareRevisionString}`;
+ xf.sub(`${name}:info`, data => {
+ console.log(data);
+ dom.name.textContent = `${data.name}`;
+ dom.model.textContent = `${data.modelNumberString}`;
+ dom.manufacturer.textContent = `${data.manufacturerNameString}`;
+ dom.firmware.textContent = `${data.firmwareRevisionString}`;
});
}
function HrbSettingsView(args) {
- let dom = args.dom;
let name = args.name || 'hrb';
+ let dom = {
+ switchBtn: q.get('#hrb-settings-btn'),
+ indicator: q.get('#hrb-settings-btn .indicator'),
+ name: q.get('#hrb-settings-name'),
+ manufacturer: q.get('#hrb-settings-manufacturer'),
+ model: q.get('#hrb-settings-model'),
+ firmware: q.get('#hrb-settings-firmware'),
+ value: q.get('#hrb-settings-value'),
+ battery: q.get('#hrb-settings-battery'),
+ };
- xf.sub('db:hr', e => {
- let hr = e.detail.data.hr;
+ xf.sub('db:hr', hr => {
+ // let hr = e.detail.data.hr;
dom.value.textContent = `${hr} bpm`;
});
- xf.sub(`${name}:info`, e => {
- console.log(e.detail.data);
- dom.name.textContent = `${e.detail.data.name}`;
- dom.model.textContent = `${e.detail.data.modelNumberString}`;
- dom.manufacturer.textContent = `${e.detail.data.manufacturerNameString}`;
- dom.firmware.textContent = `${e.detail.data.firmwareRevisionString}`;
+ xf.sub(`${name}:info`, data => {
+ console.log(data);
+ dom.name.textContent = `${data.name}`;
+ dom.model.textContent = `${data.modelNumberString}`;
+ dom.manufacturer.textContent = `${data.manufacturerNameString}`;
+ dom.firmware.textContent = `${data.firmwareRevisionString}`;
});
}
function GraphPower(args) {
- let dom = args.dom;
+ let dom = {
+ cont: q.get('#graph-power'),
+ graph: q.get('#graph-power .graph'),
+ // ftp: q.get('#ftp-line-value')
+ };
let ftp = 100;
let size = dom.cont.getBoundingClientRect().width;
let count = 0;
let scale = 400;
+ let workout = {};
+ let intervalIndex = 0;
+ let width = 1;
- xf.sub('db:ftp', e => {
- ftp = e.detail.data.ftp;
- dom.ftp.textContent = `FTP ${ftp}`;
+ xf.sub('db:ftp', x => {
+ // dom.ftp.textContent = `FTP ${x}`;
+ ftp = x;
});
- xf.sub('db:pwr', e => {
- let pwr = e.detail.data.pwr;
+
+ xf.reg('db:elapsed', db => {
+ let pwr = db.pwr;
let h = valueToHeight(scale, pwr);
- count += 1;
- if(count >= size) {
- dom.graph.removeChild(dom.graph.childNodes[0]);
- }
- dom.graph.insertAdjacentHTML('beforeend', `
`);
+ dom.graph.insertAdjacentHTML('beforeend', `
`);
+ });
+
+ // xf.sub('db:pwr', pwr => {
+ // let h = valueToHeight(scale, pwr);
+ // count += 1;
+ // if(count >= size) {
+ // dom.graph.removeChild(dom.graph.childNodes[0]);
+ // }
+ // dom.graph.insertAdjacentHTML('beforeend', `
`);
+ // });
+
+ xf.sub('watch:nextWorkoutInterval', index => {
+ dom.graph.innerHTML = '';
+ intervalIndex = index;
+ width = size / workout.intervals[intervalIndex].duration;
+ });
+
+ xf.sub('db:workout', x => {
+ workout = x;
});
}
@@ -153,8 +255,8 @@ function GraphHr(args) {
let count = 0;
let scale = 200;
let size = dom.cont.getBoundingClientRect().width;
- xf.sub('db:hr', e => {
- let hr = e.detail.data.hr;
+ xf.sub('db:hr', hr => {
+ // let hr = e.detail.data.hr;
let h = valueToHeight(scale, hr);
count += 1;
if(count >= size) {
@@ -214,7 +316,7 @@ function NavigationWidget(args) {
e.preventDefault();
e.stopImmediatePropagation();
- i = dom.homeBtn.getAttribute('date-index');
+ i = dom.homeBtn.getAttribute('data-index');
dom.homePage.style.display = 'block';
dom.settingsPage.style.display = 'none';
dom.workoutsPage.style.display = 'none';
@@ -234,7 +336,7 @@ function NavigationWidget(args) {
e.preventDefault();
e.stopImmediatePropagation();
- i = dom.settingsBtn.getAttribute('date-index');
+ i = dom.settingsBtn.getAttribute('data-index');
dom.settingsPage.style.display = 'block';
dom.homePage.style.display = 'none';
dom.workoutsPage.style.display = 'none';
@@ -254,7 +356,7 @@ function NavigationWidget(args) {
e.preventDefault();
e.stopImmediatePropagation();
- i = dom.workoutsBtn.getAttribute('date-index');
+ i = dom.workoutsBtn.getAttribute('data-index');
dom.workoutsPage.style.display = 'block';
dom.homePage.style.display = 'none';
dom.settingsPage.style.display = 'none';
@@ -276,12 +378,12 @@ function SettingsView(args) {
let ftp = 100;
let weight = 75;
- xf.sub('db:ftp', e => {
- ftp = e.detail.data.ftp;
+ xf.sub('db:ftp', ftp => {
+ // ftp = e.detail.data.ftp;
dom.ftp.value = ftp;
});
- xf.sub('db:weight', e => {
- weight = e.detail.data.weight;
+ xf.sub('db:weight', weight => {
+ // weight = e.detail.data.weight;
dom.weight.value = weight;
});
@@ -297,14 +399,136 @@ function SettingsView(args) {
}, dom.weightBtn);
}
+function NumberInput(args) {
+ let dom = args.dom;
+ let name = args.name || `number-input`;
+ let value = args.init || 0;
+ let type = args.type || 'Int';
+ let minValueSupported = args.min || 0;
+ let maxValueSupported = args.max || 0;
+ let incStep = args.inc || 1;
+ let set = args.set || function(x) { return x; };
+
+ const inRange = (target, minValueSupported, maxValueSupported, init = 0) => {
+ let value = init;
+ if(target >= maxValueSupported) {
+ value = maxValueSupported;
+ } else if(target < minValueSupported) {
+ value = minValueSupported;
+ } else {
+ value = target;
+ }
+ return value;
+ };
+
+ xf.sub('change', e => {
+ let x = 0;
+ if(type === 'Int') {
+ x = parseInt(e.target.value || 0);
+ } else {
+ x = parseFloat(e.target.value || 0);
+ }
+ if(x > minValueSupported && x < maxValueSupported) {
+ value = x;
+ }
+ if(x >= maxValueSupported) {
+ value = maxValueSupported;
+ }
+ if(x <= minValueSupported) {
+ value = minValueSupported;
+ }
+ dom.input.value = value;
+ set(value);
+ xf.dispatch(`ui:${name}-target`, value);
+ }, dom.input);
+
+ xf.sub('pointerup', e => {
+ let target = value + incStep;
+ value = inRange(target, minValueSupported, maxValueSupported, value);
+ dom.input.value = value;
+ set(value);
+ xf.dispatch(`ui:${name}-target`, value);
+ }, dom.incBtn);
+
+ xf.sub('pointerup', e => {
+ let target = value - incStep;
+ value = inRange(target, minValueSupported, maxValueSupported, value);
+ dom.input.value = value;
+ set(value);
+ xf.dispatch(`ui:${name}-target`, value);
+ }, dom.decBtn);
+}
+
function ControlView(args) {
- let dom = args.dom;
+ let dom = {
+ resistanceModeBtn: q.get('#resistance-mode-btn'),
+ slopeModeBtn: q.get('#slope-mode-btn'),
+ ergModeBtn: q.get('#erg-mode-btn'),
+
+ resistanceControls: q.get('#resistance-mode-controls'),
+ slopeControls: q.get('#slope-mode-controls'),
+ ergControls: q.get('#erg-mode-controls'),
+
+ resistanceParams: q.get('#resistance-mode-params'),
+ slopeParams: q.get('#slope-mode-params'),
+ ergParams: q.get('#erg-mode-params'),
+
+ resistanceValue: q.get('#resistance-value'),
+ resistanceInc: q.get('#resistance-inc'),
+ resistanceDec: q.get('#resistance-dec'),
+
+ slopeValue: q.get('#slope-value'),
+ slopeInc: q.get('#slope-inc'),
+ slopeDec: q.get('#slope-dec'),
+
+ targetPower: q.get('#target-power-value'),
+ workPower: q.get('#work-power-value'),
+ restPower: q.get('#rest-power-value'),
+ setTargetPower: q.get('#set-target-power'),
+ startWorkInterval: q.get('#start-work-interval'),
+ startRestInterval: q.get('#start-rest-interval'),
+ };
- xf.sub('db:controllableFeatures', e => {
- let features = e.detail.data.controllableFeatures;
- Resistance(features);
- Slope(features);
- ERG(features);
+ xf.sub('pointerup', e => {
+ xf.dispatch('ui:erg-mode');
+ dom.ergModeBtn.classList.add('active');
+ dom.resistanceModeBtn.classList.remove('active');
+ dom.slopeModeBtn.classList.remove('active');
+ dom.ergControls.style.display = 'block';
+ dom.resistanceControls.style.display = 'none';
+ dom.slopeControls.style.display = 'none';
+ }, dom.ergModeBtn);
+
+ xf.sub('pointerup', e => {
+ xf.dispatch('ui:resistance-mode');
+ dom.ergModeBtn.classList.remove('active');
+ dom.resistanceModeBtn.classList.add('active');
+ dom.slopeModeBtn.classList.remove('active');
+ dom.ergControls.style.display = 'none';
+ dom.resistanceControls.style.display = 'block';
+ dom.slopeControls.style.display = 'none';
+ }, dom.resistanceModeBtn);
+
+ xf.sub('pointerup', e => {
+ xf.dispatch('ui:slope-mode');
+ dom.ergModeBtn.classList.remove('active');
+ dom.resistanceModeBtn.classList.remove('active');
+ dom.slopeModeBtn.classList.add('active');
+ dom.ergControls.style.display = 'none';
+ dom.resistanceControls.style.display = 'none';
+ dom.slopeControls.style.display = 'block';
+ }, dom.slopeModeBtn);
+
+
+ // ERG({power: {params: {min: 0, max: 800, inc: 1}}});
+ // Slope({slope: {params: {min: 0, max: 30, inc: 0.5}}});
+ // Resistance({resistance: {params: {min: 0, max: 100, inc: 1}}});
+
+ xf.sub('db:controllableFeatures', controllableFeatures => {
+ let features = controllableFeatures;
+ Resistance(features); // will overflow max value
+ Slope(features); // speed based, will ignore max value
+ ERG(features); // will ignore max value (most likely)
});
function Resistance(features) {
@@ -316,113 +540,48 @@ function ControlView(args) {
dom.resistanceParams.textContent = `${minResistanceSupported} to ${maxResistanceSupported}`;
- xf.sub('change', e => {
- let value = parseInt(e.target.value || 0);
- if(value <= minResistanceSupported) {
- resistance = minResistanceSupported;
- dom.resistanceValue.value = resistance;
- }
- if(value >= maxResistanceSupported) {
- resistance = maxResistanceSupported;
- dom.resistanceValue.value = resistance;
- }
- if(value >= minResistanceSupported && value < maxResistanceSupported) {
- resistance = value;
- }
- xf.dispatch('ui:resistance-target', resistance);
- }, dom.resistanceValue);
-
- xf.sub('pointerup', e => {
- let target = resistance + resistanceInc;
- if(target >= maxResistanceSupported) {
- resistance = maxResistanceSupported;
- } else if(target < minResistanceSupported) {
- resistance = minResistanceSupported;
- } else {
- resistance = target;
- }
- dom.resistanceValue.value = resistance;
- xf.dispatch('ui:resistance-target', resistance);
- }, dom.resistanceInc);
-
- xf.sub('pointerup', e => {
- let target = resistance - resistanceInc;
- if(target >= maxResistanceSupported) {
- resistance = maxResistanceSupported;
- } else if(target < 0) {
- resistance = minResistanceSupported;
- } else {
- resistance = target;
- }
- dom.resistanceValue.value = resistance;
- xf.dispatch('ui:resistance-target', resistance);
- }, dom.resistanceDec);
+ NumberInput({dom: {input: dom.resistanceValue,
+ incBtn: dom.resistanceInc,
+ decBtn: dom.resistanceDec},
+ name: 'resistance',
+ init: resistance,
+ type: 'Int',
+ min: minResistanceSupported,
+ max: maxResistanceSupported,
+ inc: resistanceInc});
}
function Slope(features) {
// Slope mode
- // Waiting on:
- // https://stackoverflow.com/questions/65257156/what-is-the-supported-range-of-indoor-bike-simulation-parameters-in-the-ftms-spe
let slope = 0;
- let minSlopeSupported = 0; //-10;
- let maxSlopeSupported = 14.0; // change it
+ let minSlopeSupported = 0;
+ let maxSlopeSupported = 30.0; // maybe ... it is speed dependant
let slopeInc = 0.5;
dom.slopeParams.textContent = `${minSlopeSupported} to ${maxSlopeSupported}`;
- xf.sub('change', e => {
- let value = parseFloat(e.target.value || 0);
- if(value > minSlopeSupported && value < maxSlopeSupported) {
- slope = value;
- }
- if(value >= maxSlopeSupported) {
- slope = maxSlopeSupported - 0;
- dom.slopeValue.value = slope;
- }
- if(value <= minSlopeSupported) {
- slope = minSlopeSupported + 0;
- dom.slopeValue.value = slope;
- }
+ xf.sub('ui:slope-mode', e => {
xf.dispatch('ui:slope-target', slope);
- }, dom.slopeValue);
-
- xf.sub('pointerup', e => {
- let target = slope + slopeInc;
- if(target >= maxSlopeSupported) {
- slope = maxSlopeSupported;
- } else if(target < minSlopeSupported) {
- slope = minSlopeSupported;
- } else {
- slope = target;
- }
- dom.slopeValue.value = slope;
- xf.dispatch('ui:slope-target', slope);
- }, dom.slopeInc);
-
- xf.sub('pointerup', e => {
- let target = slope - slopeInc;
- if(target >= maxSlopeSupported) {
- slope = maxSlopeSupported;
- } else if(target < 0) {
- slope = 0;
- } else {
- slope = target;
- }
- dom.slopeValue.value = slope;
- xf.dispatch('ui:slope-target', slope);
- }, dom.slopeDec);
-
- // xf.sub('pointerup', e => {
- // xf.dispatch('ui:slope-target', slope);
- // }, dom.slopeSet);
+ });
+
+ NumberInput({dom: {input: dom.slopeValue,
+ incBtn: dom.slopeInc,
+ decBtn: dom.slopeDec},
+ name: 'slope',
+ init: slope,
+ type: 'Float',
+ min: minSlopeSupported,
+ max: maxSlopeSupported,
+ inc: slopeInc,
+ set: value => slope = value});
}
// ERG mode
function ERG(features) {
- let targetPwr = 100;
- let workPwr = 235;
- let restPwr = 100;
+ let targetPwr = dom.targetPower.value || 100;
+ let workPwr = dom.workPower.value || 235;
+ let restPwr = dom.restPower || 100;
let minPowerSupported = features.power.params.min || 0;
let maxPowerSupported = features.power.params.max || 0;
@@ -430,7 +589,9 @@ function ControlView(args) {
dom.ergParams.textContent = `${minPowerSupported} to ${maxPowerSupported}`;
- xf.sub('change', e => { targetPwr = parseInt(e.target.value); }, dom.targetPower);
+ xf.sub('change', e => {
+ alert(`change targetPwr`);
+ targetPwr = parseInt(e.target.value); }, dom.targetPower);
xf.sub('change', e => { workPwr = parseInt(e.target.value); }, dom.workPower);
xf.sub('change', e => { restPwr = parseInt(e.target.value); }, dom.restPower);
@@ -451,7 +612,16 @@ function ControlView(args) {
}
function WatchView(args) {
- let dom = args.dom;
+ let dom = {
+ start: q.get('#watch-start'),
+ pause: q.get('#watch-pause'),
+ lap: q.get('#watch-lap'),
+ stop: q.get('#watch-stop'),
+ save: q.get('#activity-save'),
+ workout: q.get('#start-workout'),
+ cont: q.get('#watch'),
+ name: q.get('#workout-name'),
+ };
xf.sub('pointerup', e => xf.dispatch('ui:watchStart'), dom.start);
xf.sub('pointerup', e => xf.dispatch('ui:watchPause'), dom.pause);
@@ -621,6 +791,7 @@ export {
ControllableSettingsView,
HrbSettingsView,
DataScreen,
+ DataBar,
GraphHr,
GraphPower,
GraphWorkout,
diff --git a/workout.js b/workout.js
index 4f3822f..0dd5cc0 100644
--- a/workout.js
+++ b/workout.js
@@ -66,8 +66,9 @@ class StopWatch {
}
init() {
let self = this;
- xf.sub('db:workout', e => {
- self.workout = e.detail.data.workout.intervals;
+ xf.sub('db:workout', workout => {
+ // self.workout = e.detail.data.workout.intervals;
+ self.workout = workout.intervals;
});
}
start() {
@@ -143,7 +144,6 @@ class StopWatch {
self.stepTime = stepDuration;
self.workoutCurrentStepDuration = stepDuration;
-
xf.dispatch('watch:nextWorkoutStep', s);
xf.dispatch('watch:stepTime', stepDuration);
xf.dispatch('watch:step');
@@ -190,6 +190,11 @@ class StopWatch {
self.started = true;
}
}
+ restore(args) {
+ let self = this;
+ self.elapsed = args.elapsed;
+ self.resume();
+ }
stop () {
let self = this;
if(self.started) {
diff --git a/workouts/workouts.js b/workouts/workouts.js
index b8463c8..2da93ca 100644
--- a/workouts/workouts.js
+++ b/workouts/workouts.js
@@ -2,13 +2,13 @@ let workouts =
[
{ name: 'Dijon',
type: 'VO2 Max',
- description: '60/60s or 60 sec ON at 131% of FTP followed by 60 sec OFF. In 2 groups by 8 reps each.',
+ description: '60/60s or 60 sec ON at 121% of FTP followed by 60 sec OFF. In 2 groups by 8 reps each.',
duration: 57,
xml:
`
Marinov
Dijon
- 60/60s or 60 sec ON at 131% of FTP followed by 60 sec OFF. In 2 groups by 8 reps each.
+ 60/60s or 60 sec ON at 121% of FTP followed by 60 sec OFF. In 2 groups by 8 reps each.
bike
@@ -20,10 +20,10 @@ let workouts =
-
-
-
-
+
+
+
+
@@ -49,10 +49,10 @@ let workouts =
-
-
-
-
+
+
+
+
@@ -104,13 +104,13 @@ let workouts =
-
+
-
+
-
-
+
+
`
},
@@ -130,38 +130,36 @@ let workouts =
-
-
-
-
+
+
+
+
`
},
-{ name: 'Maple (flat)',
+{ name: 'Honey',
type: 'Sweet Spot',
description: '4 times 10 min Sweet Spot intervals with 5 min recovery in between.',
duration: 80,
xml:
`
Marinov
- Maple
+ Honey
4 times 10 min Sweet Spot intervals with 5 min recovery in between.
bike
-
-
-
-
-
-
-
+
+
+
+
+
`
},
{ name: 'Baguette',
type: 'Base',
description: 'The bread and butter of Endurance training with efforts in Zone 1 and 2.',
- duration: 120,
+ duration: 90,
xml:
`
Marinov
@@ -171,21 +169,17 @@ let workouts =
-
-
-
-
-
+
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
`
},
@@ -202,7 +196,7 @@ let workouts =
-
+
@@ -216,7 +210,7 @@ let workouts =
-
+
`
},
diff --git a/xf.js b/xf.js
index a17e866..0514e88 100644
--- a/xf.js
+++ b/xf.js
@@ -1,4 +1,7 @@
-let evt = name => value => new CustomEvent(name, {detail: {data: value}});
+const evt = name => value => new CustomEvent(name, {detail: {data: value}});
+const evtSource = name => name.split(':')[0];
+const evtProp = name => name.split(':')[1];
+const dbSource = name => name.startsWith('db');
function dispatch (name, value) {
document.dispatchEvent(evt(name)(value));
@@ -10,7 +13,10 @@ function sub(name, handler, el = false) {
if(el) {
el.addEventListener(name, e => handler(e));
} else {
- document.addEventListener(name, e => handler(e));
+ document.addEventListener(name, e => {
+ dbSource(name) ? handler(e.detail.data[evtProp(name)]) : handler(e.detail.data) ;
+ // handler(e);
+ });
}
};