incyclist-services 1.7.118 → 1.7.119

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.
@@ -103,7 +103,8 @@ let RidePageService = (() => {
103
103
  this.eventHandler['route-update'] = this.onRouteUpdate.bind(this);
104
104
  this.eventHandler['view-changed'] = this.onViewChanged.bind(this);
105
105
  this.eventHandler['prev-rides-update'] = this.onPrevRidesTick.bind(this);
106
- this.workoutEventHandler['step-changed'] = this.onWorkoutUpdate.bind(this);
106
+ this.workoutEventHandler['step-changed'] = this.onWorkoutStepChanged.bind(this);
107
+ this.workoutEventHandler['step-countdown'] = this.onWorkoutStepCountdown.bind(this);
107
108
  this.workoutEventHandler['update'] = this.onWorkoutUpdate.bind(this);
108
109
  this.workoutEventHandler['forward'] = this.onWorkoutUpdate.bind(this);
109
110
  this.workoutEventHandler['backward'] = this.onWorkoutUpdate.bind(this);
@@ -631,6 +632,13 @@ let RidePageService = (() => {
631
632
  onWorkoutUpdate() {
632
633
  this.updatePageDisplay();
633
634
  }
635
+ onWorkoutStepChanged(update) {
636
+ this.onWorkoutUpdate();
637
+ this.getPageObserver()?.emit('step-changed', update);
638
+ }
639
+ onWorkoutStepCountdown(tick) {
640
+ this.getPageObserver()?.emit('step-countdown', tick);
641
+ }
634
642
  onWorkoutFinished() {
635
643
  try {
636
644
  if (this.isRideType('Workout'))
@@ -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)();
@@ -100,7 +100,8 @@ let RidePageService = (() => {
100
100
  this.eventHandler['route-update'] = this.onRouteUpdate.bind(this);
101
101
  this.eventHandler['view-changed'] = this.onViewChanged.bind(this);
102
102
  this.eventHandler['prev-rides-update'] = this.onPrevRidesTick.bind(this);
103
- this.workoutEventHandler['step-changed'] = this.onWorkoutUpdate.bind(this);
103
+ this.workoutEventHandler['step-changed'] = this.onWorkoutStepChanged.bind(this);
104
+ this.workoutEventHandler['step-countdown'] = this.onWorkoutStepCountdown.bind(this);
104
105
  this.workoutEventHandler['update'] = this.onWorkoutUpdate.bind(this);
105
106
  this.workoutEventHandler['forward'] = this.onWorkoutUpdate.bind(this);
106
107
  this.workoutEventHandler['backward'] = this.onWorkoutUpdate.bind(this);
@@ -628,6 +629,13 @@ let RidePageService = (() => {
628
629
  onWorkoutUpdate() {
629
630
  this.updatePageDisplay();
630
631
  }
632
+ onWorkoutStepChanged(update) {
633
+ this.onWorkoutUpdate();
634
+ this.getPageObserver()?.emit('step-changed', update);
635
+ }
636
+ onWorkoutStepCountdown(tick) {
637
+ this.getPageObserver()?.emit('step-countdown', tick);
638
+ }
631
639
  onWorkoutFinished() {
632
640
  try {
633
641
  if (this.isRideType('Workout'))
@@ -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();
@@ -3,7 +3,7 @@ import { CurrentRideDisplayProps, CurrentRideState, IObserver, RideType } from "
3
3
  import { AnyRidePageDisplayProps, GPXRidePageDisplayProps, IRidePageService, PrevRidesRowProps, 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 {
@@ -74,6 +74,8 @@ export declare class RidePageService extends IncyclistPageService implements IRi
74
74
  getLoadButtonMode(): LoadButtonMode;
75
75
  protected getWorkoutRideDisplayProps(): WorkoutRidePageDisplayProps;
76
76
  protected onWorkoutUpdate(): void;
77
+ protected onWorkoutStepChanged(update?: WorkoutDisplayProperties): void;
78
+ protected onWorkoutStepCountdown(tick?: StepCountdownTick): void;
77
79
  protected onWorkoutFinished(): void;
78
80
  protected finishRide(): void;
79
81
  protected subscribeToWorkoutObserver(): void;
@@ -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.119",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.27"
6
6
  },