incyclist-services 1.7.118 → 1.7.120

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.
@@ -47,6 +47,7 @@ const Injection_1 = require("../../../base/decorators/Injection");
47
47
  const i18n_1 = require("../../../i18n");
48
48
  exports.DB_VERSION = '5';
49
49
  exports.DB_NAME = 'db';
50
+ const SHORT_RIDE_FILTER_CUTOFF = new Date('2026-08-28').getTime();
50
51
  let ActivitiesRepository = (() => {
51
52
  let _classDecorators = [types_1.Singleton];
52
53
  let _classDescriptor;
@@ -109,7 +110,7 @@ let ActivitiesRepository = (() => {
109
110
  this.write();
110
111
  }
111
112
  if (writeDetails) {
112
- this.writeDetails(activity);
113
+ await this.writeDetails(activity);
113
114
  }
114
115
  }
115
116
  migrate(activity, writeSummary = true) {
@@ -224,6 +225,16 @@ let ActivitiesRepository = (() => {
224
225
  }
225
226
  search(criteria) {
226
227
  let result = this.activities;
228
+ if (!this.isMobile()) {
229
+ result = result?.filter(ai => {
230
+ const { rideTime, startTime } = ai.summary;
231
+ if (rideTime < 1)
232
+ return false;
233
+ if (rideTime <= 30 && startTime < SHORT_RIDE_FILTER_CUTOFF)
234
+ return false;
235
+ return true;
236
+ });
237
+ }
227
238
  result = this.checkIdFilter(result, criteria);
228
239
  result = this.checkHashFilter(result, criteria);
229
240
  result = this.checkStartPosFilter(result, criteria);
@@ -475,7 +486,7 @@ let ActivitiesRepository = (() => {
475
486
  }
476
487
  async writeDetails(activity) {
477
488
  const name = activity.summary.name;
478
- this.getRepo().write(name, activity.details);
489
+ await this.getRepo().write(name, activity.details);
479
490
  }
480
491
  async scanForNewActivities() {
481
492
  try {
@@ -509,6 +520,13 @@ let ActivitiesRepository = (() => {
509
520
  getRouteList() {
510
521
  return (0, routes_1.useRouteList)();
511
522
  }
523
+ isMobile() {
524
+ const { appInfo } = this.getBindings();
525
+ if (!appInfo) {
526
+ return false;
527
+ }
528
+ return appInfo?.getChannel() === 'mobile';
529
+ }
512
530
  getBindings() {
513
531
  return (0, api_1.getBindings)();
514
532
  }
@@ -254,7 +254,7 @@ let Activity = (() => {
254
254
  isRouteAvailable() {
255
255
  if (!this.details?.route)
256
256
  return false;
257
- if (this.details.routeType === 'Free-Ride')
257
+ if (this.details.routeType === 'Free-Ride' || this.details.routeType === 'None')
258
258
  return false;
259
259
  return this.getRouteList().getRouteDescription(this.details.route.id) !== undefined;
260
260
  }
@@ -277,7 +277,7 @@ let Activity = (() => {
277
277
  canStart() {
278
278
  if (!this.details?.route)
279
279
  return false;
280
- if (this.details.routeType === 'Free-Ride')
280
+ if (this.details.routeType === 'Free-Ride' || this.details.routeType === 'None')
281
281
  return false;
282
282
  if (this.details.routeType === 'Video') {
283
283
  const isOnline = this.getOnlineMonitoring().onlineStatus;
@@ -198,7 +198,7 @@ let ActivityRideService = (() => {
198
198
  this.disableDeviceHealthCheck();
199
199
  this.state = 'completed';
200
200
  this.updateActivityTime();
201
- this._save();
201
+ await this._save();
202
202
  this.emit('completed');
203
203
  this.tsPauseStart = undefined;
204
204
  delete this.tsStart;
@@ -536,7 +536,7 @@ let ActivityRideService = (() => {
536
536
  if (this.state !== 'completed') {
537
537
  await this.stop();
538
538
  }
539
- this.getRepo().delete(this.activity.id);
539
+ await this.getRepo().delete(this.activity.id);
540
540
  this.state = 'idle';
541
541
  }
542
542
  save() {
@@ -552,7 +552,7 @@ let ActivityRideService = (() => {
552
552
  let success = false;
553
553
  try {
554
554
  emit('start', success);
555
- await this._save();
555
+ this._save();
556
556
  success = await this._convertAndUpload();
557
557
  (0, sleep_1.sleep)(0).then(() => {
558
558
  delete this.saveObserver;
@@ -965,7 +965,7 @@ let ActivityRideService = (() => {
965
965
  await fs.writeFile(fileName, Buffer.from(data));
966
966
  this.activity.tcxFileName = fileName;
967
967
  }
968
- await this._save();
968
+ this._save();
969
969
  emit('save.done', format, true);
970
970
  return true;
971
971
  }
@@ -22,8 +22,11 @@ function isVirtualShiftingEnabled(mode) {
22
22
  function getLoadButtonMode(mode) {
23
23
  if (!mode)
24
24
  return 'power';
25
- if (mode.isSIM() || mode.isResistance()) {
26
- return isVirtualShiftingEnabled(mode) ? 'gear' : 'hidden';
27
- }
28
- return 'power';
25
+ if (mode.isERG())
26
+ return 'power';
27
+ if (mode.isSIM() && isVirtualShiftingEnabled(mode))
28
+ return 'gear';
29
+ if (mode.isResistance())
30
+ return 'gear';
31
+ return 'hidden';
29
32
  }
@@ -39,6 +39,7 @@ const gd_eventlog_1 = require("gd-eventlog");
39
39
  const decorators_1 = require("../../base/decorators");
40
40
  const pages_1 = require("../../base/pages");
41
41
  const display_1 = require("../display");
42
+ const devices_1 = require("../../devices");
42
43
  const sleep_1 = require("../../utils/sleep");
43
44
  const monitoring_1 = require("../../monitoring");
44
45
  const activities_1 = require("../../activities");
@@ -64,6 +65,7 @@ let RidePageService = (() => {
64
65
  let _getActivityRide_decorators;
65
66
  let _getUserSettings_decorators;
66
67
  let _getOnlineStatusMonitoring_decorators;
68
+ let _getDeviceConfiguration_decorators;
67
69
  var RidePageService = class extends _classSuper {
68
70
  static { _classThis = this; }
69
71
  static {
@@ -73,11 +75,13 @@ let RidePageService = (() => {
73
75
  _getActivityRide_decorators = [decorators_1.Injectable];
74
76
  _getUserSettings_decorators = [decorators_1.Injectable];
75
77
  _getOnlineStatusMonitoring_decorators = [decorators_1.Injectable];
78
+ _getDeviceConfiguration_decorators = [decorators_1.Injectable];
76
79
  __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);
77
80
  __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);
78
81
  __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);
79
82
  __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);
80
83
  __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);
84
+ __esDecorate(this, null, _getDeviceConfiguration_decorators, { kind: "method", name: "getDeviceConfiguration", static: false, private: false, access: { has: obj => "getDeviceConfiguration" in obj, get: obj => obj.getDeviceConfiguration }, metadata: _metadata }, null, _instanceExtraInitializers);
81
85
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
82
86
  RidePageService = _classThis = _classDescriptor.value;
83
87
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
@@ -92,6 +96,7 @@ let RidePageService = (() => {
92
96
  menuProps = null;
93
97
  isInitialized = false;
94
98
  startGateProps = null;
99
+ onDeviceModeChangedBound = this.onDeviceModeChanged.bind(this);
95
100
  prevRidesVisibleRows = DEFAULT_PREV_RIDES_VISIBLE_ROWS;
96
101
  prevRidesMode = 'list';
97
102
  gestureHintDismissed = false;
@@ -103,7 +108,8 @@ let RidePageService = (() => {
103
108
  this.eventHandler['route-update'] = this.onRouteUpdate.bind(this);
104
109
  this.eventHandler['view-changed'] = this.onViewChanged.bind(this);
105
110
  this.eventHandler['prev-rides-update'] = this.onPrevRidesTick.bind(this);
106
- this.workoutEventHandler['step-changed'] = this.onWorkoutUpdate.bind(this);
111
+ this.workoutEventHandler['step-changed'] = this.onWorkoutStepChanged.bind(this);
112
+ this.workoutEventHandler['step-countdown'] = this.onWorkoutStepCountdown.bind(this);
107
113
  this.workoutEventHandler['update'] = this.onWorkoutUpdate.bind(this);
108
114
  this.workoutEventHandler['forward'] = this.onWorkoutUpdate.bind(this);
109
115
  this.workoutEventHandler['backward'] = this.onWorkoutUpdate.bind(this);
@@ -142,6 +148,7 @@ let RidePageService = (() => {
142
148
  const registerAndStart = () => {
143
149
  this.registerHandlers(this.rideObserver, this.eventHandler);
144
150
  this.subscribeToWorkoutObserver();
151
+ this.getDeviceConfiguration().on('mode-changed', this.onDeviceModeChangedBound);
145
152
  const state = service.getState();
146
153
  if (state === 'Started' || state === 'Active' || state === 'Paused') {
147
154
  this.logError(new Error(`start() requested while ride is ${state}`), 'openPage');
@@ -186,6 +193,7 @@ let RidePageService = (() => {
186
193
  this.getRideDisplay().stop();
187
194
  this.unregisterHandlers(this.rideObserver, this.eventHandler);
188
195
  this.unsubscribeFromWorkoutObserver();
196
+ this.getDeviceConfiguration().off('mode-changed', this.onDeviceModeChangedBound);
189
197
  this.menuProps = null;
190
198
  this.isInitialized = false;
191
199
  super.closePage();
@@ -277,9 +285,11 @@ let RidePageService = (() => {
277
285
  onMenuOpen() {
278
286
  try {
279
287
  const state = this.getRideDisplay().getState();
288
+ const loadControl = this.getLoadControlProps();
289
+ const showRideSettings = !this.isRideType('Workout');
280
290
  this.menuProps = this.isWorkoutAttached()
281
- ? { showResume: state === 'Paused', ...this.getStepFlags() }
282
- : { showResume: state === 'Paused' };
291
+ ? { showResume: state === 'Paused', ...this.getStepFlags(), loadControl, showRideSettings }
292
+ : { showResume: state === 'Paused', loadControl, showRideSettings };
283
293
  this.updatePageDisplay();
284
294
  }
285
295
  catch (err) {
@@ -396,7 +406,7 @@ let RidePageService = (() => {
396
406
  const available = this.getCornerWidgetStates();
397
407
  if (available.length <= 1)
398
408
  return undefined;
399
- const current = this.getUserSettings().get(CORNER_WIDGET_SETTING_KEY, 'elevation');
409
+ const current = this.getUserSettings().get(CORNER_WIDGET_SETTING_KEY, 'workout');
400
410
  return available.includes(current) ? current : available[0];
401
411
  }
402
412
  catch (err) {
@@ -413,7 +423,7 @@ let RidePageService = (() => {
413
423
  onToggleCornerWidget() {
414
424
  try {
415
425
  const available = this.getCornerWidgetStates();
416
- const current = this.getUserSettings().get(CORNER_WIDGET_SETTING_KEY, 'elevation');
426
+ const current = this.getUserSettings().get(CORNER_WIDGET_SETTING_KEY, 'workout');
417
427
  const next = available[(available.indexOf(current) + 1) % available.length];
418
428
  this.getUserSettings().set(CORNER_WIDGET_SETTING_KEY, next);
419
429
  this.updatePageDisplay();
@@ -582,8 +592,7 @@ let RidePageService = (() => {
582
592
  const plainErgAdjustment = !this.getWorkoutRide().inUse() && this.getLoadButtonMode() === 'power';
583
593
  if (plainErgAdjustment) {
584
594
  const sign = deltaPct >= 0 ? 1 : -1;
585
- const nominal = Math.abs(deltaPct) === 1 ? 5 : 50;
586
- return this.getRideDisplay().adjustDevicePower(sign * nominal);
595
+ return this.getRideDisplay().adjustDevicePower(sign * this.getNominalErgWatts(deltaPct));
587
596
  }
588
597
  return deltaPct >= 0
589
598
  ? this.getWorkoutRide().powerUp(deltaPct)
@@ -631,6 +640,13 @@ let RidePageService = (() => {
631
640
  onWorkoutUpdate() {
632
641
  this.updatePageDisplay();
633
642
  }
643
+ onWorkoutStepChanged(update) {
644
+ this.onWorkoutUpdate();
645
+ this.getPageObserver()?.emit('step-changed', update);
646
+ }
647
+ onWorkoutStepCountdown(tick) {
648
+ this.getPageObserver()?.emit('step-countdown', tick);
649
+ }
634
650
  onWorkoutFinished() {
635
651
  try {
636
652
  if (this.isRideType('Workout'))
@@ -756,6 +772,40 @@ let RidePageService = (() => {
756
772
  const wo = this.getWorkoutRide().getDashboardDisplayProperties();
757
773
  return { canStepBack: !!wo.canShowBackward, canStepForward: !!wo.canShowForward };
758
774
  }
775
+ getLoadControlProps() {
776
+ const mode = this.getLoadButtonMode();
777
+ if (mode === 'hidden')
778
+ return { visible: false };
779
+ if (mode === 'gear')
780
+ return { visible: true, label: 'Gear', buttons: { inc1: '+1', dec1: '-1', inc5: '+5', dec5: '-5' } };
781
+ if (this.isWorkoutAttached()) {
782
+ const buttons = this.getWorkoutRide().getDashboardDisplayProperties().loadButtons;
783
+ return { visible: true, label: 'Load', buttons };
784
+ }
785
+ const small = this.getNominalErgWatts(1);
786
+ const big = this.getNominalErgWatts(5);
787
+ return {
788
+ visible: true,
789
+ label: 'Load',
790
+ buttons: { inc1: `+${small}W`, dec1: `-${small}W`, inc5: `+${big}W`, dec5: `-${big}W` }
791
+ };
792
+ }
793
+ getNominalErgWatts(magnitude) {
794
+ return Math.abs(magnitude) === 1 ? 5 : 50;
795
+ }
796
+ onDeviceModeChanged() {
797
+ setTimeout(() => {
798
+ try {
799
+ if (!this.menuProps)
800
+ return;
801
+ this.menuProps = { ...this.menuProps, loadControl: this.getLoadControlProps() };
802
+ this.updatePageDisplay();
803
+ }
804
+ catch (err) {
805
+ this.logError(err, 'onDeviceModeChanged');
806
+ }
807
+ }, 0);
808
+ }
759
809
  getLastLogTime() {
760
810
  const logs = this.getActivityRide().getActivity()?.logs;
761
811
  if (!logs || logs.length === 0)
@@ -812,14 +862,18 @@ let RidePageService = (() => {
812
862
  }
813
863
  }
814
864
  buildPausedMenuProps() {
865
+ const loadControl = this.getLoadControlProps();
866
+ const showRideSettings = !this.isRideType('Workout');
815
867
  return this.isWorkoutAttached()
816
- ? { showResume: true, ...this.getStepFlags() }
817
- : { showResume: true };
868
+ ? { showResume: true, ...this.getStepFlags(), loadControl, showRideSettings }
869
+ : { showResume: true, loadControl, showRideSettings };
818
870
  }
819
871
  buildFinishedMenuProps() {
872
+ const loadControl = this.getLoadControlProps();
873
+ const showRideSettings = !this.isRideType('Workout');
820
874
  return this.isWorkoutAttached()
821
- ? { showResume: false, finished: true, canStepBack: false, canStepForward: false }
822
- : { showResume: false, finished: true };
875
+ ? { showResume: false, finished: true, canStepBack: false, canStepForward: false, loadControl, showRideSettings }
876
+ : { showResume: false, finished: true, loadControl, showRideSettings };
823
877
  }
824
878
  buildBaseDisplayProps() {
825
879
  const state = this.getRideDisplay().getState();
@@ -953,6 +1007,9 @@ let RidePageService = (() => {
953
1007
  getOnlineStatusMonitoring() {
954
1008
  return (0, monitoring_1.useOnlineStatusMonitoring)();
955
1009
  }
1010
+ getDeviceConfiguration() {
1011
+ return (0, devices_1.useDeviceConfiguration)();
1012
+ }
956
1013
  };
957
1014
  return RidePageService = _classThis;
958
1015
  })();
@@ -609,6 +609,8 @@ let RouteListService = (() => {
609
609
  }
610
610
  getRouteDescription(id) {
611
611
  try {
612
+ if (!id)
613
+ return undefined;
612
614
  return this.getVisibleRoutes().find(r => r.description.id === id)?.description;
613
615
  }
614
616
  catch (err) {
@@ -617,6 +619,8 @@ let RouteListService = (() => {
617
619
  }
618
620
  getRoute(id) {
619
621
  try {
622
+ if (!id)
623
+ return undefined;
620
624
  return this.getVisibleRoutes().find(r => r.description.id === id);
621
625
  }
622
626
  catch (err) {
@@ -48,6 +48,10 @@ const list_1 = require("../list");
48
48
  const decorators_1 = require("../../base/decorators");
49
49
  const DEFAULT_FTP = 200;
50
50
  const WORKOUT_ZOOM = 1200;
51
+ const STEP_COUNTDOWN_THRESHOLDS = [4, 3, 2, 1, 0];
52
+ const hasValidDuration = (duration) => {
53
+ return typeof duration === 'number' && Number.isFinite(duration) && duration > 0;
54
+ };
51
55
  let WorkoutRide = (() => {
52
56
  let _classDecorators = [types_1.Singleton];
53
57
  let _classDescriptor;
@@ -86,6 +90,8 @@ let WorkoutRide = (() => {
86
90
  tsCurrent;
87
91
  trainingTime;
88
92
  currentLimits;
93
+ countdownTimers = [];
94
+ firedCountdownThresholds = new Set();
89
95
  updateInterval;
90
96
  currentStep;
91
97
  isFreeRide;
@@ -136,6 +142,8 @@ let WorkoutRide = (() => {
136
142
  this.logEvent({ message: 'workout started', settings: this.settings });
137
143
  if (!this.updateInterval) {
138
144
  this.update();
145
+ this.resetCountdownProgress();
146
+ this.rescheduleStepCountdown();
139
147
  this.updateInterval = setInterval(() => { this.update(); }, 500);
140
148
  }
141
149
  if (paused)
@@ -155,6 +163,7 @@ let WorkoutRide = (() => {
155
163
  this.tsPauseStart = ts;
156
164
  this.tsCurrent = ts;
157
165
  this.state = 'paused';
166
+ this.clearCountdownTimers();
158
167
  this.emit('paused');
159
168
  this.logger.logEvent({ message: 'workout paused' });
160
169
  }
@@ -178,6 +187,7 @@ let WorkoutRide = (() => {
178
187
  this.tsPauseStart = undefined;
179
188
  this.tsCurrent = ts;
180
189
  this.state = 'active';
190
+ this.rescheduleStepCountdown();
181
191
  this.logger.logEvent({ message: 'workout resumed', offset: this.offset });
182
192
  this.emit('resumed');
183
193
  }
@@ -219,6 +229,8 @@ let WorkoutRide = (() => {
219
229
  }
220
230
  this.manualTimeOffset += limits.remaining;
221
231
  this.update();
232
+ this.resetCountdownProgress();
233
+ this.rescheduleStepCountdown();
222
234
  this.observer.emit('forward', ts, limits.remaining);
223
235
  }
224
236
  catch (err) {
@@ -258,6 +270,8 @@ let WorkoutRide = (() => {
258
270
  }
259
271
  }
260
272
  this.update();
273
+ this.resetCountdownProgress();
274
+ this.rescheduleStepCountdown();
261
275
  this.observer.emit('backward', ts, diff, jumpType, limits?.step, target);
262
276
  }
263
277
  catch (err) {
@@ -508,7 +522,46 @@ let WorkoutRide = (() => {
508
522
  else if (this.currentStep.power && !prevStep.power) {
509
523
  this.stopFreeRide();
510
524
  }
525
+ if (!this.firedCountdownThresholds.has(0)) {
526
+ this.emit('step-countdown', { secondsRemaining: 0 });
527
+ }
511
528
  this.emit('step-changed', this.getDashboardDisplayProperties());
529
+ this.resetCountdownProgress();
530
+ this.rescheduleStepCountdown();
531
+ }
532
+ clearCountdownTimers() {
533
+ this.countdownTimers.forEach(t => clearTimeout(t));
534
+ this.countdownTimers = [];
535
+ }
536
+ resetCountdownProgress() {
537
+ this.firedCountdownThresholds = new Set();
538
+ }
539
+ rescheduleStepCountdown() {
540
+ this.clearCountdownTimers();
541
+ if (this.state !== 'active')
542
+ return;
543
+ const duration = this.currentLimits?.duration;
544
+ const remaining = this.currentLimits?.remaining;
545
+ const trainingTime = this.trainingTime;
546
+ if (!hasValidDuration(duration) || remaining === undefined || trainingTime === undefined)
547
+ return;
548
+ const stepEnd = trainingTime + remaining;
549
+ STEP_COUNTDOWN_THRESHOLDS.forEach(secondsRemaining => {
550
+ if (this.firedCountdownThresholds.has(secondsRemaining))
551
+ return;
552
+ const targetTrainingTime = stepEnd - secondsRemaining;
553
+ if (targetTrainingTime < trainingTime)
554
+ return;
555
+ const wallClockDeadline = this.tsStart + (this.offset ?? 0) + (targetTrainingTime - (this.manualTimeOffset ?? 0)) * 1000;
556
+ const delayMs = wallClockDeadline - Date.now();
557
+ if (delayMs < 0)
558
+ return;
559
+ const timer = setTimeout(() => {
560
+ this.firedCountdownThresholds.add(secondsRemaining);
561
+ this.emit('step-countdown', { secondsRemaining });
562
+ }, delayMs);
563
+ this.countdownTimers.push(timer);
564
+ });
512
565
  }
513
566
  checkIfDone() {
514
567
  const time = (Date.now() - this.tsStart - (this.offset ?? 0)) / 1000 + (this.manualTimeOffset ?? 0);
@@ -691,6 +744,7 @@ let WorkoutRide = (() => {
691
744
  clearInterval(this.updateInterval);
692
745
  this.updateInterval = undefined;
693
746
  }
747
+ this.clearCountdownTimers();
694
748
  }
695
749
  getCyclingModeText() {
696
750
  const deviceRide = (0, devices_1.useDeviceRide)();
@@ -44,6 +44,7 @@ import { Injectable } from "../../../base/decorators/Injection";
44
44
  import { useUnitConverter } from "../../../i18n";
45
45
  export const DB_VERSION = '5';
46
46
  export const DB_NAME = 'db';
47
+ const SHORT_RIDE_FILTER_CUTOFF = new Date('2026-08-28').getTime();
47
48
  let ActivitiesRepository = (() => {
48
49
  let _classDecorators = [Singleton];
49
50
  let _classDescriptor;
@@ -106,7 +107,7 @@ let ActivitiesRepository = (() => {
106
107
  this.write();
107
108
  }
108
109
  if (writeDetails) {
109
- this.writeDetails(activity);
110
+ await this.writeDetails(activity);
110
111
  }
111
112
  }
112
113
  migrate(activity, writeSummary = true) {
@@ -221,6 +222,16 @@ let ActivitiesRepository = (() => {
221
222
  }
222
223
  search(criteria) {
223
224
  let result = this.activities;
225
+ if (!this.isMobile()) {
226
+ result = result?.filter(ai => {
227
+ const { rideTime, startTime } = ai.summary;
228
+ if (rideTime < 1)
229
+ return false;
230
+ if (rideTime <= 30 && startTime < SHORT_RIDE_FILTER_CUTOFF)
231
+ return false;
232
+ return true;
233
+ });
234
+ }
224
235
  result = this.checkIdFilter(result, criteria);
225
236
  result = this.checkHashFilter(result, criteria);
226
237
  result = this.checkStartPosFilter(result, criteria);
@@ -472,7 +483,7 @@ let ActivitiesRepository = (() => {
472
483
  }
473
484
  async writeDetails(activity) {
474
485
  const name = activity.summary.name;
475
- this.getRepo().write(name, activity.details);
486
+ await this.getRepo().write(name, activity.details);
476
487
  }
477
488
  async scanForNewActivities() {
478
489
  try {
@@ -506,6 +517,13 @@ let ActivitiesRepository = (() => {
506
517
  getRouteList() {
507
518
  return useRouteList();
508
519
  }
520
+ isMobile() {
521
+ const { appInfo } = this.getBindings();
522
+ if (!appInfo) {
523
+ return false;
524
+ }
525
+ return appInfo?.getChannel() === 'mobile';
526
+ }
509
527
  getBindings() {
510
528
  return getBindings();
511
529
  }
@@ -251,7 +251,7 @@ let Activity = (() => {
251
251
  isRouteAvailable() {
252
252
  if (!this.details?.route)
253
253
  return false;
254
- if (this.details.routeType === 'Free-Ride')
254
+ if (this.details.routeType === 'Free-Ride' || this.details.routeType === 'None')
255
255
  return false;
256
256
  return this.getRouteList().getRouteDescription(this.details.route.id) !== undefined;
257
257
  }
@@ -274,7 +274,7 @@ let Activity = (() => {
274
274
  canStart() {
275
275
  if (!this.details?.route)
276
276
  return false;
277
- if (this.details.routeType === 'Free-Ride')
277
+ if (this.details.routeType === 'Free-Ride' || this.details.routeType === 'None')
278
278
  return false;
279
279
  if (this.details.routeType === 'Video') {
280
280
  const isOnline = this.getOnlineMonitoring().onlineStatus;
@@ -192,7 +192,7 @@ let ActivityRideService = (() => {
192
192
  this.disableDeviceHealthCheck();
193
193
  this.state = 'completed';
194
194
  this.updateActivityTime();
195
- this._save();
195
+ await this._save();
196
196
  this.emit('completed');
197
197
  this.tsPauseStart = undefined;
198
198
  delete this.tsStart;
@@ -530,7 +530,7 @@ let ActivityRideService = (() => {
530
530
  if (this.state !== 'completed') {
531
531
  await this.stop();
532
532
  }
533
- this.getRepo().delete(this.activity.id);
533
+ await this.getRepo().delete(this.activity.id);
534
534
  this.state = 'idle';
535
535
  }
536
536
  save() {
@@ -546,7 +546,7 @@ let ActivityRideService = (() => {
546
546
  let success = false;
547
547
  try {
548
548
  emit('start', success);
549
- await this._save();
549
+ this._save();
550
550
  success = await this._convertAndUpload();
551
551
  sleep(0).then(() => {
552
552
  delete this.saveObserver;
@@ -959,7 +959,7 @@ let ActivityRideService = (() => {
959
959
  await fs.writeFile(fileName, Buffer.from(data));
960
960
  this.activity.tcxFileName = fileName;
961
961
  }
962
- await this._save();
962
+ this._save();
963
963
  emit('save.done', format, true);
964
964
  return true;
965
965
  }
@@ -18,8 +18,11 @@ export function isVirtualShiftingEnabled(mode) {
18
18
  export function getLoadButtonMode(mode) {
19
19
  if (!mode)
20
20
  return 'power';
21
- if (mode.isSIM() || mode.isResistance()) {
22
- return isVirtualShiftingEnabled(mode) ? 'gear' : 'hidden';
23
- }
24
- return 'power';
21
+ if (mode.isERG())
22
+ return 'power';
23
+ if (mode.isSIM() && isVirtualShiftingEnabled(mode))
24
+ return 'gear';
25
+ if (mode.isResistance())
26
+ return 'gear';
27
+ return 'hidden';
25
28
  }
@@ -36,6 +36,7 @@ import { EventLogger } from "gd-eventlog";
36
36
  import { Injectable, Singleton } from "../../base/decorators";
37
37
  import { IncyclistPageService } from "../../base/pages";
38
38
  import { useRideDisplay } from "../display";
39
+ import { useDeviceConfiguration } from "../../devices";
39
40
  import { sleep } from "../../utils/sleep";
40
41
  import { useOnlineStatusMonitoring } from "../../monitoring";
41
42
  import { useActivityRide } from "../../activities";
@@ -61,6 +62,7 @@ let RidePageService = (() => {
61
62
  let _getActivityRide_decorators;
62
63
  let _getUserSettings_decorators;
63
64
  let _getOnlineStatusMonitoring_decorators;
65
+ let _getDeviceConfiguration_decorators;
64
66
  var RidePageService = class extends _classSuper {
65
67
  static { _classThis = this; }
66
68
  static {
@@ -70,11 +72,13 @@ let RidePageService = (() => {
70
72
  _getActivityRide_decorators = [Injectable];
71
73
  _getUserSettings_decorators = [Injectable];
72
74
  _getOnlineStatusMonitoring_decorators = [Injectable];
75
+ _getDeviceConfiguration_decorators = [Injectable];
73
76
  __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);
74
77
  __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);
75
78
  __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);
76
79
  __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);
77
80
  __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);
81
+ __esDecorate(this, null, _getDeviceConfiguration_decorators, { kind: "method", name: "getDeviceConfiguration", static: false, private: false, access: { has: obj => "getDeviceConfiguration" in obj, get: obj => obj.getDeviceConfiguration }, metadata: _metadata }, null, _instanceExtraInitializers);
78
82
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
79
83
  RidePageService = _classThis = _classDescriptor.value;
80
84
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
@@ -89,6 +93,7 @@ let RidePageService = (() => {
89
93
  menuProps = null;
90
94
  isInitialized = false;
91
95
  startGateProps = null;
96
+ onDeviceModeChangedBound = this.onDeviceModeChanged.bind(this);
92
97
  prevRidesVisibleRows = DEFAULT_PREV_RIDES_VISIBLE_ROWS;
93
98
  prevRidesMode = 'list';
94
99
  gestureHintDismissed = false;
@@ -100,7 +105,8 @@ let RidePageService = (() => {
100
105
  this.eventHandler['route-update'] = this.onRouteUpdate.bind(this);
101
106
  this.eventHandler['view-changed'] = this.onViewChanged.bind(this);
102
107
  this.eventHandler['prev-rides-update'] = this.onPrevRidesTick.bind(this);
103
- this.workoutEventHandler['step-changed'] = this.onWorkoutUpdate.bind(this);
108
+ this.workoutEventHandler['step-changed'] = this.onWorkoutStepChanged.bind(this);
109
+ this.workoutEventHandler['step-countdown'] = this.onWorkoutStepCountdown.bind(this);
104
110
  this.workoutEventHandler['update'] = this.onWorkoutUpdate.bind(this);
105
111
  this.workoutEventHandler['forward'] = this.onWorkoutUpdate.bind(this);
106
112
  this.workoutEventHandler['backward'] = this.onWorkoutUpdate.bind(this);
@@ -139,6 +145,7 @@ let RidePageService = (() => {
139
145
  const registerAndStart = () => {
140
146
  this.registerHandlers(this.rideObserver, this.eventHandler);
141
147
  this.subscribeToWorkoutObserver();
148
+ this.getDeviceConfiguration().on('mode-changed', this.onDeviceModeChangedBound);
142
149
  const state = service.getState();
143
150
  if (state === 'Started' || state === 'Active' || state === 'Paused') {
144
151
  this.logError(new Error(`start() requested while ride is ${state}`), 'openPage');
@@ -183,6 +190,7 @@ let RidePageService = (() => {
183
190
  this.getRideDisplay().stop();
184
191
  this.unregisterHandlers(this.rideObserver, this.eventHandler);
185
192
  this.unsubscribeFromWorkoutObserver();
193
+ this.getDeviceConfiguration().off('mode-changed', this.onDeviceModeChangedBound);
186
194
  this.menuProps = null;
187
195
  this.isInitialized = false;
188
196
  super.closePage();
@@ -274,9 +282,11 @@ let RidePageService = (() => {
274
282
  onMenuOpen() {
275
283
  try {
276
284
  const state = this.getRideDisplay().getState();
285
+ const loadControl = this.getLoadControlProps();
286
+ const showRideSettings = !this.isRideType('Workout');
277
287
  this.menuProps = this.isWorkoutAttached()
278
- ? { showResume: state === 'Paused', ...this.getStepFlags() }
279
- : { showResume: state === 'Paused' };
288
+ ? { showResume: state === 'Paused', ...this.getStepFlags(), loadControl, showRideSettings }
289
+ : { showResume: state === 'Paused', loadControl, showRideSettings };
280
290
  this.updatePageDisplay();
281
291
  }
282
292
  catch (err) {
@@ -393,7 +403,7 @@ let RidePageService = (() => {
393
403
  const available = this.getCornerWidgetStates();
394
404
  if (available.length <= 1)
395
405
  return undefined;
396
- const current = this.getUserSettings().get(CORNER_WIDGET_SETTING_KEY, 'elevation');
406
+ const current = this.getUserSettings().get(CORNER_WIDGET_SETTING_KEY, 'workout');
397
407
  return available.includes(current) ? current : available[0];
398
408
  }
399
409
  catch (err) {
@@ -410,7 +420,7 @@ let RidePageService = (() => {
410
420
  onToggleCornerWidget() {
411
421
  try {
412
422
  const available = this.getCornerWidgetStates();
413
- const current = this.getUserSettings().get(CORNER_WIDGET_SETTING_KEY, 'elevation');
423
+ const current = this.getUserSettings().get(CORNER_WIDGET_SETTING_KEY, 'workout');
414
424
  const next = available[(available.indexOf(current) + 1) % available.length];
415
425
  this.getUserSettings().set(CORNER_WIDGET_SETTING_KEY, next);
416
426
  this.updatePageDisplay();
@@ -579,8 +589,7 @@ let RidePageService = (() => {
579
589
  const plainErgAdjustment = !this.getWorkoutRide().inUse() && this.getLoadButtonMode() === 'power';
580
590
  if (plainErgAdjustment) {
581
591
  const sign = deltaPct >= 0 ? 1 : -1;
582
- const nominal = Math.abs(deltaPct) === 1 ? 5 : 50;
583
- return this.getRideDisplay().adjustDevicePower(sign * nominal);
592
+ return this.getRideDisplay().adjustDevicePower(sign * this.getNominalErgWatts(deltaPct));
584
593
  }
585
594
  return deltaPct >= 0
586
595
  ? this.getWorkoutRide().powerUp(deltaPct)
@@ -628,6 +637,13 @@ let RidePageService = (() => {
628
637
  onWorkoutUpdate() {
629
638
  this.updatePageDisplay();
630
639
  }
640
+ onWorkoutStepChanged(update) {
641
+ this.onWorkoutUpdate();
642
+ this.getPageObserver()?.emit('step-changed', update);
643
+ }
644
+ onWorkoutStepCountdown(tick) {
645
+ this.getPageObserver()?.emit('step-countdown', tick);
646
+ }
631
647
  onWorkoutFinished() {
632
648
  try {
633
649
  if (this.isRideType('Workout'))
@@ -753,6 +769,40 @@ let RidePageService = (() => {
753
769
  const wo = this.getWorkoutRide().getDashboardDisplayProperties();
754
770
  return { canStepBack: !!wo.canShowBackward, canStepForward: !!wo.canShowForward };
755
771
  }
772
+ getLoadControlProps() {
773
+ const mode = this.getLoadButtonMode();
774
+ if (mode === 'hidden')
775
+ return { visible: false };
776
+ if (mode === 'gear')
777
+ return { visible: true, label: 'Gear', buttons: { inc1: '+1', dec1: '-1', inc5: '+5', dec5: '-5' } };
778
+ if (this.isWorkoutAttached()) {
779
+ const buttons = this.getWorkoutRide().getDashboardDisplayProperties().loadButtons;
780
+ return { visible: true, label: 'Load', buttons };
781
+ }
782
+ const small = this.getNominalErgWatts(1);
783
+ const big = this.getNominalErgWatts(5);
784
+ return {
785
+ visible: true,
786
+ label: 'Load',
787
+ buttons: { inc1: `+${small}W`, dec1: `-${small}W`, inc5: `+${big}W`, dec5: `-${big}W` }
788
+ };
789
+ }
790
+ getNominalErgWatts(magnitude) {
791
+ return Math.abs(magnitude) === 1 ? 5 : 50;
792
+ }
793
+ onDeviceModeChanged() {
794
+ setTimeout(() => {
795
+ try {
796
+ if (!this.menuProps)
797
+ return;
798
+ this.menuProps = { ...this.menuProps, loadControl: this.getLoadControlProps() };
799
+ this.updatePageDisplay();
800
+ }
801
+ catch (err) {
802
+ this.logError(err, 'onDeviceModeChanged');
803
+ }
804
+ }, 0);
805
+ }
756
806
  getLastLogTime() {
757
807
  const logs = this.getActivityRide().getActivity()?.logs;
758
808
  if (!logs || logs.length === 0)
@@ -809,14 +859,18 @@ let RidePageService = (() => {
809
859
  }
810
860
  }
811
861
  buildPausedMenuProps() {
862
+ const loadControl = this.getLoadControlProps();
863
+ const showRideSettings = !this.isRideType('Workout');
812
864
  return this.isWorkoutAttached()
813
- ? { showResume: true, ...this.getStepFlags() }
814
- : { showResume: true };
865
+ ? { showResume: true, ...this.getStepFlags(), loadControl, showRideSettings }
866
+ : { showResume: true, loadControl, showRideSettings };
815
867
  }
816
868
  buildFinishedMenuProps() {
869
+ const loadControl = this.getLoadControlProps();
870
+ const showRideSettings = !this.isRideType('Workout');
817
871
  return this.isWorkoutAttached()
818
- ? { showResume: false, finished: true, canStepBack: false, canStepForward: false }
819
- : { showResume: false, finished: true };
872
+ ? { showResume: false, finished: true, canStepBack: false, canStepForward: false, loadControl, showRideSettings }
873
+ : { showResume: false, finished: true, loadControl, showRideSettings };
820
874
  }
821
875
  buildBaseDisplayProps() {
822
876
  const state = this.getRideDisplay().getState();
@@ -950,6 +1004,9 @@ let RidePageService = (() => {
950
1004
  getOnlineStatusMonitoring() {
951
1005
  return useOnlineStatusMonitoring();
952
1006
  }
1007
+ getDeviceConfiguration() {
1008
+ return useDeviceConfiguration();
1009
+ }
953
1010
  };
954
1011
  return RidePageService = _classThis;
955
1012
  })();
@@ -603,6 +603,8 @@ let RouteListService = (() => {
603
603
  }
604
604
  getRouteDescription(id) {
605
605
  try {
606
+ if (!id)
607
+ return undefined;
606
608
  return this.getVisibleRoutes().find(r => r.description.id === id)?.description;
607
609
  }
608
610
  catch (err) {
@@ -611,6 +613,8 @@ let RouteListService = (() => {
611
613
  }
612
614
  getRoute(id) {
613
615
  try {
616
+ if (!id)
617
+ return undefined;
614
618
  return this.getVisibleRoutes().find(r => r.description.id === id);
615
619
  }
616
620
  catch (err) {
@@ -45,6 +45,10 @@ import { useWorkoutList } from "../list";
45
45
  import { Injectable } from "../../base/decorators";
46
46
  const DEFAULT_FTP = 200;
47
47
  const WORKOUT_ZOOM = 1200;
48
+ const STEP_COUNTDOWN_THRESHOLDS = [4, 3, 2, 1, 0];
49
+ const hasValidDuration = (duration) => {
50
+ return typeof duration === 'number' && Number.isFinite(duration) && duration > 0;
51
+ };
48
52
  let WorkoutRide = (() => {
49
53
  let _classDecorators = [Singleton];
50
54
  let _classDescriptor;
@@ -83,6 +87,8 @@ let WorkoutRide = (() => {
83
87
  tsCurrent;
84
88
  trainingTime;
85
89
  currentLimits;
90
+ countdownTimers = [];
91
+ firedCountdownThresholds = new Set();
86
92
  updateInterval;
87
93
  currentStep;
88
94
  isFreeRide;
@@ -133,6 +139,8 @@ let WorkoutRide = (() => {
133
139
  this.logEvent({ message: 'workout started', settings: this.settings });
134
140
  if (!this.updateInterval) {
135
141
  this.update();
142
+ this.resetCountdownProgress();
143
+ this.rescheduleStepCountdown();
136
144
  this.updateInterval = setInterval(() => { this.update(); }, 500);
137
145
  }
138
146
  if (paused)
@@ -152,6 +160,7 @@ let WorkoutRide = (() => {
152
160
  this.tsPauseStart = ts;
153
161
  this.tsCurrent = ts;
154
162
  this.state = 'paused';
163
+ this.clearCountdownTimers();
155
164
  this.emit('paused');
156
165
  this.logger.logEvent({ message: 'workout paused' });
157
166
  }
@@ -175,6 +184,7 @@ let WorkoutRide = (() => {
175
184
  this.tsPauseStart = undefined;
176
185
  this.tsCurrent = ts;
177
186
  this.state = 'active';
187
+ this.rescheduleStepCountdown();
178
188
  this.logger.logEvent({ message: 'workout resumed', offset: this.offset });
179
189
  this.emit('resumed');
180
190
  }
@@ -216,6 +226,8 @@ let WorkoutRide = (() => {
216
226
  }
217
227
  this.manualTimeOffset += limits.remaining;
218
228
  this.update();
229
+ this.resetCountdownProgress();
230
+ this.rescheduleStepCountdown();
219
231
  this.observer.emit('forward', ts, limits.remaining);
220
232
  }
221
233
  catch (err) {
@@ -255,6 +267,8 @@ let WorkoutRide = (() => {
255
267
  }
256
268
  }
257
269
  this.update();
270
+ this.resetCountdownProgress();
271
+ this.rescheduleStepCountdown();
258
272
  this.observer.emit('backward', ts, diff, jumpType, limits?.step, target);
259
273
  }
260
274
  catch (err) {
@@ -505,7 +519,46 @@ let WorkoutRide = (() => {
505
519
  else if (this.currentStep.power && !prevStep.power) {
506
520
  this.stopFreeRide();
507
521
  }
522
+ if (!this.firedCountdownThresholds.has(0)) {
523
+ this.emit('step-countdown', { secondsRemaining: 0 });
524
+ }
508
525
  this.emit('step-changed', this.getDashboardDisplayProperties());
526
+ this.resetCountdownProgress();
527
+ this.rescheduleStepCountdown();
528
+ }
529
+ clearCountdownTimers() {
530
+ this.countdownTimers.forEach(t => clearTimeout(t));
531
+ this.countdownTimers = [];
532
+ }
533
+ resetCountdownProgress() {
534
+ this.firedCountdownThresholds = new Set();
535
+ }
536
+ rescheduleStepCountdown() {
537
+ this.clearCountdownTimers();
538
+ if (this.state !== 'active')
539
+ return;
540
+ const duration = this.currentLimits?.duration;
541
+ const remaining = this.currentLimits?.remaining;
542
+ const trainingTime = this.trainingTime;
543
+ if (!hasValidDuration(duration) || remaining === undefined || trainingTime === undefined)
544
+ return;
545
+ const stepEnd = trainingTime + remaining;
546
+ STEP_COUNTDOWN_THRESHOLDS.forEach(secondsRemaining => {
547
+ if (this.firedCountdownThresholds.has(secondsRemaining))
548
+ return;
549
+ const targetTrainingTime = stepEnd - secondsRemaining;
550
+ if (targetTrainingTime < trainingTime)
551
+ return;
552
+ const wallClockDeadline = this.tsStart + (this.offset ?? 0) + (targetTrainingTime - (this.manualTimeOffset ?? 0)) * 1000;
553
+ const delayMs = wallClockDeadline - Date.now();
554
+ if (delayMs < 0)
555
+ return;
556
+ const timer = setTimeout(() => {
557
+ this.firedCountdownThresholds.add(secondsRemaining);
558
+ this.emit('step-countdown', { secondsRemaining });
559
+ }, delayMs);
560
+ this.countdownTimers.push(timer);
561
+ });
509
562
  }
510
563
  checkIfDone() {
511
564
  const time = (Date.now() - this.tsStart - (this.offset ?? 0)) / 1000 + (this.manualTimeOffset ?? 0);
@@ -688,6 +741,7 @@ let WorkoutRide = (() => {
688
741
  clearInterval(this.updateInterval);
689
742
  this.updateInterval = undefined;
690
743
  }
744
+ this.clearCountdownTimers();
691
745
  }
692
746
  getCyclingModeText() {
693
747
  const deviceRide = useDeviceRide();
@@ -55,6 +55,7 @@ export declare class ActivitiesRepository {
55
55
  protected logError(err: Error, fn: string, logProps?: any): void;
56
56
  protected getRepo(): JsonRepository;
57
57
  protected getRouteList(): import("../../../routes").RouteListService;
58
+ protected isMobile(): boolean;
58
59
  protected getBindings(): import("../../../api").IncyclistBindings;
59
60
  protected getUnitConverter(): import("../../../i18n").UnitConverterService;
60
61
  }
@@ -1,9 +1,9 @@
1
1
  import { IncyclistPageService } from "../../base/pages";
2
2
  import { CurrentRideDisplayProps, CurrentRideState, IObserver, RideType } from "../../types";
3
- import { AnyRidePageDisplayProps, GPXRidePageDisplayProps, IRidePageService, PrevRidesRowProps, RideMenuProps, RidePageDisplayProps, StartGateProps, VideoRidePageDisplayProps, WorkoutDashboardLine, WorkoutGestureHint, WorkoutGraphActuals, WorkoutGraphPlan, WorkoutRideMenuProps, WorkoutRidePageDisplayProps, WorkoutUpcomingSteps } from "./types";
3
+ import { AnyRidePageDisplayProps, GPXRidePageDisplayProps, IRidePageService, PrevRidesRowProps, LoadControlButtonLabels, RideMenuProps, RidePageDisplayProps, StartGateProps, VideoRidePageDisplayProps, WorkoutDashboardLine, WorkoutGestureHint, WorkoutGraphActuals, WorkoutGraphPlan, WorkoutRideMenuProps, WorkoutRidePageDisplayProps, WorkoutUpcomingSteps } from "./types";
4
4
  import { ISecretBinding } from "../../api/secret";
5
5
  import { PrevRidesListDisplayProps } from "../../activities";
6
- import type { LoadButtonMode, PowerAdjustmentResult, WorkoutDisplayProperties } from "../../workouts/ride/types";
6
+ import type { LoadButtonMode, PowerAdjustmentResult, StepCountdownTick, WorkoutDisplayProperties } from "../../workouts/ride/types";
7
7
  import type { Workout } from "../../workouts/base/model";
8
8
  import type { StepDefinition } from "../../workouts/base/model/types";
9
9
  export declare class RidePageService extends IncyclistPageService implements IRidePageService {
@@ -16,6 +16,7 @@ export declare class RidePageService extends IncyclistPageService implements IRi
16
16
  protected menuProps: RideMenuProps | null;
17
17
  protected isInitialized: boolean;
18
18
  protected startGateProps: StartGateProps | null;
19
+ protected onDeviceModeChangedBound: any;
19
20
  protected prevRidesVisibleRows: number;
20
21
  protected prevRidesMode: 'condensed' | 'list';
21
22
  protected gestureHintDismissed: boolean;
@@ -74,6 +75,8 @@ export declare class RidePageService extends IncyclistPageService implements IRi
74
75
  getLoadButtonMode(): LoadButtonMode;
75
76
  protected getWorkoutRideDisplayProps(): WorkoutRidePageDisplayProps;
76
77
  protected onWorkoutUpdate(): void;
78
+ protected onWorkoutStepChanged(update?: WorkoutDisplayProperties): void;
79
+ protected onWorkoutStepCountdown(tick?: StepCountdownTick): void;
77
80
  protected onWorkoutFinished(): void;
78
81
  protected finishRide(): void;
79
82
  protected subscribeToWorkoutObserver(): void;
@@ -89,6 +92,13 @@ export declare class RidePageService extends IncyclistPageService implements IRi
89
92
  canStepBack: boolean;
90
93
  canStepForward: boolean;
91
94
  };
95
+ protected getLoadControlProps(): {
96
+ visible: boolean;
97
+ label?: 'Load' | 'Gear';
98
+ buttons?: LoadControlButtonLabels;
99
+ };
100
+ protected getNominalErgWatts(magnitude: number): number;
101
+ protected onDeviceModeChanged(): void;
92
102
  protected getLastLogTime(): number;
93
103
  protected getElapsedActivityTime(): number;
94
104
  protected getEmptyWorkoutDisplayProps(): WorkoutRidePageDisplayProps;
@@ -129,5 +139,6 @@ export declare class RidePageService extends IncyclistPageService implements IRi
129
139
  protected getActivityRide(): import("../../activities").ActivityRideService;
130
140
  protected getUserSettings(): import("../../settings").UserSettingsService;
131
141
  protected getOnlineStatusMonitoring(): import("../../monitoring").OnlineStateMonitoringService;
142
+ protected getDeviceConfiguration(): import("../../devices").DeviceConfigurationService;
132
143
  }
133
144
  export declare const getRidePageService: () => IRidePageService;
@@ -9,11 +9,23 @@ export interface StartGateProps {
9
9
  title: string;
10
10
  body: string;
11
11
  }
12
+ export interface LoadControlButtonLabels {
13
+ inc1: string;
14
+ dec1: string;
15
+ inc5: string;
16
+ dec5: string;
17
+ }
12
18
  export interface RideMenuProps {
13
19
  showResume?: boolean;
14
20
  finished?: boolean;
15
21
  canStepBack?: boolean;
16
22
  canStepForward?: boolean;
23
+ loadControl?: {
24
+ visible: boolean;
25
+ label?: 'Load' | 'Gear';
26
+ buttons?: LoadControlButtonLabels;
27
+ };
28
+ showRideSettings?: boolean;
17
29
  }
18
30
  export interface RidePageDisplayProps {
19
31
  rideState: CurrentRideState;
@@ -139,6 +151,12 @@ export interface WorkoutDashboardLine {
139
151
  export type WorkoutRideMenuProps = RideMenuProps & {
140
152
  canStepBack: boolean;
141
153
  canStepForward: boolean;
154
+ loadControl: {
155
+ visible: boolean;
156
+ label?: 'Load' | 'Gear';
157
+ buttons?: LoadControlButtonLabels;
158
+ };
159
+ showRideSettings: boolean;
142
160
  };
143
161
  export interface WorkoutGestureHint {
144
162
  visible: boolean;
@@ -20,6 +20,8 @@ export declare class WorkoutRide extends IncyclistService {
20
20
  protected tsCurrent: number;
21
21
  protected trainingTime: number;
22
22
  protected currentLimits: ActiveWorkoutLimit;
23
+ private countdownTimers;
24
+ private firedCountdownThresholds;
23
25
  protected updateInterval: NodeJS.Timeout;
24
26
  protected currentStep: StepDefinition;
25
27
  protected isFreeRide: boolean;
@@ -56,6 +58,9 @@ export declare class WorkoutRide extends IncyclistService {
56
58
  getObserver(): Observer;
57
59
  protected update(startIfInitialized?: boolean): void;
58
60
  private onStepChange;
61
+ private clearCountdownTimers;
62
+ private resetCountdownProgress;
63
+ private rescheduleStepCountdown;
59
64
  private checkIfDone;
60
65
  protected startFreeRide(): Promise<void>;
61
66
  protected stopFreeRide(): Promise<void>;
@@ -39,3 +39,6 @@ export interface WorkoutDisplayProperties {
39
39
  dec5: string;
40
40
  };
41
41
  }
42
+ export interface StepCountdownTick {
43
+ secondsRemaining: 4 | 3 | 2 | 1 | 0;
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.7.118",
3
+ "version": "1.7.120",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.27"
6
6
  },