incyclist-services 1.7.77 → 1.7.79
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/workouts/base/graph/series.js +48 -5
- package/lib/cjs/workouts/page/service.js +18 -3
- package/lib/cjs/workouts/ride/page/service.js +29 -31
- package/lib/cjs/workouts/ride/service.js +1 -1
- package/lib/esm/workouts/base/graph/series.js +46 -5
- package/lib/esm/workouts/page/service.js +18 -3
- package/lib/esm/workouts/ride/page/service.js +30 -32
- package/lib/esm/workouts/ride/service.js +1 -1
- package/lib/types/workouts/base/graph/series.d.ts +12 -1
- package/lib/types/workouts/page/service.d.ts +1 -0
- package/lib/types/workouts/page/types.d.ts +1 -0
- package/lib/types/workouts/ride/page/service.d.ts +1 -2
- package/lib/types/workouts/ride/page/types.d.ts +2 -3
- package/lib/types/workouts/ride/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WORKOUT_ZONE_COLORS = void 0;
|
|
4
4
|
exports.getStepDuration = getStepDuration;
|
|
5
5
|
exports.getStepPower = getStepPower;
|
|
6
|
+
exports.getStepTargetText = getStepTargetText;
|
|
7
|
+
exports.getFlattenedSteps = getFlattenedSteps;
|
|
6
8
|
exports.getWorkoutGraphSeries = getWorkoutGraphSeries;
|
|
7
9
|
const model_1 = require("../model");
|
|
8
10
|
exports.WORKOUT_ZONE_COLORS = [
|
|
@@ -95,6 +97,36 @@ function getStepPower(step, ftp) {
|
|
|
95
97
|
const stepText = formatPowerText(low, high, unit, rangePrefix);
|
|
96
98
|
return appendConvertedSuffix(stepText, low, high, unit, ftp);
|
|
97
99
|
}
|
|
100
|
+
function resolvePowerWatts(power, ftp) {
|
|
101
|
+
if (!power)
|
|
102
|
+
return {};
|
|
103
|
+
const toWatts = (v) => {
|
|
104
|
+
if (v === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
if (power.type === 'watt')
|
|
107
|
+
return Math.round(v);
|
|
108
|
+
return ftp === undefined ? undefined : Math.round(v / 100 * ftp);
|
|
109
|
+
};
|
|
110
|
+
return { min: toWatts(power.min), max: toWatts(power.max) };
|
|
111
|
+
}
|
|
112
|
+
function formatRange(low, high, unit, rangePrefix = '') {
|
|
113
|
+
if (low !== undefined && high !== undefined && Math.round(low) !== Math.round(high))
|
|
114
|
+
return `${rangePrefix}${Math.round(low)}-${Math.round(high)}${unit}`;
|
|
115
|
+
const v = high ?? low;
|
|
116
|
+
return v === undefined ? '' : `${Math.round(v)}${unit}`;
|
|
117
|
+
}
|
|
118
|
+
function getStepTargetText(step, ftp, powerOverrideWatts) {
|
|
119
|
+
const { min: minPower, max: maxPower } = powerOverrideWatts ?? resolvePowerWatts(step.power, ftp);
|
|
120
|
+
const ascending = step.steady || step.cooldown === false;
|
|
121
|
+
const lowPower = ascending ? minPower : maxPower;
|
|
122
|
+
const highPower = ascending ? maxPower : minPower;
|
|
123
|
+
const rangePrefix = step.steady ? '' : 'Ramp ';
|
|
124
|
+
const powerText = formatRange(lowPower, highPower, 'W', rangePrefix);
|
|
125
|
+
const hrText = formatRange(step.hrm?.min, step.hrm?.max, 'HR');
|
|
126
|
+
const cadenceText = formatRange(step.cadence?.min, step.cadence?.max, ' rpm');
|
|
127
|
+
const parts = [powerText, hrText, cadenceText].filter(Boolean);
|
|
128
|
+
return parts.length ? parts.join(' at ') : 'free';
|
|
129
|
+
}
|
|
98
130
|
function coerceToWorkout(workout) {
|
|
99
131
|
if (workout instanceof model_1.Workout)
|
|
100
132
|
return workout;
|
|
@@ -139,15 +171,27 @@ function buildRampBars(wo, step, stepStart, ftp, absValues) {
|
|
|
139
171
|
}
|
|
140
172
|
return bars;
|
|
141
173
|
}
|
|
142
|
-
function
|
|
143
|
-
const { ftp, absValues = false } = opts ?? {};
|
|
174
|
+
function getFlattenedSteps(workout) {
|
|
144
175
|
const wo = coerceToWorkout(workout);
|
|
145
176
|
if (!wo)
|
|
146
177
|
return [];
|
|
147
|
-
const
|
|
178
|
+
const result = [];
|
|
148
179
|
let stepStart = 0;
|
|
149
180
|
for (let limits = wo.getLimits(0, true); limits !== undefined; limits = wo.getLimits(stepStart + 0.01, true)) {
|
|
150
181
|
const step = limits.step;
|
|
182
|
+
const duration = step.getDuration();
|
|
183
|
+
result.push({ step, start: stepStart, end: stepStart + duration, duration });
|
|
184
|
+
stepStart += duration;
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
function getWorkoutGraphSeries(workout, opts) {
|
|
189
|
+
const { ftp, absValues = false } = opts ?? {};
|
|
190
|
+
const wo = coerceToWorkout(workout);
|
|
191
|
+
if (!wo)
|
|
192
|
+
return [];
|
|
193
|
+
const bars = [];
|
|
194
|
+
getFlattenedSteps(wo).forEach(({ step, start: stepStart }) => {
|
|
151
195
|
if (step.steady) {
|
|
152
196
|
const bar = buildSteadyBar(step, stepStart, ftp, absValues);
|
|
153
197
|
if (bar)
|
|
@@ -156,7 +200,6 @@ function getWorkoutGraphSeries(workout, opts) {
|
|
|
156
200
|
else {
|
|
157
201
|
bars.push(...buildRampBars(wo, step, stepStart, ftp, absValues));
|
|
158
202
|
}
|
|
159
|
-
|
|
160
|
-
}
|
|
203
|
+
});
|
|
161
204
|
return bars;
|
|
162
205
|
}
|
|
@@ -74,6 +74,7 @@ let WorkoutListPageService = (() => {
|
|
|
74
74
|
__runInitializers(_classThis, _classExtraInitializers);
|
|
75
75
|
}
|
|
76
76
|
groupFilter = (__runInitializers(this, _instanceExtraInitializers), null);
|
|
77
|
+
detailWorkoutId = null;
|
|
77
78
|
importPhase;
|
|
78
79
|
importGroup;
|
|
79
80
|
importingFileName;
|
|
@@ -132,7 +133,7 @@ let WorkoutListPageService = (() => {
|
|
|
132
133
|
const workouts = groups.selected ? allWorkouts.filter(w => w.group === groups.selected) : allWorkouts;
|
|
133
134
|
const selectedId = service.getSelected()?.id ?? null;
|
|
134
135
|
const isEmpty = allWorkouts.length === 0;
|
|
135
|
-
return { pageType: 'list', loading, upcoming, groups, workouts, selectedId, isEmpty };
|
|
136
|
+
return { pageType: 'list', loading, upcoming, groups, workouts, selectedId, isEmpty, detailWorkoutId: this.detailWorkoutId };
|
|
136
137
|
}
|
|
137
138
|
catch (err) {
|
|
138
139
|
this.logError(err, 'getPageDisplayProps');
|
|
@@ -199,10 +200,24 @@ let WorkoutListPageService = (() => {
|
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
202
|
onOpenDetails(id) {
|
|
202
|
-
|
|
203
|
+
try {
|
|
204
|
+
this.detailWorkoutId = id;
|
|
205
|
+
this.logEvent({ message: 'details opened', id });
|
|
206
|
+
this.emitPageUpdate();
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
this.logError(err, 'onOpenDetails');
|
|
210
|
+
}
|
|
203
211
|
}
|
|
204
212
|
onCloseDetails() {
|
|
205
|
-
|
|
213
|
+
try {
|
|
214
|
+
this.detailWorkoutId = null;
|
|
215
|
+
this.logEvent({ message: 'details closed' });
|
|
216
|
+
this.emitPageUpdate();
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
this.logError(err, 'onCloseDetails');
|
|
220
|
+
}
|
|
206
221
|
}
|
|
207
222
|
onSetFtp(ftp) {
|
|
208
223
|
try {
|
|
@@ -383,48 +383,46 @@ let WorkoutRidePageService = (() => {
|
|
|
383
383
|
};
|
|
384
384
|
}
|
|
385
385
|
buildUpcomingSteps(current, ftp) {
|
|
386
|
+
const empty = { previous: null, current: null, upcoming: [], hasMore: false };
|
|
386
387
|
if (!current)
|
|
387
|
-
return
|
|
388
|
+
return empty;
|
|
388
389
|
const limits = this.getWorkoutRide().getCurrentLimits();
|
|
389
390
|
if (!limits)
|
|
390
|
-
return
|
|
391
|
+
return empty;
|
|
391
392
|
const currentStep = {
|
|
392
|
-
label:
|
|
393
|
+
label: (0, graph_1.getStepTargetText)(limits.step ?? {}, ftp, { min: limits.minPower, max: limits.maxPower }),
|
|
393
394
|
targetPower: limits.targetPower ?? null,
|
|
394
395
|
duration: limits.duration,
|
|
395
396
|
remaining: limits.remaining,
|
|
396
397
|
isCurrent: true
|
|
397
398
|
};
|
|
398
399
|
const elapsedTime = this.getElapsedActivityTime();
|
|
399
|
-
const
|
|
400
|
-
const currentIndex =
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
|
|
400
|
+
const flattened = (0, graph_1.getFlattenedSteps)(current);
|
|
401
|
+
const currentIndex = flattened.findIndex(s => s.start <= elapsedTime && s.end > elapsedTime);
|
|
402
|
+
const toDisplay = (entry) => ({
|
|
403
|
+
label: (0, graph_1.getStepTargetText)(entry.step, ftp),
|
|
404
|
+
targetPower: this.getStepAbsolutePower(entry.step, ftp),
|
|
405
|
+
duration: entry.duration,
|
|
406
|
+
remaining: null,
|
|
407
|
+
isCurrent: false
|
|
408
|
+
});
|
|
409
|
+
if (currentIndex === -1)
|
|
410
|
+
return { previous: null, current: currentStep, upcoming: [], hasMore: false };
|
|
411
|
+
const previous = currentIndex > 0 ? toDisplay(flattened[currentIndex - 1]) : null;
|
|
412
|
+
const upcomingEntries = flattened.slice(currentIndex + 1, currentIndex + 1 + UPCOMING_STEPS_COUNT);
|
|
413
|
+
const upcoming = upcomingEntries.map(toDisplay);
|
|
414
|
+
const hasMore = currentIndex + 1 + UPCOMING_STEPS_COUNT < flattened.length;
|
|
415
|
+
return { previous, current: currentStep, upcoming, hasMore };
|
|
411
416
|
}
|
|
412
417
|
buildDashboardLine(wo) {
|
|
413
418
|
const limits = this.getWorkoutRide().getCurrentLimits();
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
formatCurrentStepLabel(limits) {
|
|
423
|
-
if (limits.targetPower === undefined || limits.targetPower === null)
|
|
424
|
-
return 'free';
|
|
425
|
-
if (limits.minPower !== undefined && limits.maxPower !== undefined && limits.minPower !== limits.maxPower)
|
|
426
|
-
return `${Math.round(limits.minPower)}-${Math.round(limits.maxPower)}W`;
|
|
427
|
-
return `${Math.round(limits.targetPower)}W`;
|
|
419
|
+
const title = wo.title ?? '';
|
|
420
|
+
if (!limits)
|
|
421
|
+
return { text: title, mode: wo.mode ?? null };
|
|
422
|
+
const target = (0, graph_1.getStepTargetText)(limits.step ?? {}, wo.ftp, { min: limits.minPower, max: limits.maxPower });
|
|
423
|
+
const duration = (0, graph_1.getStepDuration)({ duration: limits.duration });
|
|
424
|
+
const text = title ? `${target} for ${duration} - ${title}` : `${target} for ${duration}`;
|
|
425
|
+
return { text, mode: wo.mode ?? null };
|
|
428
426
|
}
|
|
429
427
|
getStepAbsolutePower(step, ftp) {
|
|
430
428
|
const p = step.power;
|
|
@@ -461,8 +459,8 @@ let WorkoutRidePageService = (() => {
|
|
|
461
459
|
startGateProps: null,
|
|
462
460
|
title: '',
|
|
463
461
|
graph: { bars: [], ftp: 0, ftpLine: 0, domain: { x: [0, 0], y: [0, 0] } },
|
|
464
|
-
steps: { current: null, upcoming: [] },
|
|
465
|
-
dashboard: {
|
|
462
|
+
steps: { previous: null, current: null, upcoming: [], hasMore: false },
|
|
463
|
+
dashboard: { text: '', mode: null }
|
|
466
464
|
};
|
|
467
465
|
}
|
|
468
466
|
updatePageDisplay() {
|
|
@@ -498,7 +498,7 @@ let WorkoutRide = (() => {
|
|
|
498
498
|
request.maxCadence = limits?.cadence?.max ? Math.round(limits.cadence.max) : undefined;
|
|
499
499
|
request.minHrm = limits.hrm?.min ? Math.round(limits.hrm.min) : undefined;
|
|
500
500
|
request.maxHrm = limits.hrm?.max ? Math.round(limits.hrm.max) : undefined;
|
|
501
|
-
this.currentLimits = { ...request, duration: limits.duration, remaining: limits.remaining };
|
|
501
|
+
this.currentLimits = { ...request, duration: limits.duration, remaining: limits.remaining, step: limits.step };
|
|
502
502
|
}
|
|
503
503
|
getZoomParameters(time) {
|
|
504
504
|
let start, stop;
|
|
@@ -89,6 +89,36 @@ export function getStepPower(step, ftp) {
|
|
|
89
89
|
const stepText = formatPowerText(low, high, unit, rangePrefix);
|
|
90
90
|
return appendConvertedSuffix(stepText, low, high, unit, ftp);
|
|
91
91
|
}
|
|
92
|
+
function resolvePowerWatts(power, ftp) {
|
|
93
|
+
if (!power)
|
|
94
|
+
return {};
|
|
95
|
+
const toWatts = (v) => {
|
|
96
|
+
if (v === undefined)
|
|
97
|
+
return undefined;
|
|
98
|
+
if (power.type === 'watt')
|
|
99
|
+
return Math.round(v);
|
|
100
|
+
return ftp === undefined ? undefined : Math.round(v / 100 * ftp);
|
|
101
|
+
};
|
|
102
|
+
return { min: toWatts(power.min), max: toWatts(power.max) };
|
|
103
|
+
}
|
|
104
|
+
function formatRange(low, high, unit, rangePrefix = '') {
|
|
105
|
+
if (low !== undefined && high !== undefined && Math.round(low) !== Math.round(high))
|
|
106
|
+
return `${rangePrefix}${Math.round(low)}-${Math.round(high)}${unit}`;
|
|
107
|
+
const v = high ?? low;
|
|
108
|
+
return v === undefined ? '' : `${Math.round(v)}${unit}`;
|
|
109
|
+
}
|
|
110
|
+
export function getStepTargetText(step, ftp, powerOverrideWatts) {
|
|
111
|
+
const { min: minPower, max: maxPower } = powerOverrideWatts ?? resolvePowerWatts(step.power, ftp);
|
|
112
|
+
const ascending = step.steady || step.cooldown === false;
|
|
113
|
+
const lowPower = ascending ? minPower : maxPower;
|
|
114
|
+
const highPower = ascending ? maxPower : minPower;
|
|
115
|
+
const rangePrefix = step.steady ? '' : 'Ramp ';
|
|
116
|
+
const powerText = formatRange(lowPower, highPower, 'W', rangePrefix);
|
|
117
|
+
const hrText = formatRange(step.hrm?.min, step.hrm?.max, 'HR');
|
|
118
|
+
const cadenceText = formatRange(step.cadence?.min, step.cadence?.max, ' rpm');
|
|
119
|
+
const parts = [powerText, hrText, cadenceText].filter(Boolean);
|
|
120
|
+
return parts.length ? parts.join(' at ') : 'free';
|
|
121
|
+
}
|
|
92
122
|
function coerceToWorkout(workout) {
|
|
93
123
|
if (workout instanceof Workout)
|
|
94
124
|
return workout;
|
|
@@ -133,15 +163,27 @@ function buildRampBars(wo, step, stepStart, ftp, absValues) {
|
|
|
133
163
|
}
|
|
134
164
|
return bars;
|
|
135
165
|
}
|
|
136
|
-
export function
|
|
137
|
-
const { ftp, absValues = false } = opts ?? {};
|
|
166
|
+
export function getFlattenedSteps(workout) {
|
|
138
167
|
const wo = coerceToWorkout(workout);
|
|
139
168
|
if (!wo)
|
|
140
169
|
return [];
|
|
141
|
-
const
|
|
170
|
+
const result = [];
|
|
142
171
|
let stepStart = 0;
|
|
143
172
|
for (let limits = wo.getLimits(0, true); limits !== undefined; limits = wo.getLimits(stepStart + 0.01, true)) {
|
|
144
173
|
const step = limits.step;
|
|
174
|
+
const duration = step.getDuration();
|
|
175
|
+
result.push({ step, start: stepStart, end: stepStart + duration, duration });
|
|
176
|
+
stepStart += duration;
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
export function getWorkoutGraphSeries(workout, opts) {
|
|
181
|
+
const { ftp, absValues = false } = opts ?? {};
|
|
182
|
+
const wo = coerceToWorkout(workout);
|
|
183
|
+
if (!wo)
|
|
184
|
+
return [];
|
|
185
|
+
const bars = [];
|
|
186
|
+
getFlattenedSteps(wo).forEach(({ step, start: stepStart }) => {
|
|
145
187
|
if (step.steady) {
|
|
146
188
|
const bar = buildSteadyBar(step, stepStart, ftp, absValues);
|
|
147
189
|
if (bar)
|
|
@@ -150,7 +192,6 @@ export function getWorkoutGraphSeries(workout, opts) {
|
|
|
150
192
|
else {
|
|
151
193
|
bars.push(...buildRampBars(wo, step, stepStart, ftp, absValues));
|
|
152
194
|
}
|
|
153
|
-
|
|
154
|
-
}
|
|
195
|
+
});
|
|
155
196
|
return bars;
|
|
156
197
|
}
|
|
@@ -71,6 +71,7 @@ let WorkoutListPageService = (() => {
|
|
|
71
71
|
__runInitializers(_classThis, _classExtraInitializers);
|
|
72
72
|
}
|
|
73
73
|
groupFilter = (__runInitializers(this, _instanceExtraInitializers), null);
|
|
74
|
+
detailWorkoutId = null;
|
|
74
75
|
importPhase;
|
|
75
76
|
importGroup;
|
|
76
77
|
importingFileName;
|
|
@@ -129,7 +130,7 @@ let WorkoutListPageService = (() => {
|
|
|
129
130
|
const workouts = groups.selected ? allWorkouts.filter(w => w.group === groups.selected) : allWorkouts;
|
|
130
131
|
const selectedId = service.getSelected()?.id ?? null;
|
|
131
132
|
const isEmpty = allWorkouts.length === 0;
|
|
132
|
-
return { pageType: 'list', loading, upcoming, groups, workouts, selectedId, isEmpty };
|
|
133
|
+
return { pageType: 'list', loading, upcoming, groups, workouts, selectedId, isEmpty, detailWorkoutId: this.detailWorkoutId };
|
|
133
134
|
}
|
|
134
135
|
catch (err) {
|
|
135
136
|
this.logError(err, 'getPageDisplayProps');
|
|
@@ -196,10 +197,24 @@ let WorkoutListPageService = (() => {
|
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
199
|
onOpenDetails(id) {
|
|
199
|
-
|
|
200
|
+
try {
|
|
201
|
+
this.detailWorkoutId = id;
|
|
202
|
+
this.logEvent({ message: 'details opened', id });
|
|
203
|
+
this.emitPageUpdate();
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
this.logError(err, 'onOpenDetails');
|
|
207
|
+
}
|
|
200
208
|
}
|
|
201
209
|
onCloseDetails() {
|
|
202
|
-
|
|
210
|
+
try {
|
|
211
|
+
this.detailWorkoutId = null;
|
|
212
|
+
this.logEvent({ message: 'details closed' });
|
|
213
|
+
this.emitPageUpdate();
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
this.logError(err, 'onCloseDetails');
|
|
217
|
+
}
|
|
203
218
|
}
|
|
204
219
|
onSetFtp(ftp) {
|
|
205
220
|
try {
|
|
@@ -39,7 +39,7 @@ import { IncyclistPageService } from "../../../base/pages";
|
|
|
39
39
|
import { useRideDisplay } from "../../../ride/display";
|
|
40
40
|
import { useActivityRide } from "../../../activities";
|
|
41
41
|
import { useWorkoutRide } from "../service";
|
|
42
|
-
import {
|
|
42
|
+
import { getFlattenedSteps, getStepDuration, getStepTargetText, getWorkoutGraphSeries } from "../../base/graph";
|
|
43
43
|
const BACKGROUND_PAUSE_TIMEOUT_MS = 300000;
|
|
44
44
|
const UPCOMING_STEPS_COUNT = 3;
|
|
45
45
|
const DEFAULT_LOAD_INCREMENT = 1;
|
|
@@ -380,48 +380,46 @@ let WorkoutRidePageService = (() => {
|
|
|
380
380
|
};
|
|
381
381
|
}
|
|
382
382
|
buildUpcomingSteps(current, ftp) {
|
|
383
|
+
const empty = { previous: null, current: null, upcoming: [], hasMore: false };
|
|
383
384
|
if (!current)
|
|
384
|
-
return
|
|
385
|
+
return empty;
|
|
385
386
|
const limits = this.getWorkoutRide().getCurrentLimits();
|
|
386
387
|
if (!limits)
|
|
387
|
-
return
|
|
388
|
+
return empty;
|
|
388
389
|
const currentStep = {
|
|
389
|
-
label:
|
|
390
|
+
label: getStepTargetText(limits.step ?? {}, ftp, { min: limits.minPower, max: limits.maxPower }),
|
|
390
391
|
targetPower: limits.targetPower ?? null,
|
|
391
392
|
duration: limits.duration,
|
|
392
393
|
remaining: limits.remaining,
|
|
393
394
|
isCurrent: true
|
|
394
395
|
};
|
|
395
396
|
const elapsedTime = this.getElapsedActivityTime();
|
|
396
|
-
const
|
|
397
|
-
const currentIndex =
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
|
|
397
|
+
const flattened = getFlattenedSteps(current);
|
|
398
|
+
const currentIndex = flattened.findIndex(s => s.start <= elapsedTime && s.end > elapsedTime);
|
|
399
|
+
const toDisplay = (entry) => ({
|
|
400
|
+
label: getStepTargetText(entry.step, ftp),
|
|
401
|
+
targetPower: this.getStepAbsolutePower(entry.step, ftp),
|
|
402
|
+
duration: entry.duration,
|
|
403
|
+
remaining: null,
|
|
404
|
+
isCurrent: false
|
|
405
|
+
});
|
|
406
|
+
if (currentIndex === -1)
|
|
407
|
+
return { previous: null, current: currentStep, upcoming: [], hasMore: false };
|
|
408
|
+
const previous = currentIndex > 0 ? toDisplay(flattened[currentIndex - 1]) : null;
|
|
409
|
+
const upcomingEntries = flattened.slice(currentIndex + 1, currentIndex + 1 + UPCOMING_STEPS_COUNT);
|
|
410
|
+
const upcoming = upcomingEntries.map(toDisplay);
|
|
411
|
+
const hasMore = currentIndex + 1 + UPCOMING_STEPS_COUNT < flattened.length;
|
|
412
|
+
return { previous, current: currentStep, upcoming, hasMore };
|
|
408
413
|
}
|
|
409
414
|
buildDashboardLine(wo) {
|
|
410
415
|
const limits = this.getWorkoutRide().getCurrentLimits();
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
formatCurrentStepLabel(limits) {
|
|
420
|
-
if (limits.targetPower === undefined || limits.targetPower === null)
|
|
421
|
-
return 'free';
|
|
422
|
-
if (limits.minPower !== undefined && limits.maxPower !== undefined && limits.minPower !== limits.maxPower)
|
|
423
|
-
return `${Math.round(limits.minPower)}-${Math.round(limits.maxPower)}W`;
|
|
424
|
-
return `${Math.round(limits.targetPower)}W`;
|
|
416
|
+
const title = wo.title ?? '';
|
|
417
|
+
if (!limits)
|
|
418
|
+
return { text: title, mode: wo.mode ?? null };
|
|
419
|
+
const target = getStepTargetText(limits.step ?? {}, wo.ftp, { min: limits.minPower, max: limits.maxPower });
|
|
420
|
+
const duration = getStepDuration({ duration: limits.duration });
|
|
421
|
+
const text = title ? `${target} for ${duration} - ${title}` : `${target} for ${duration}`;
|
|
422
|
+
return { text, mode: wo.mode ?? null };
|
|
425
423
|
}
|
|
426
424
|
getStepAbsolutePower(step, ftp) {
|
|
427
425
|
const p = step.power;
|
|
@@ -458,8 +456,8 @@ let WorkoutRidePageService = (() => {
|
|
|
458
456
|
startGateProps: null,
|
|
459
457
|
title: '',
|
|
460
458
|
graph: { bars: [], ftp: 0, ftpLine: 0, domain: { x: [0, 0], y: [0, 0] } },
|
|
461
|
-
steps: { current: null, upcoming: [] },
|
|
462
|
-
dashboard: {
|
|
459
|
+
steps: { previous: null, current: null, upcoming: [], hasMore: false },
|
|
460
|
+
dashboard: { text: '', mode: null }
|
|
463
461
|
};
|
|
464
462
|
}
|
|
465
463
|
updatePageDisplay() {
|
|
@@ -495,7 +495,7 @@ let WorkoutRide = (() => {
|
|
|
495
495
|
request.maxCadence = limits?.cadence?.max ? Math.round(limits.cadence.max) : undefined;
|
|
496
496
|
request.minHrm = limits.hrm?.min ? Math.round(limits.hrm.min) : undefined;
|
|
497
497
|
request.maxHrm = limits.hrm?.max ? Math.round(limits.hrm.max) : undefined;
|
|
498
|
-
this.currentLimits = { ...request, duration: limits.duration, remaining: limits.remaining };
|
|
498
|
+
this.currentLimits = { ...request, duration: limits.duration, remaining: limits.remaining, step: limits.step };
|
|
499
499
|
}
|
|
500
500
|
getZoomParameters(time) {
|
|
501
501
|
let start, stop;
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import { Workout } from "../model";
|
|
1
|
+
import { Step, Workout } from "../model";
|
|
2
2
|
import { StepDefinition } from "../model/types";
|
|
3
3
|
import { WorkoutGraphPlanBar, WorkoutGraphSeriesOptions } from "./types";
|
|
4
4
|
export declare const WORKOUT_ZONE_COLORS: string[];
|
|
5
5
|
export declare function getStepDuration(step: StepDefinition): string;
|
|
6
6
|
export declare function getStepPower(step: StepDefinition, ftp?: number): string;
|
|
7
|
+
export declare function getStepTargetText(step: StepDefinition, ftp?: number, powerOverrideWatts?: {
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
}): string;
|
|
11
|
+
export interface FlattenedStep {
|
|
12
|
+
step: Step;
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
duration: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function getFlattenedSteps(workout: Workout): FlattenedStep[];
|
|
7
18
|
export declare function getWorkoutGraphSeries(workout: Workout, opts?: WorkoutGraphSeriesOptions): WorkoutGraphPlanBar[];
|
|
@@ -6,6 +6,7 @@ import { WorkoutListService } from "../list/service";
|
|
|
6
6
|
import { GroupFilterProps, IWorkoutListPageService, UpcomingTrainingProps, WorkoutDetailsProps, WorkoutImportDisplayProps, WorkoutListItemProps, WorkoutListPageDisplayProps } from "./types";
|
|
7
7
|
export declare class WorkoutListPageService extends IncyclistPageService implements IWorkoutListPageService {
|
|
8
8
|
protected groupFilter: string | null;
|
|
9
|
+
protected detailWorkoutId: string | null;
|
|
9
10
|
protected importPhase: 'landing' | 'importing' | 'result' | 'error' | undefined;
|
|
10
11
|
protected importGroup: string;
|
|
11
12
|
protected importingFileName: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IncyclistPageService } from "../../../base/pages";
|
|
2
2
|
import type { IObserver } from "../../../base/typedefs";
|
|
3
3
|
import type { CurrentRideState, RideType } from "../../../types";
|
|
4
|
-
import type {
|
|
4
|
+
import type { WorkoutDisplayProperties } from "../types";
|
|
5
5
|
import type { Workout } from "../../base/model";
|
|
6
6
|
import type { StepDefinition } from "../../base/model/types";
|
|
7
7
|
import type { IWorkoutRidePageService, WorkoutDashboardLine, WorkoutGraphActuals, WorkoutGraphPlan, WorkoutRideMenuProps, WorkoutRidePageDisplayProps, WorkoutUpcomingSteps } from "./types";
|
|
@@ -51,7 +51,6 @@ export declare class WorkoutRidePageService extends IncyclistPageService impleme
|
|
|
51
51
|
protected buildGraphPlan(current: Workout | undefined, ftp: number): WorkoutGraphPlan;
|
|
52
52
|
protected buildUpcomingSteps(current: Workout | undefined, ftp: number): WorkoutUpcomingSteps;
|
|
53
53
|
protected buildDashboardLine(wo: WorkoutDisplayProperties): WorkoutDashboardLine;
|
|
54
|
-
protected formatCurrentStepLabel(limits: ActiveWorkoutLimit): string;
|
|
55
54
|
protected getStepAbsolutePower(step: StepDefinition, ftp?: number): number | null;
|
|
56
55
|
protected getStepFlags(): {
|
|
57
56
|
canStepBack: boolean;
|
|
@@ -28,13 +28,12 @@ export interface WorkoutStepDisplay {
|
|
|
28
28
|
isCurrent: boolean;
|
|
29
29
|
}
|
|
30
30
|
export interface WorkoutUpcomingSteps {
|
|
31
|
+
previous: WorkoutStepDisplay | null;
|
|
31
32
|
current: WorkoutStepDisplay | null;
|
|
32
33
|
upcoming: WorkoutStepDisplay[];
|
|
34
|
+
hasMore: boolean;
|
|
33
35
|
}
|
|
34
36
|
export interface WorkoutDashboardLine {
|
|
35
|
-
targetPower: number | null;
|
|
36
|
-
targetDuration: number;
|
|
37
|
-
remaining: number;
|
|
38
37
|
text: string;
|
|
39
38
|
mode: string | null;
|
|
40
39
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Workout } from "../base/model";
|
|
2
|
+
import { StepDefinition } from "../base/model/types";
|
|
2
3
|
export interface WorkoutRequest {
|
|
3
4
|
time: number;
|
|
4
5
|
minPower?: number;
|
|
@@ -12,6 +13,7 @@ export interface WorkoutRequest {
|
|
|
12
13
|
export interface ActiveWorkoutLimit extends WorkoutRequest {
|
|
13
14
|
duration: number;
|
|
14
15
|
remaining: number;
|
|
16
|
+
step?: StepDefinition;
|
|
15
17
|
}
|
|
16
18
|
export interface WorkoutDisplayProperties {
|
|
17
19
|
workout?: Workout;
|