From bd4ac45fabfce86610e9e407fd0353086123dd5b Mon Sep 17 00:00:00 2001 From: dvmarinoff Date: Fri, 8 Jan 2021 19:41:07 +0200 Subject: [PATCH] working IndexDB layer + refactoring + zoom level - working IndexDB Layer with promises - Power Graph shows the progress in the current interval - adding Connections section with Garmin IQ import page link - refactoring Views.js and Dom.js - xf.sub return the property and not event object, unless it is called with dom element. - NumberInput - Dom will be gradually moved to each View function. - Enhancing the white theme (it is still hidden) - Experimenting with many new design ideas, will keep them as Zoom Levels as in Bike Navs, but they are hidden for now. --- controllers.js | 40 +- css/flux.css | 200 +++++++++- db.js | 55 ++- dom.js | 102 +----- functions.js | 5 + images/connections/garmin-connect.jpg | Bin 0 -> 8674 bytes index.html | 180 ++++++--- index.js | 28 +- lock.js | 7 + session.js | 76 ++++ storage.js | 179 +++++---- test/mock.js | 14 +- views.js | 505 +++++++++++++++++--------- workout.js | 11 +- workouts/workouts.js | 80 ++-- xf.js | 10 +- 16 files changed, 1000 insertions(+), 492 deletions(-) create mode 100644 images/connections/garmin-connect.jpg create mode 100644 session.js diff --git a/controllers.js b/controllers.js index edff44f..74e70b4 100644 --- a/controllers.js +++ b/controllers.js @@ -9,18 +9,17 @@ function DeviceController(args) { let hrb = args.hrb; let watch = args.watch; - xf.sub('db:targetPwr', e => { - let targetPwr = e.detail.data.targetPwr; + xf.sub('db:targetPwr', targetPwr => { + // let targetPwr = e.detail.data.targetPwr; controllable.setTargetPower(targetPwr); }); - xf.sub('db:resistanceTarget', e => { - let resistance = e.detail.data.resistanceTarget; - // resistance *= 10; + xf.sub('db:resistanceTarget', resistanceTarget => { + let resistance = resistanceTarget; resistance = parseInt(resistance); controllable.setTargetResistanceLevel(resistance); }); - xf.sub('db:slopeTarget', e => { - let slope = e.detail.data.slopeTarget; + xf.sub('db:slopeTarget', slopeTarget => { + let slope = slopeTarget; slope *= 100; slope = parseInt(slope); controllable.setSimulationParameters({grade: slope}); @@ -29,7 +28,28 @@ function DeviceController(args) { xf.sub('ui:watchPause', e => { watch.pause(); }); xf.sub('ui:watchResume', e => { watch.resume(); }); xf.sub('ui:watchLap', e => { watch.lap(); }); - xf.sub('ui:watchStop', e => { watch.stop(); }); + xf.sub('ui:watchStop', e => { + const stop = confirm('Confirm Stop?'); + if(stop) { + watch.stop(); + } + }); + + + xf.sub(`session:watchRestore`, session => { + watch.elapsed = session.elapsed; + watch.lapTime = session.lapTime; + watch.stepTime = session.stepTime; + + if(session.watchState === 'started') { + watch.workoutStarted = true; + xf.dispatch('ui:watchStart'); + } + if(session.watchState === 'paused') { + watch.workoutStarted = true; + } + + }); xf.sub('ui:controllableSwitch', e => { if(controllable.device.connected) { @@ -81,8 +101,8 @@ function Screen() { function FileController() { - xf.sub('db:workoutFile', e => { - let workoutFile = e.detail.data.workoutFile; + xf.sub('db:workoutFile', workoutFile => { + // let workoutFile = e.detail.data.workoutFile; let fileHandler = new FileHandler(); fileHandler.readFile(workoutFile); }); diff --git a/css/flux.css b/css/flux.css index faf2e3c..ca04a92 100644 --- a/css/flux.css +++ b/css/flux.css @@ -211,7 +211,11 @@ body.white-theme { } .white-theme .devices { background-color: var(--white); - box-shadow: 0px 1px 1px 0px var(--black-opacity-01); + + /* color: var(--white); */ + /* background-color: var(--chrome); */ + + /* box-shadow: 0px 1px 1px 0px var(--black-opacity-01); */ } .white-theme .menu-cont { background-color: var(--white); @@ -260,6 +264,12 @@ body.white-theme { .white-theme #progress-active { background-color: var(--black-opacity-02); } +.white-theme .a { + color: var(--gray); +} +.white-theme .mode.active { + color: var(--dark); +} /* Dark Theme */ body.dark-theme { @@ -286,6 +296,9 @@ body.dark-theme { .dark-theme .workout { border-bottom: 1px solid var(--white-opacity-01); } +.dark-theme .mode.active { + color: #fff; +} .icon-btn .icon { fill: var(--gray); @@ -361,6 +374,7 @@ body.dark-theme { justify-content: center; align-items: center; align-content: stretch; + wrap: wrap; z-index: 2; } .menu-cont.active { @@ -453,8 +467,7 @@ body.dark-theme { } .a { - color: #121212; - text-decoration: none; + color: #bbb; text-transform: uppercase; cursor: pointer; } @@ -515,17 +528,28 @@ body.dark-theme { .connection-screen { } +/* Zoom Level */ +.zoom-level-one { + /* display: none; */ +} -/* Data Screen */ -.data-screen { +.zoom-level-two { + display: none; +} + +/* Data Tiles */ +.data-tiles { width: 100%; } .data-tile { float: left; width: 33.3333%; + min-width: 30px; padding: 10px; text-align: center; } +.tile-heading { +} .tile-value { display: flex; justify-content: center; @@ -539,6 +563,106 @@ body.dark-theme { text-align: center; } +/* Data Bar */ +.data-bar { + display: flex; + /* display: none; */ + justify-content: center; + align-items: center; + position: relative; + padding: 10px 0px; + height: 150px; + color: var(--white); + /* background-color: #aaa; */ + background-color: var(--dark); + /* background-color: var(--chrome); */ + z-index: 0; +} +.data-bar-progress-cont { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: flex-end; + position: absolute; + left: 0px; + bottom: 0px; + padding: 0; + /* background-color: var(--zone-gray); */ + /* background-color: var(--zone-blue); */ + /* background-color: var(--zone-green); */ + /* background-color: var(--zone-yellow); */ + /* background-color: var(--zone-orange); */ + /* background-color: var(--zone-red); */ + z-index: 1; +} + +.data-bar-left { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 50%; + padding: 0px 0px 0px 0px; + z-index: 2; +} +.data-bar-right { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 50%; + padding: 0px 0px 0px 0px; + z-index: 2; +} +.data-bar-power { + /* width: 80px; */ + /* text-align: right; */ + text-align: center; + /* margin-bottom: 35px; */ +} +.data-bar-cadence { + /* width: 60px; */ + /* text-align: right; */ + text-align: center; + /* margin-bottom: 35px; */ +} +.data-bar-heading { + /* text-align: right; */ + width: 100%; + margin: 0 0 15px 0; + /* font-size: 18px; */ + font-size: 12px; + /* font-weight: bold; */ + text-transform: uppercase; +} +.data-bar-value { + /* width: 80px; */ + width: 100%; + text-align: right; + font-size: 48px; + font-weight: bold; + text-transform: uppercase; +} +.data-bar-target-heading { + font-size: 14px; + font-weight: bold; + text-transform: uppercase; + margin: 0 0 10px 0; +} +.data-bar-target-value { + font-size: 14px; + font-weight: bold; + text-transform: uppercase; +} + + /* Controls */ #controls { @@ -618,24 +742,25 @@ body.dark-theme { } .mode { padding: 0px 20px; - /* border: 1px solid rgba(255,255,255, 0.1); */ - color: #aaa; + color: var(--gray); + cursor: pointer; } .mode.active { - color: #fff; - /* border-bottom: 1px solid rgba(255,255,255, 0.9); */ + font-weight: bold; } .resistance-mode { + display: none; margin-bottom: 20px; } .slope-mode { - margin-bottom: 20px; + display: none; + margin-bottom: 50px; } .erg-mode { - margin-bottom: 20px; + margin-bottom: 50px; } .mode-controls { - margin-bottom: 20px; + margin-bottom: 50px; } .mode-controls header { margin: 0px auto 5px; @@ -759,6 +884,7 @@ body.dark-theme { /* Graph */ +.graphs {} .graph-screen { position: relative; height: 120px; @@ -1082,13 +1208,53 @@ body.dark-theme { font-weight: bold; } +.connections { + margin: 30px auto; +} +.connections h2 { + text-align: center; +} +.connection { + display: flex; + max-width: 480px; + margin: 0 auto; + align-items: center; + /* justify-content: center; */ + padding: 10px 10px; +} +.connection h3 {} +.connection-icon { + width: 40px; + height: 40px; + margin-right: 10px; +} +.connection-content {} + @media all and (min-width: 1800px) { } @media all and (min-width: 1600px) { } @media all and (max-width: 1250px) { } -@media all and (max-width: 980px) { +@media all and (max-width: 1024px) { + .control-screen { + height: 50px; + bottom: 49px; + } + .watch { + /* padding: 5px 0 5px; */ + padding: 10px 5px; + } + .control-btn { + margin: 0 14px; + } + .menu { + height: 50px; + /* border-top: 1px solid rgba(0,0,0,.4); */ + } + .tab-btn { + height: 50px; + } } @media all and (max-width: 768px) { } @@ -1119,12 +1285,12 @@ body.dark-theme { font-size: 10px; } - .control-screen { - height: 40px; - } + /* .control-screen { */ + /* height: 40px; */ + /* } */ .watch { /* margin: 10px 0 14px; */ - padding: 9px 5px; + /* padding: 9px 5px; */ } .devices { padding: 10px 10px 10px 10px; diff --git a/db.js b/db.js index 26d9d09..39fbb73 100644 --- a/db.js +++ b/db.js @@ -3,6 +3,8 @@ import { avgOfArray, maxOfArray, sum, mps, kph, timeDiff } from './functions.js'; import { Encode } from './fit.js'; import { FileHandler } from './file.js'; +import { IDB, Storage } from './storage.js'; +import { Session } from './session.js'; import { xf, DB } from './xf.js'; let db = DB({ @@ -24,6 +26,7 @@ let db = DB({ lapStartTime: Date.now(), workoutIntervalIndex: 0, timestamp: Date.now(), + watchState: 'stopped', inProgress: false, ftp: 0, @@ -36,15 +39,22 @@ let db = DB({ points: [], controllableFeatures: {}, + garminImportUrl: 'https://connect.garmin.com/modern/import-data', }); xf.reg('device:hr', x => db.hr = x); xf.reg('device:pwr', x => db.pwr = x); xf.reg('device:spd', x => db.spd = x); xf.reg('device:cad', x => db.cad = x); xf.reg('device:dist', x => db.distance = x); -xf.reg('watch:started', x => db.lapStartTime = Date.now()); +xf.reg('watch:started', x => { + db.lapStartTime = Date.now(); + db.watchState = 'started'; +}); +xf.reg('watch:paused', x => db.watchState = 'paused'); +xf.reg('watch:stopped', x => db.watchState = 'stopped'); xf.reg('watch:elapsed', x => db.elapsed = x); xf.reg('watch:lapTime', x => db.lapTime = x); +xf.reg('watch:stepTime', x => db.stepTime = x); xf.reg('ui:target-pwr', x => db.targetPwr = x); xf.reg('ui:ftp', x => db.ftp = x); xf.reg('storage:ftp', x => db.ftp = x); @@ -53,7 +63,7 @@ xf.reg('storage:weight', x => db.weight = x); xf.reg('ui:workoutFile', x => db.workoutFile = x); xf.reg('ui:workout:set', x => db.workout = db.workouts[x]); xf.reg('workout:add', x => db.workouts.push(x)); -xf.reg('watch:elapsed', watchTime => { +xf.reg('watch:elapsed', x => { db.distance += 1 * mps(db.spd); let record = { timestamp: Date.now(), power: db.pwr, @@ -89,11 +99,6 @@ xf.reg('watch:nextWorkoutStep', step => { db.workoutStepIndex = step; db.targetPwr = targetPwr; }); -xf.sub('ui:activity:save', x => { - let activity = Encode({data: db.records, laps: db.laps}); - let fileHandler = new FileHandler(); - fileHandler.downloadActivity(activity); -}); xf.reg('ui:resistance-target', x => db.resistanceTarget = x); xf.reg('ui:slope-target', x => db.slopeTarget = x); xf.reg('ui:tab', i => db.tab = i ); @@ -101,5 +106,41 @@ xf.reg('device:features', x => { console.log('controllable:features'); db.controllableFeatures = x; }); +xf.sub('ui:activity:save', x => { + let activity = Encode({data: db.records, laps: db.laps}); + let fileHandler = new FileHandler(); + fileHandler.downloadActivity(activity); +}); + + +// let storage = new Storage(); +let idb = new IDB(); +let session = {}; + +xf.reg('app:start', async function (x) { + await idb.open('store', 1, 'session'); + session = new Session({idb: idb}); + await session.restore(); + +}); + +xf.reg('lock:beforeunload', e => { + session.backup(idb, db); +}); +xf.reg('lock:release', e => { + session.backup(idb, db); +}); +xf.reg(`session:restore`, session => { + + for(let prop in session) { + if (session.hasOwnProperty(prop)) { + db[prop] = session[prop]; + } + } + // db.controllable = session.controllable; + // db.hrm = session.hrm; + console.log(session); +}); + export { db }; diff --git a/dom.js b/dom.js index d69995a..6e85293 100644 --- a/dom.js +++ b/dom.js @@ -1,86 +1,12 @@ import { q } from './q.js'; let dom = { - hrbConnectionScreen: { - switchBtn: q.get('#hrb-connection-btn'), - indicator: q.get('#hrb-connection-btn .indicator'), - }, - controllableConnectionScreen: { - switchBtn: q.get('#controllable-connection-btn'), - indicator: q.get('#controllable-connection-btn .indicator'), - }, - hrbSettings: { - 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'), - }, - controllableSettings: { - 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'), - }, - datascreen: { - 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') - }, - watch: { - start: q.get('#watch-start'), - pause: q.get('#watch-pause'), - // resume: q.get('#watch-resume'), - 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'), - }, - controls: { - resistanceMode: q.get('#resistance-mode-btn'), - slopeMode: q.get('#slope-mode-btn'), - ergMode: q.get('#erg-mode-btn'), - freeMode: q.get('#free-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'), - // resistanceSet: q.get('#resistance-set'), - slopeValue: q.get('#slope-value'), - slopeInc: q.get('#slope-inc'), - slopeDec: q.get('#slope-dec'), - // slopeSet: q.get('#slope-set'), - - - 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'), + graphWorkout: { + // progress: q.get('#progress'), + name: q.get('#current-workout-name'), + graph: q.get('#current-workout-graph'), + intervals: [], + steps: [], }, settings: { ftp: q.get('#ftp-value'), @@ -113,22 +39,6 @@ let dom = { activity: { saveBtn: q.get('#activity-save'), }, - graphWorkout: { - // progress: q.get('#progress'), - name: q.get('#current-workout-name'), - graph: q.get('#current-workout-graph'), - intervals: [], - steps: [], - }, - // graphHr: { - // cont: q.get('#graph-hr'), - // graph: q.get('#graph-hr .graph') - // }, - graphPower: { - cont: q.get('#graph-power'), - graph: q.get('#graph-power .graph'), - ftp: q.get('#ftp-line-value') - }, // recon: { // section: q.get('#recon-cont'), // cont: q.get('#recon-graph'), diff --git a/functions.js b/functions.js index 837bfec..658071b 100644 --- a/functions.js +++ b/functions.js @@ -12,11 +12,14 @@ let avg = (x, y) => (x + y) / 2; let last = xs => xs[xs.length - 1]; let first = xs => xs[0]; let second = xs => xs[1]; +let third = xs => xs[2]; let format = (x, precision = 1000) => round(x * precision) / precision; let mps = kph => format(kph / 3.6); let kph = mps => 3.6 * mps; let nextToLast = xs => xs[xs.length - 2]; +const rand = (min = 0, max = 10) => Math.floor(Math.random() * (max - min + 1) + min); + function avgOfArray(xs, prop = false) { if(prop !== false) { return xs.reduce( (acc,v,i) => acc+(v[prop]-acc)/(i+1), 0); @@ -178,11 +181,13 @@ export { mps, kph, avg, + rand, avgOfArray, maxOfArray, sum, first, second, + third, last, nextToLast, round, diff --git a/images/connections/garmin-connect.jpg b/images/connections/garmin-connect.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8efd912711d66c785ed32f98edb0c4334976ad39 GIT binary patch literal 8674 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 B*zW)U literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 038df92..97343e9 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,7 @@ -
+
-
-
-

Power

-
-
--
+ + +
+
+
+

Power

+
+
--
+
-
-
-

Interval Time

-
-
--:--
+
+

Interval Time

+
+
--:--
+
-
-
-

Heart Rate

-
-
--
+
+

Heart Rate

+
+
--
+
-
-
-

Target

-
-
--
+ +
+

Target

+
+
--
+
+
+
+

Elapsed Time

+
+
--:--:--
+
+
+
+

Cadence

+
+
--
+
-
-

Elapsed Time

-
-
--:--:--
+
+
+

Speed

+
+
--
+
+
+
+

Distance

+
+
--
+
-
-

Cadence

-
-
--
+ +
+ +
+
+

Power

+ +
+
+
-
-
-

Speed

-
-
--
+ + + +
+
+
+
+
+

Watt

+
--
+
-
-
-

Distance

-
-
--
+
+
+

RPM

+
--
+
-
-
-
-
-

Power

-
FTP
-
+
+
+

Target

+
+
--
+
+
+
+

Interval Time

+
+
--:--
+
+
+ +
+

Heart Rate

+
+
--
+
+ +
+ +
+

Elapsed Time

+
+
--:--:--
+
+
+ +
-
+ +
@@ -112,7 +170,6 @@

ERG
Resistance
Slope
-
Free
@@ -123,7 +180,7 @@

+ type="number" value="0" autocomplete="off" tabindex="-1"/>
@@ -137,7 +194,7 @@

+ type="number" value="0" autocomplete="off" tabindex="-1"/>
@@ -150,15 +207,15 @@

- +
- +
- +
@@ -273,9 +330,20 @@

Battery

+
+

Connections

+
+ GC + +
+
+
Free ride
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); + }); } };