incyclist-services 1.7.95 → 1.7.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/lib/cjs/ride/page/index.js +1 -0
  2. package/lib/cjs/ride/page/service.js +458 -124
  3. package/lib/cjs/workouts/ride/index.js +0 -1
  4. package/lib/esm/ride/page/index.js +1 -0
  5. package/lib/esm/ride/page/service.js +458 -124
  6. package/lib/esm/workouts/ride/index.js +0 -1
  7. package/lib/types/activities/ride/service.d.ts +1 -1
  8. package/lib/types/activities/ride/types.d.ts +1 -1
  9. package/lib/types/appstate/types.d.ts +1 -1
  10. package/lib/types/ride/page/index.d.ts +1 -0
  11. package/lib/types/ride/page/service.d.ts +69 -17
  12. package/lib/types/ride/page/types.d.ts +85 -8
  13. package/lib/types/workouts/ride/index.d.ts +0 -1
  14. package/lib/types/workouts/types.d.ts +0 -1
  15. package/package.json +1 -1
  16. package/lib/cjs/activities/upload/velohero.js +0 -136
  17. package/lib/cjs/apps/velohero/VeloHeroAppConnection.js +0 -232
  18. package/lib/cjs/apps/velohero/index.js +0 -18
  19. package/lib/cjs/apps/velohero/types.js +0 -2
  20. package/lib/cjs/workouts/list/pageService.js +0 -455
  21. package/lib/cjs/workouts/ride/page/index.js +0 -18
  22. package/lib/cjs/workouts/ride/page/service.js +0 -564
  23. package/lib/cjs/workouts/ride/page/types.js +0 -2
  24. package/lib/esm/activities/upload/trainingpeaks.js +0 -123
  25. package/lib/esm/activities/upload/velohero.js +0 -133
  26. package/lib/esm/apps/base/api/trainingpeaks/api.js +0 -143
  27. package/lib/esm/apps/base/api/trainingpeaks/index.js +0 -1
  28. package/lib/esm/apps/base/api/trainingpeaks/types.js +0 -1
  29. package/lib/esm/apps/trainingpeaks/TrainingPeaksAppConnection.js +0 -189
  30. package/lib/esm/apps/trainingpeaks/types.js +0 -1
  31. package/lib/esm/apps/velohero/VeloHeroAppConnection.js +0 -229
  32. package/lib/esm/apps/velohero/index.js +0 -2
  33. package/lib/esm/apps/velohero/types.js +0 -1
  34. package/lib/esm/workouts/calendar/sync/trainingpeaks/provider.js +0 -201
  35. package/lib/esm/workouts/list/pageService.js +0 -451
  36. package/lib/types/activities/upload/trainingpeaks.d.ts +0 -16
  37. package/lib/types/activities/upload/velohero.d.ts +0 -18
  38. package/lib/types/apps/base/api/trainingpeaks/api.d.ts +0 -30
  39. package/lib/types/apps/base/api/trainingpeaks/index.d.ts +0 -2
  40. package/lib/types/apps/base/api/trainingpeaks/types.d.ts +0 -55
  41. package/lib/types/apps/trainingpeaks/TrainingPeaksAppConnection.d.ts +0 -22
  42. package/lib/types/apps/trainingpeaks/types.d.ts +0 -6
  43. package/lib/types/apps/velohero/VeloHeroAppConnection.d.ts +0 -24
  44. package/lib/types/apps/velohero/index.d.ts +0 -2
  45. package/lib/types/apps/velohero/types.d.ts +0 -9
  46. package/lib/types/workouts/calendar/sync/trainingpeaks/provider.d.ts +0 -22
  47. package/lib/types/workouts/list/pageService.d.ts +0 -150
@@ -40,9 +40,16 @@ const decorators_1 = require("../../base/decorators");
40
40
  const pages_1 = require("../../base/pages");
41
41
  const display_1 = require("../display");
42
42
  const sleep_1 = require("../../utils/sleep");
43
- const api_1 = require("../../api");
44
43
  const monitoring_1 = require("../../monitoring");
44
+ const activities_1 = require("../../activities");
45
+ const settings_1 = require("../../settings");
46
+ const service_1 = require("../../workouts/ride/service");
47
+ const graph_1 = require("../../workouts/base/graph");
45
48
  const BACKGROUND_PAUSE_TIMEOUT_MS = 300000;
