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.
- package/lib/cjs/ride/page/service.js +9 -1
- package/lib/cjs/workouts/ride/service.js +54 -0
- package/lib/esm/ride/page/service.js +9 -1
- package/lib/esm/workouts/ride/service.js +54 -0
- package/lib/types/ride/page/service.d.ts +3 -1
- package/lib/types/workouts/ride/service.d.ts +5 -0
- package/lib/types/workouts/ride/types.d.ts +3 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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.
|
|
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>;
|