49
+ const UPCOMING_STEPS_COUNT = 3;
50
+ const DEFAULT_LOAD_INCREMENT = 1;
51
+ const HINTS_WORKOUT_GESTURES_KEY = 'hints.workoutRideGestures';
52
+ const LOAD_INCREMENT_SETTING_KEY = 'preferences.workouts.loadIncrement';
46
53
  let RidePageService = (() => {
47
54
  let _classDecorators = [decorators_1.Singleton];
48
55
  let _classDescriptor;
@@ -51,17 +58,23 @@ let RidePageService = (() => {
51
58
  let _classSuper = pages_1.IncyclistPageService;
52
59
  let _instanceExtraInitializers = [];
53
60
  let _getRideDisplay_decorators;
54
- let _getindings_decorators;
61
+ let _getWorkoutRide_decorators;
62
+ let _getActivityRide_decorators;
63
+ let _getUserSettings_decorators;
55
64
  let _getOnlineStatusMonitoring_decorators;
56
65
  var RidePageService = class extends _classSuper {
57
66
  static { _classThis = this; }
58
67
  static {
59
68
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
60
69
  _getRideDisplay_decorators = [decorators_1.Injectable];
61
- _getindings_decorators = [decorators_1.Injectable];
70
+ _getWorkoutRide_decorators = [decorators_1.Injectable];
71
+ _getActivityRide_decorators = [decorators_1.Injectable];
72
+ _getUserSettings_decorators = [decorators_1.Injectable];
62
73
  _getOnlineStatusMonitoring_decorators = [decorators_1.Injectable];
63
74
  __esDecorate(this, null, _getRideDisplay_decorators, { kind: "method", name: "getRideDisplay", static: false, private: false, access: { has: obj => "getRideDisplay" in obj, get: obj => obj.getRideDisplay }, metadata: _metadata }, null, _instanceExtraInitializers);
64
- __esDecorate(this, null, _getindings_decorators, { kind: "method", name: "getindings", static: false, private: false, access: { has: obj => "getindings" in obj, get: obj => obj.getindings }, metadata: _metadata }, null, _instanceExtraInitializers);
75
+ __esDecorate(this, null, _getWorkoutRide_decorators, { kind: "method", name: "getWorkoutRide", static: false, private: false, access: { has: obj => "getWorkoutRide" in obj, get: obj => obj.getWorkoutRide }, metadata: _metadata }, null, _instanceExtraInitializers);
76
+ __esDecorate(this, null, _getActivityRide_decorators, { kind: "method", name: "getActivityRide", static: false, private: false, access: { has: obj => "getActivityRide" in obj, get: obj => obj.getActivityRide }, metadata: _metadata }, null, _instanceExtraInitializers);
77
+ __esDecorate(this, null, _getUserSettings_decorators, { kind: "method", name: "getUserSettings", static: false, private: false, access: { has: obj => "getUserSettings" in obj, get: obj => obj.getUserSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
65
78
  __esDecorate(this, null, _getOnlineStatusMonitoring_decorators, { kind: "method", name: "getOnlineStatusMonitoring", static: false, private: false, access: { has: obj => "getOnlineStatusMonitoring" in obj, get: obj => obj.getOnlineStatusMonitoring }, metadata: _metadata }, null, _instanceExtraInitializers);
66
79
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
67
80
  RidePageService = _classThis = _classDescriptor.value;
@@ -69,15 +82,24 @@ let RidePageService = (() => {
69
82
  __runInitializers(_classThis, _classExtraInitializers);
70
83
  }
71
84
  eventHandler = (__runInitializers(this, _instanceExtraInitializers), {});
85
+ workoutEventHandler = {};
86
+ workoutObserverSubscribed = false;
72
87
  backgroundTimer;
73
88
  backgroundPausedByService = false;
74
89
  menuProps = null;
75
90
  isInitialized = false;
76
91
  startGateProps = null;
92
+ gestureHintDismissed = false;
77
93
  constructor() {
78
94
  super('RidePage');
79
95
  this.eventHandler['state-update'] = this.onDisplayStateUpdate.bind(this);
80
96
  this.eventHandler['route-update'] = this.onRouteUpdate.bind(this);
97
+ this.workoutEventHandler['step-changed'] = this.onWorkoutUpdate.bind(this);
98
+ this.workoutEventHandler['update'] = this.onWorkoutUpdate.bind(this);
99
+ this.workoutEventHandler['forward'] = this.onWorkoutUpdate.bind(this);
100
+ this.workoutEventHandler['backward'] = this.onWorkoutUpdate.bind(this);
101
+ this.workoutEventHandler['completed'] = this.onWorkoutFinished.bind(this);
102
+ this.workoutEventHandler['stopped'] = this.onWorkoutFinished.bind(this);
81
103
  }
82
104
  async initPage() {
83
105
  try {
@@ -92,19 +114,24 @@ let RidePageService = (() => {
92
114
  }
93
115
  openPage(simulate) {
94
116
  try {
95
- this.logEvent({ message: 'page shown', page: 'Rides' });
96
- gd_eventlog_1.EventLogger.setGlobalConfig('page', 'Rides');
117
+ const pageLogName = this.getPageLogName();
118
+ this.logEvent({ message: 'page shown', page: pageLogName });
119
+ gd_eventlog_1.EventLogger.setGlobalConfig('page', pageLogName);
120
+ this.gestureHintDismissed = false;
97
121
  super.openPage();
98
122
  try {
99
123
  const service = this.getRideDisplay();
100
124
  const registerAndStart = () => {
101
- this.registerEventHandlers();
125
+ this.registerHandlers(this.rideObserver, this.eventHandler);
126
+ this.subscribeToWorkoutObserver();
102
127
  service.start(simulate);
103
- (0, sleep_1.sleep)(5).then(() => {
104
- this.updatePageDisplay();
105
- });
128
+ if (this.isRideType('Video', 'GPX')) {
129
+ (0, sleep_1.sleep)(5).then(() => {
130
+ this.updatePageDisplay();
131
+ });
132
+ }
106
133
  };
107
- if (!this.isInitialized) {
134
+ if (this.requiresOwnInit() && !this.isInitialized) {
108
135
  service.init()
109
136
  .then(() => {
110
137
  this.isInitialized = true;
@@ -129,8 +156,10 @@ let RidePageService = (() => {
129
156
  closePage() {
130
157
  try {
131
158
  gd_eventlog_1.EventLogger.setGlobalConfig('page', null);
132
- this.logEvent({ message: 'page closed', page: 'Rides' });
159
+ this.logEvent({ message: 'page closed', page: this.getPageLogName() });
133
160
  this.getRideDisplay().stop();
161
+ this.unregisterHandlers(this.rideObserver, this.eventHandler);
162
+ this.unsubscribeFromWorkoutObserver();
134
163
  this.menuProps = null;
135
164
  this.isInitialized = false;
136
165
  super.closePage();
@@ -142,7 +171,7 @@ let RidePageService = (() => {
142
171
  async pausePage() {
143
172
  try {
144
173
  this.backgroundTimer = setTimeout(() => {
145
- this.getRideDisplay().pause('user');
174
+ this.getRideDisplay().pause(this.getBackgroundPauseRequester());
146
175
  this.backgroundPausedByService = true;
147
176
  }, BACKGROUND_PAUSE_TIMEOUT_MS);
148
177
  this.isInitialized = false;
@@ -166,63 +195,13 @@ let RidePageService = (() => {
166
195
  getRideObserver() {
167
196
  return this.rideObserver ?? null;
168
197
  }
169
- getPageDisplayProps() {
170
- const startOverlayProps = this.getRideDisplay().getStartOverlayProps();
171
- const noRideProps = {
172
- rideState: 'Error',
173
- startOverlayProps,
174
- rideType: null,
175
- menuProps: null,
176
- startGateProps: null
177
- };
178
- try {
179
- const rideType = this.getRideDisplay().getRideType();
180
- switch (rideType) {
181
- case 'Video': return this.getVideoRideDisplayProps();
182
- case 'GPX': return this.getGPXRideDisplayProps();
183
- default:
184
- return noRideProps;
185
- }
186
- }
187
- catch {
188
- return noRideProps;
189
- }
190
- }
191
- onRefreshSecrets() {
192
- this.hideStartgate();
193
- }
194
- onContinueAnyway() {
195
- this.hideStartgate();
196
- }
197
- onMenuOpen() {
198
- try {
199
- const state = this.getRideDisplay().getState();
200
- this.menuProps = { showResume: state === 'Paused' };
201
- this.updatePageDisplay();
202
- }
203
- catch (err) {
204
- this.logError(err, 'onMenuOpen');
205
- }
206
- }
207
- onMenuClose() {
208
- try {
209
- const state = this.getRideDisplay().getState();
210
- if (state === 'Finished' || this.menuProps?.finished) {
211
- this.moveToPreviousPage();
212
- this.closePage();
213
- return;
214
- }
215
- this.menuProps = null;
216
- this.updatePageDisplay();
217
- }
218
- catch (err) {
219
- this.logError(err, 'onMenuClose');
220
- }
198
+ getRideType() {
199
+ return this.getRideDisplay().getRideType();
221
200
  }
222
201
  onPause() {
223
202
  try {
224
203
  this.getRideDisplay().pause('user');
225
- this.menuProps = { showResume: true };
204
+ this.menuProps = this.buildPausedMenuProps();
226
205
  this.updatePageDisplay();
227
206
  }
228
207
  catch (err) {
@@ -239,25 +218,6 @@ let RidePageService = (() => {
239
218
  this.logError(err, 'onResume');
240
219
  }
241
220
  }
242
- onFinished() {
243
- try {
244
- this.menuProps = { showResume: false, finished: true };
245
- this.updatePageDisplay();
246
- }
247
- catch (err) {
248
- this.logError(err, 'onResume');
249
- }
250
- }
251
- onEndRide() {
252
- try {
253
- this.getRideDisplay().stop();
254
- this.moveToPreviousPage();
255
- this.closePage();
256
- }
257
- catch (err) {
258
- this.logError(err, 'onEndRide');
259
- }
260
- }
261
221
  onRetryStart() {
262
222
  try {
263
223
  this.getRideDisplay().retryStart();
@@ -288,17 +248,81 @@ let RidePageService = (() => {
288
248
  this.logError(err, 'onCancelStart');
289
249
  }
290
250
  }
251
+ onMenuOpen() {
252
+ try {
253
+ const state = this.getRideDisplay().getState();
254
+ this.menuProps = this.isRideType('Workout')
255
+ ? { showResume: state === 'Paused', ...this.getStepFlags() }
256
+ : { showResume: state === 'Paused' };
257
+ this.updatePageDisplay();
258
+ }
259
+ catch (err) {
260
+ this.logError(err, 'onMenuOpen');
261
+ }
262
+ }
263
+ onMenuClose() {
264
+ try {
265
+ if (this.isRideType('Workout')) {
266
+ this.menuProps = null;
267
+ this.updatePageDisplay();
268
+ return;
269
+ }
270
+ const state = this.getRideDisplay().getState();
271
+ if (state === 'Finished' || this.menuProps?.finished) {
272
+ this.moveToPreviousPage();
273
+ this.closePage();
274
+ return;
275
+ }
276
+ this.menuProps = null;
277
+ this.updatePageDisplay();
278
+ }
279
+ catch (err) {
280
+ this.logError(err, 'onMenuClose');
281
+ }
282
+ }
283
+ getPageDisplayProps() {
284
+ const startOverlayProps = this.getRideDisplay().getStartOverlayProps();
285
+ const noRideProps = {
286
+ rideState: 'Error',
287
+ startOverlayProps,
288
+ rideType: null,
289
+ menuProps: null,
290
+ startGateProps: null
291
+ };
292
+ try {
293
+ const rideType = this.getRideDisplay().getRideType();
294
+ switch (rideType) {
295
+ case 'Video': return this.getVideoRideDisplayProps();
296
+ case 'GPX': return this.getGPXRideDisplayProps();
297
+ case 'Workout': return this.getWorkoutRideDisplayProps();
298
+ default:
299
+ return noRideProps;
300
+ }
301
+ }
302
+ catch {
303
+ return noRideProps;
304
+ }
305
+ }
306
+ onRefreshSecrets() {
307
+ this.hideStartgate();
308
+ }
309
+ onContinueAnyway() {
310
+ this.hideStartgate();
311
+ }
312
+ onEndRide() {
313
+ try {
314
+ this.getRideDisplay().stop();
315
+ this.moveToPreviousPage();
316
+ this.closePage();
317
+ }
318
+ catch (err) {
319
+ this.logError(err, 'onEndRide');
320
+ }
321
+ }
291
322
  getVideoRideDisplayProps() {
292
323
  const props = this.rideDisplayProps;
293
- const state = this.getRideDisplay().getState();
294
- const rideType = this.getRideDisplay().getRideType();
295
- const isStarting = state === 'Idle' || state === 'Starting' || state === 'Error';
296
324
  const displayProps = {
297
- rideState: state,
298
- rideType,
299
- startGateProps: this.startGateProps,
300
- startOverlayProps: isStarting ? this.getRideDisplay().getStartOverlayProps() : null,
301
- menuProps: this.menuProps,
325
+ ...this.buildBaseDisplayProps(),
302
326
  video: props.video,
303
327
  videos: props.videos,
304
328
  route: props.route
@@ -307,15 +331,8 @@ let RidePageService = (() => {
307
331
  }
308
332
  getGPXRideDisplayProps() {
309
333
  const props = this.rideDisplayProps;
310
- const state = this.getRideDisplay().getState();
311
- const rideType = this.getRideDisplay().getRideType();
312
- const isStarting = state === 'Idle' || state === 'Starting' || state === 'Error';
313
334
  const displayProps = {
314
- rideState: state,
315
- rideType,
316
- startGateProps: this.startGateProps,
317
- startOverlayProps: isStarting ? this.getRideDisplay().getStartOverlayProps() : null,
318
- menuProps: this.menuProps,
335
+ ...this.buildBaseDisplayProps(),
319
336
  rideView: props.rideView,
320
337
  route: props.route,
321
338
  displayObserver: props.displayObserver
@@ -342,36 +359,351 @@ let RidePageService = (() => {
342
359
  this.startGateProps = null;
343
360
  this.updatePageDisplay();
344
361
  }
345
- getRideType() {
346
- return this.getRideDisplay().getRideType();
362
+ onRouteUpdate() {
363
+ this.updatePageDisplay();
347
364
  }
348
- updatePageDisplay() {
349
- this.getPageObserver()?.emit('page-update');
365
+ get rideDisplayProps() {
366
+ return this.getRideDisplay().getDisplayProperties();
367
+ }
368
+ getSecretBinding() {
369
+ return this.getBindings().secret;
370
+ }
371
+ getGraphActuals() {
372
+ try {
373
+ const state = this.getRideDisplay().getState();
374
+ if (state === 'Idle' || state === 'Starting' || state === 'Started') {
375
+ return { power: [], heartrate: [], position: 0 };
376
+ }
377
+ const logs = this.getActivityRide().getActivity()?.logs ?? [];
378
+ const power = [];
379
+ const heartrate = [];
380
+ logs.forEach(log => {
381
+ if (log.power !== undefined)
382
+ power.push({ x: log.time, y: log.power });
383
+ if (log.heartrate !== undefined)
384
+ heartrate.push({ x: log.time, y: log.heartrate });
385
+ });
386
+ return { power, heartrate, position: this.getElapsedActivityTime() };
387
+ }
388
+ catch (err) {
389
+ this.logError(err, 'getGraphActuals');
390
+ return { power: [], heartrate: [], position: 0 };
391
+ }
392
+ }
393
+ onStop() {
394
+ try {
395
+ this.finishRide();
396
+ }
397
+ catch (err) {
398
+ this.logError(err, 'onStop');
399
+ }
400
+ }
401
+ onStepBack() {
402
+ try {
403
+ this.getRideDisplay().backward();
404
+ }
405
+ catch (err) {
406
+ this.logError(err, 'onStepBack');
407
+ }
408
+ }
409
+ onStepForward() {
410
+ try {
411
+ this.getRideDisplay().forward();
412
+ }
413
+ catch (err) {
414
+ this.logError(err, 'onStepForward');
415
+ }
416
+ }
417
+ onIncreaseLoad() {
418
+ this.adjustLoad(this.getLoadIncrement());
350
419
  }
351
- registerEventHandlers() {
352
- const events = Object.keys(this.eventHandler);
353
- events.forEach(event => { this.rideObserver?.on(event, this.eventHandler[event]); });
420
+ onDecreaseLoad() {
421
+ this.adjustLoad(-this.getLoadIncrement());
422
+ }
423
+ onSetLoadIncrement(value) {
424
+ try {
425
+ this.getUserSettings().set(LOAD_INCREMENT_SETTING_KEY, value);
426
+ this.updatePageDisplay();
427
+ }
428
+ catch (err) {
429
+ this.logError(err, 'onSetLoadIncrement');
430
+ }
354
431
  }
355
- unregisterEventHandlers() {
356
- const events = Object.keys(this.eventHandler);
357
- events.forEach(event => { this.rideObserver?.off(event, this.eventHandler[event]); });
432
+ onGestureHintDismissed({ dontShowAgain }) {
433
+ try {
434
+ this.gestureHintDismissed = true;
435
+ if (dontShowAgain) {
436
+ this.getUserSettings().set(HINTS_WORKOUT_GESTURES_KEY, true);
437
+ }
438
+ this.updatePageDisplay();
439
+ }
440
+ catch (err) {
441
+ this.logError(err, 'onGestureHintDismissed');
442
+ }
443
+ }
444
+ adjustLoad(deltaPct) {
445
+ try {
446
+ if (deltaPct >= 0)
447
+ return this.getWorkoutRide().powerUp(deltaPct);
448
+ else
449
+ return this.getWorkoutRide().powerDown(-deltaPct);
450
+ }
451
+ catch (err) {
452
+ this.logError(err, 'adjustLoad');
453
+ return undefined;
454
+ }
455
+ }
456
+ getLoadButtonMode() {
457
+ try {
458
+ return this.getWorkoutRide().getLoadButtonMode();
459
+ }
460
+ catch (err) {
461
+ this.logError(err, 'getLoadButtonMode');
462
+ return 'power';
463
+ }
464
+ }
465
+ getWorkoutRideDisplayProps() {
466
+ try {
467
+ const rideType = this.getRideDisplay().getRideType();
468
+ if (rideType !== 'Workout') {
469
+ this.logError(new Error(`unexpected ride type '${rideType}' for WorkoutRidePage`), 'getWorkoutRideDisplayProps');
470
+ return this.getEmptyWorkoutDisplayProps();
471
+ }
472
+ const base = this.buildBaseDisplayProps();
473
+ const wo = this.getWorkoutRide().getDashboardDisplayProperties();
474
+ const current = this.getRideDisplay().getDisplayProperties().workout;
475
+ return {
476
+ ...base,
477
+ menuProps: base.menuProps,
478
+ title: wo.title ?? '',
479
+ graph: this.buildGraphPlan(current, wo.ftp),
480
+ steps: this.buildUpcomingSteps(current, wo.ftp),
481
+ dashboard: this.buildDashboardLine(wo),
482
+ gestureHint: this.buildGestureHint(base.startOverlayProps === null),
483
+ loadIncrement: this.getLoadIncrement(),
484
+ loadButtonMode: wo.loadButtonMode ?? 'power'
485
+ };
486
+ }
487
+ catch (err) {
488
+ this.logError(err, 'getWorkoutRideDisplayProps');
489
+ return this.getEmptyWorkoutDisplayProps();
490
+ }
491
+ }
492
+ onWorkoutUpdate() {
493
+ this.updatePageDisplay();
494
+ }
495
+ onWorkoutFinished() {
496
+ try {
497
+ this.menuProps = this.buildFinishedMenuProps();
498
+ this.updatePageDisplay();
499
+ }
500
+ catch (err) {
501
+ this.logError(err, 'onWorkoutFinished');
502
+ }
503
+ }
504
+ finishRide() {
505
+ this.getRideDisplay().stop(true);
506
+ this.onWorkoutFinished();
507
+ }
508
+ subscribeToWorkoutObserver() {
509
+ if (!this.isRideType('Workout') || this.workoutObserverSubscribed)
510
+ return;
511
+ const observer = this.getWorkoutRide().getObserver();
512
+ if (!observer)
513
+ return;
514
+ this.registerHandlers(observer, this.workoutEventHandler);
515
+ this.workoutObserverSubscribed = true;
516
+ }
517
+ unsubscribeFromWorkoutObserver() {
518
+ if (!this.workoutObserverSubscribed)
519
+ return;
520
+ this.unregisterHandlers(this.getWorkoutRide().getObserver(), this.workoutEventHandler);
521
+ this.workoutObserverSubscribed = false;
522
+ }
523
+ buildGestureHint(startOverlayCleared) {
524
+ if (this.gestureHintDismissed || !startOverlayCleared)
525
+ return null;
526
+ if (this.getElapsedActivityTime() !== 0 || this.getCurrentCadence() !== 0)
527
+ return null;
528
+ if (this.getUserSettings().get(HINTS_WORKOUT_GESTURES_KEY, false))
529
+ return null;
530
+ return { visible: true };
531
+ }
532
+ getCurrentCadence() {
533
+ return this.getActivityRide().getCurrentValues?.()?.cadence ?? 0;
534
+ }
535
+ getLoadIncrement() {
536
+ return this.getUserSettings().get(LOAD_INCREMENT_SETTING_KEY, DEFAULT_LOAD_INCREMENT);
537
+ }
538
+ buildGraphPlan(current, ftp) {
539
+ if (!current) {
540
+ return { bars: [], ftp: ftp ?? 0, ftpLine: ftp ?? 0, domain: { x: [0, 0], y: [0, 0] } };
541
+ }
542
+ const bars = (0, graph_1.getWorkoutGraphSeries)(current, { ftp, absValues: true });
543
+ const lastBarX = bars.length ? bars.at(-1).x : 0;
544
+ const maxXCandidates = [this.getLastLogTime(), lastBarX, current.duration ?? 0].filter(Number.isFinite);
545
+ const maxX = maxXCandidates.length ? Math.max(...maxXCandidates) : 0;
546
+ const maxBarPower = bars.length ? Math.max(...bars.map(b => b.y)) : 0;
547
+ return {
548
+ bars,
549
+ ftp,
550
+ ftpLine: ftp,
551
+ domain: { x: [0, maxX], y: [0, maxBarPower * 1.1] }
552
+ };
553
+ }
554
+ buildUpcomingSteps(current, ftp) {
555
+ const empty = { previous: null, current: null, upcoming: [], hasMore: false };
556
+ if (!current)
557
+ return empty;
558
+ const limits = this.getWorkoutRide().getCurrentLimits();
559
+ if (!limits)
560
+ return empty;
561
+ const currentStep = {
562
+ label: (0, graph_1.getStepTargetText)(limits.step ?? {}, ftp),
563
+ targetPower: limits.targetPower ?? null,
564
+ duration: limits.duration,
565
+ remaining: limits.remaining,
566
+ isCurrent: true
567
+ };
568
+ const elapsedTime = this.getElapsedActivityTime();
569
+ const flattened = (0, graph_1.getFlattenedSteps)(current);
570
+ const currentIndex = flattened.findIndex(s => s.start <= elapsedTime && s.end > elapsedTime);
571
+ const toDisplay = (entry) => ({
572
+ label: (0, graph_1.getStepTargetText)(entry.step, ftp),
573
+ targetPower: this.getStepAbsolutePower(entry.step, ftp),
574
+ duration: entry.duration,
575
+ remaining: null,
576
+ isCurrent: false
577
+ });
578
+ if (currentIndex === -1)
579
+ return { previous: null, current: currentStep, upcoming: [], hasMore: false };
580
+ const previous = currentIndex > 0 ? toDisplay(flattened[currentIndex - 1]) : null;
581
+ const upcomingEntries = flattened.slice(currentIndex + 1, currentIndex + 1 + UPCOMING_STEPS_COUNT);
582
+ const upcoming = upcomingEntries.map(toDisplay);
583
+ const hasMore = currentIndex + 1 + UPCOMING_STEPS_COUNT < flattened.length;
584
+ return { previous, current: currentStep, upcoming, hasMore };
585
+ }
586
+ buildDashboardLine(wo) {
587
+ const limits = this.getWorkoutRide().getCurrentLimits();
588
+ const title = wo.title ?? '';
589
+ if (!limits)
590
+ return { text: title, mode: wo.mode ?? null };
591
+ const target = (0, graph_1.getStepTargetText)(limits.step ?? {}, wo.ftp);
592
+ const duration = (0, graph_1.getStepDuration)({ duration: limits.duration });
593
+ const base = `${target} for ${duration}`;
594
+ let text = base;
595
+ if (title && title !== base)
596
+ text = title.startsWith('(') ? `${base}${title}` : `${base} - ${title}`;
597
+ return { text, mode: wo.mode ?? null };
598
+ }
599
+ getStepAbsolutePower(step, ftp) {
600
+ const p = step.power;
601
+ if (!p)
602
+ return null;
603
+ const val = p.max ?? p.min;
604
+ if (val === undefined)
605
+ return null;
606
+ if (p.type === 'watt')
607
+ return Math.round(val);
608
+ if (ftp === undefined)
609
+ return null;
610
+ return Math.round(val / 100 * ftp);
611
+ }
612
+ getStepFlags() {
613
+ const wo = this.getWorkoutRide().getDashboardDisplayProperties();
614
+ return { canStepBack: !!wo.canShowBackward, canStepForward: !!wo.canShowForward };
615
+ }
616
+ getLastLogTime() {
617
+ const logs = this.getActivityRide().getActivity()?.logs;
618
+ if (!logs || logs.length === 0)
619
+ return 0;
620
+ return logs.at(-1).time ?? 0;
621
+ }
622
+ getElapsedActivityTime() {
623
+ return this.getActivityRide().getActivity()?.time ?? 0;
624
+ }
625
+ getEmptyWorkoutDisplayProps() {
626
+ return {
627
+ rideState: 'Error',
628
+ rideType: null,
629
+ startOverlayProps: null,
630
+ menuProps: null,
631
+ startGateProps: null,
632
+ title: '',
633
+ graph: { bars: [], ftp: 0, ftpLine: 0, domain: { x: [0, 0], y: [0, 0] } },
634
+ steps: { previous: null, current: null, upcoming: [], hasMore: false },
635
+ dashboard: { text: '', mode: null },
636
+ gestureHint: null,
637
+ loadIncrement: DEFAULT_LOAD_INCREMENT,
638
+ loadButtonMode: 'power'
639
+ };
640
+ }
641
+ getPageLogName() {
642
+ return this.isRideType('Workout') ? 'WorkoutRide' : 'Rides';
643
+ }
644
+ requiresOwnInit() {
645
+ return !this.isRideType('Workout');
646
+ }
647
+ getBackgroundPauseRequester() {
648
+ return this.isRideType('Workout') ? 'device' : 'user';
649
+ }
650
+ isRideType(...types) {
651
+ try {
652
+ return types.includes(this.getRideDisplay().getRideType());
653
+ }
654
+ catch {
655
+ return false;
656
+ }
657
+ }
658
+ buildPausedMenuProps() {
659
+ return this.isRideType('Workout')
660
+ ? { showResume: true, ...this.getStepFlags() }
661
+ : { showResume: true };
662
+ }
663
+ buildFinishedMenuProps() {
664
+ return this.isRideType('Workout')
665
+ ? { showResume: false, finished: true, canStepBack: false, canStepForward: false }
666
+ : { showResume: false, finished: true };
667
+ }
668
+ buildBaseDisplayProps() {
669
+ const state = this.getRideDisplay().getState();
670
+ const isStarting = state === 'Idle' || state === 'Starting' || state === 'Error';
671
+ return {
672
+ rideState: state,
673
+ rideType: this.getRideDisplay().getRideType(),
674
+ startOverlayProps: isStarting ? this.getRideDisplay().getStartOverlayProps() : null,
675
+ menuProps: this.menuProps,
676
+ startGateProps: this.startGateProps
677
+ };
358
678
  }
359
679
  onDisplayStateUpdate(state) {
680
+ this.subscribeToWorkoutObserver();
360
681
  switch (state) {
361
682
  case 'Paused':
362
- this.menuProps = { showResume: true };
683
+ this.menuProps = this.buildPausedMenuProps();
363
684
  break;
364
685
  case 'Finished':
365
- this.menuProps = { showResume: false, finished: true };
366
- break;
686
+ this.menuProps = this.buildFinishedMenuProps();
687
+ this.updatePageDisplay();
688
+ return;
367
689
  case 'Active':
368
690
  this.menuProps = null;
369
691
  break;
370
692
  }
371
693
  this.updatePageDisplay();
372
694
  }
373
- onRouteUpdate() {
374
- this.updatePageDisplay();
695
+ updatePageDisplay() {
696
+ this.getPageObserver()?.emit('page-update');
697
+ }
698
+ registerHandlers(observer, map) {
699
+ if (!observer)
700
+ return;
701
+ Object.keys(map).forEach(event => { observer.on(event, map[event]); });
702
+ }
703
+ unregisterHandlers(observer, map) {
704
+ if (!observer)
705
+ return;
706
+ Object.keys(map).forEach(event => { observer.off(event, map[event]); });
375
707
  }
376
708
  moveToPreviousPage() {
377
709
  this.moveTo('$contentPage');
@@ -385,17 +717,17 @@ let RidePageService = (() => {
385
717
  }
386
718
  return null;
387
719
  }
388
- get rideDisplayProps() {
389
- return this.getRideDisplay().getDisplayProperties();
390
- }
391
720
  getRideDisplay() {
392
721
  return (0, display_1.useRideDisplay)();
393
722
  }
394
- getSecretBinding() {
395
- return this.getBindings().secret;
723
+ getWorkoutRide() {
724
+ return (0, service_1.useWorkoutRide)();
725
+ }
726
+ getActivityRide() {
727
+ return (0, activities_1.useActivityRide)();
396
728
  }
397
- getindings() {
398
- return (0, api_1.getBindings)();
729
+ getUserSettings() {
730
+ return (0, settings_1.useUserSettings)();
399
731
  }
400
732
  getOnlineStatusMonitoring() {
401
733
  return (0, monitoring_1.useOnlineStatusMonitoring)();
@@ -404,5 +736,7 @@ let RidePageService = (() => {
404
736
  return RidePageService = _classThis;
405
737
  })();
406
738
  exports.RidePageService = RidePageService;
407
- const getRidePageService = () => new RidePageService();
739
+ const getRidePageService = () => {
740
+ return new RidePageService();
741
+ };
408
742
  exports.getRidePageService = getRidePageService;