incyclist-services 1.7.76 → 1.7.77
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/activities/list/service.js +1 -1
- package/lib/cjs/activities/ride/service.js +2 -1
- package/lib/cjs/workouts/base/graph/index.js +18 -0
- package/lib/cjs/workouts/base/graph/series.js +162 -0
- package/lib/cjs/workouts/base/graph/types.js +2 -0
- package/lib/cjs/workouts/base/model/index.js +0 -1
- package/lib/cjs/workouts/calendar/service.js +14 -0
- package/lib/cjs/workouts/index.js +2 -0
- package/lib/cjs/workouts/list/cards/index.js +0 -1
- package/lib/cjs/workouts/list/index.js +0 -1
- package/lib/cjs/workouts/list/pageService.js +362 -5
- package/lib/cjs/workouts/list/service.js +39 -12
- package/lib/cjs/workouts/page/index.js +17 -0
- package/lib/cjs/workouts/page/service.js +455 -0
- package/lib/cjs/workouts/page/types.js +2 -0
- package/lib/cjs/workouts/ride/index.js +1 -0
- package/lib/cjs/workouts/ride/page/index.js +18 -0
- package/lib/cjs/workouts/ride/page/service.js +500 -0
- package/lib/cjs/workouts/ride/page/types.js +2 -0
- package/lib/cjs/workouts/types.js +2 -0
- package/lib/esm/activities/list/service.js +1 -1
- package/lib/esm/activities/ride/service.js +2 -1
- package/lib/esm/workouts/base/graph/index.js +2 -0
- package/lib/esm/workouts/base/graph/series.js +156 -0
- package/lib/esm/workouts/base/graph/types.js +1 -0
- package/lib/esm/workouts/base/model/index.js +0 -1
- package/lib/esm/workouts/calendar/service.js +14 -0
- package/lib/esm/workouts/index.js +2 -0
- package/lib/esm/workouts/list/cards/index.js +0 -1
- package/lib/esm/workouts/list/index.js +0 -1
- package/lib/esm/workouts/list/pageService.js +362 -5
- package/lib/esm/workouts/list/service.js +39 -12
- package/lib/esm/workouts/page/index.js +1 -0
- package/lib/esm/workouts/page/service.js +451 -0
- package/lib/esm/workouts/page/types.js +1 -0
- package/lib/esm/workouts/ride/index.js +1 -0
- package/lib/esm/workouts/ride/page/index.js +2 -0
- package/lib/esm/workouts/ride/page/service.js +496 -0
- package/lib/esm/workouts/ride/page/types.js +1 -0
- package/lib/esm/workouts/types.js +1 -0
- package/lib/types/types.d.ts +1 -0
- package/lib/types/workouts/base/graph/index.d.ts +2 -0
- package/lib/types/workouts/base/graph/series.d.ts +7 -0
- package/lib/types/workouts/base/graph/types.d.ts +11 -0
- package/lib/types/workouts/base/model/index.d.ts +0 -1
- package/lib/types/workouts/base/parsers/intervals/parser.d.ts +2 -1
- package/lib/types/workouts/calendar/service.d.ts +1 -0
- package/lib/types/workouts/index.d.ts +2 -0
- package/lib/types/workouts/list/cards/index.d.ts +0 -1
- package/lib/types/workouts/list/index.d.ts +0 -1
- package/lib/types/workouts/list/pageService.d.ts +136 -4
- package/lib/types/workouts/list/service.d.ts +4 -0
- package/lib/types/workouts/list/types.d.ts +1 -1
- package/lib/types/workouts/page/index.d.ts +1 -0
- package/lib/types/workouts/page/service.d.ts +57 -0
- package/lib/types/workouts/page/types.d.ts +95 -0
- package/lib/types/workouts/ride/index.d.ts +1 -0
- package/lib/types/workouts/ride/page/index.d.ts +2 -0
- package/lib/types/workouts/ride/page/service.d.ts +71 -0
- package/lib/types/workouts/ride/page/types.d.ts +72 -0
- package/lib/types/workouts/ride/service.d.ts +2 -1
- package/lib/types/workouts/ride/types.d.ts +1 -0
- package/lib/types/workouts/types.d.ts +8 -0
- package/package.json +1 -1
|
@@ -404,7 +404,7 @@ let ActivityListService = (() => {
|
|
|
404
404
|
startPos,
|
|
405
405
|
segment: activity?.segment,
|
|
406
406
|
started: activity ? new Date(activity.startTime) : undefined,
|
|
407
|
-
showMap:
|
|
407
|
+
showMap: activity?.routeType !== 'None',
|
|
408
408
|
points,
|
|
409
409
|
activity,
|
|
410
410
|
exports: this.exports,
|
|
@@ -425,10 +425,11 @@ let ActivityRideService = (() => {
|
|
|
425
425
|
const route = this.getRouteList().getSelected();
|
|
426
426
|
const startSettings = this.getRouteList().getStartSettings();
|
|
427
427
|
const isFreeRide = startSettings?.type === 'Free-Ride';
|
|
428
|
+
const isWorkout = this.activity?.routeType === 'None';
|
|
428
429
|
const showSave = this.activity !== undefined && !this.isSaveDone;
|
|
429
430
|
const showContinue = this.state !== 'completed';
|
|
430
431
|
const hasGPX = this.activity?.logs?.some((log) => !!log.lat && !!log.lng);
|
|
431
|
-
const showMap = hasGPX || isFreeRide || (route?.description?.hasGpx);
|
|
432
|
+
const showMap = hasGPX || isFreeRide || isWorkout || (route?.description?.hasGpx);
|
|
432
433
|
const preview = showMap ? undefined : route?.description?.previewUrl;
|
|
433
434
|
if (!this.isSummaryShown) {
|
|
434
435
|
this.logEvent({ message: 'activity summary shown', showSave, showContinue, showMap, fileLink: this.activity?.fitFileName });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./series"), exports);
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WORKOUT_ZONE_COLORS = void 0;
|
|
4
|
+
exports.getStepDuration = getStepDuration;
|
|
5
|
+
exports.getStepPower = getStepPower;
|
|
6
|
+
exports.getWorkoutGraphSeries = getWorkoutGraphSeries;
|
|
7
|
+
const model_1 = require("../model");
|
|
8
|
+
exports.WORKOUT_ZONE_COLORS = [
|
|
9
|
+
'white',
|
|
10
|
+
'#7f7f7f',
|
|
11
|
+
'#338cff',
|
|
12
|
+
'#59bf59',
|
|
13
|
+
'#ffcc3f',
|
|
14
|
+
'#ff6639',
|
|
15
|
+
'#ff330c',
|
|
16
|
+
'#ea39ff'
|
|
17
|
+
];
|
|
18
|
+
function getMinPower(step, ftp) {
|
|
19
|
+
if (step?.power === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
const p = step.power;
|
|
22
|
+
if (p.max !== undefined && p.min !== undefined && p.max === p.min)
|
|
23
|
+
return 0;
|
|
24
|
+
if (p.min === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
let res = Math.round(p.min);
|
|
27
|
+
if (p.type === 'watt' && ftp !== undefined)
|
|
28
|
+
res = Math.round(p.min / ftp * 100);
|
|
29
|
+
return res;
|
|
30
|
+
}
|
|
31
|
+
function getMaxPower(step, ftp) {
|
|
32
|
+
if (step?.power === undefined)
|
|
33
|
+
return 0;
|
|
34
|
+
const p = step.power;
|
|
35
|
+
if (p.max === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
let res = Math.round(p.max);
|
|
38
|
+
if (p.type === 'watt' && ftp !== undefined)
|
|
39
|
+
res = Math.round(p.max / ftp * 100);
|
|
40
|
+
return res;
|
|
41
|
+
}
|
|
42
|
+
function getZone(power, ftp) {
|
|
43
|
+
if (power === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (ftp === undefined)
|
|
46
|
+
return 0;
|
|
47
|
+
if (power <= 55)
|
|
48
|
+
return 1;
|
|
49
|
+
if (power <= 75)
|
|
50
|
+
return 2;
|
|
51
|
+
if (power <= 90)
|
|
52
|
+
return 3;
|
|
53
|
+
if (power <= 105)
|
|
54
|
+
return 4;
|
|
55
|
+
if (power <= 120)
|
|
56
|
+
return 5;
|
|
57
|
+
if (power <= 150)
|
|
58
|
+
return 6;
|
|
59
|
+
return 7;
|
|
60
|
+
}
|
|
61
|
+
function getStepDuration(step) {
|
|
62
|
+
if (step.duration <= 60)
|
|
63
|
+
return `${step.duration}s`;
|
|
64
|
+
if (step.duration <= 3600)
|
|
65
|
+
return `${step.duration / 60}min`;
|
|
66
|
+
return `${step.duration / 3600}h`;
|
|
67
|
+
}
|
|
68
|
+
function formatPowerText(low, high, unit, rangePrefix) {
|
|
69
|
+
if (low !== undefined && high !== undefined && low !== high)
|
|
70
|
+
return `${rangePrefix}${Math.round(low)}-${Math.round(high)}${unit}`;
|
|
71
|
+
if (high !== undefined)
|
|
72
|
+
return `${Math.round(high)}${unit}`;
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
function appendConvertedSuffix(text, low, high, unit, ftp) {
|
|
76
|
+
if (text === '' || ftp === undefined)
|
|
77
|
+
return text;
|
|
78
|
+
const isPct = unit === '%';
|
|
79
|
+
const convert = (v) => isPct ? Math.round(v / 100 * ftp) : Math.round(v * 100 / ftp);
|
|
80
|
+
const suffixUnit = isPct ? 'W' : '%';
|
|
81
|
+
const range = (low !== undefined && high !== undefined && low !== high)
|
|
82
|
+
? `${convert(low)}-${convert(high)}`
|
|
83
|
+
: `${convert(high)}`;
|
|
84
|
+
return `${text} (${range}${suffixUnit})`;
|
|
85
|
+
}
|
|
86
|
+
function getStepPower(step, ftp) {
|
|
87
|
+
if (step.power === undefined)
|
|
88
|
+
return '';
|
|
89
|
+
const { min, max, type } = step.power;
|
|
90
|
+
const unit = type === 'watt' ? 'W' : '%';
|
|
91
|
+
const ascending = step.steady || step.cooldown === false;
|
|
92
|
+
const low = ascending ? min : max;
|
|
93
|
+
const high = ascending ? max : min;
|
|
94
|
+
const rangePrefix = step.steady ? '' : 'Ramp ';
|
|
95
|
+
const stepText = formatPowerText(low, high, unit, rangePrefix);
|
|
96
|
+
return appendConvertedSuffix(stepText, low, high, unit, ftp);
|
|
97
|
+
}
|
|
98
|
+
function coerceToWorkout(workout) {
|
|
99
|
+
if (workout instanceof model_1.Workout)
|
|
100
|
+
return workout;
|
|
101
|
+
try {
|
|
102
|
+
return new model_1.Workout(workout);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function buildSteadyBar(step, stepStart, ftp, absValues) {
|
|
109
|
+
const x0 = stepStart;
|
|
110
|
+
const x = stepStart + step.getDuration();
|
|
111
|
+
let y = getMaxPower(step, ftp);
|
|
112
|
+
let y0 = getMinPower(step, ftp);
|
|
113
|
+
const zone = getZone(y, ftp);
|
|
114
|
+
if (absValues && ftp) {
|
|
115
|
+
y = ftp * y / 100;
|
|
116
|
+
y0 = ftp * y0 / 100;
|
|
117
|
+
}
|
|
118
|
+
if (y === undefined || Number.isNaN(y))
|
|
119
|
+
return undefined;
|
|
120
|
+
if (y0 !== undefined && !Number.isNaN(y0))
|
|
121
|
+
return { x, x0, y, y0, zone };
|
|
122
|
+
if (y0 === undefined)
|
|
123
|
+
return { x, x0, y, y0: 0, zone };
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
function buildRampBars(wo, step, stepStart, ftp, absValues) {
|
|
127
|
+
const bars = [];
|
|
128
|
+
const stepSize = step.getDuration() / 10;
|
|
129
|
+
for (let i = 0; i < 10; i++) {
|
|
130
|
+
const x0 = stepStart + i * stepSize;
|
|
131
|
+
const x = x0 + stepSize;
|
|
132
|
+
const limit = wo.getLimits(x0 + 0.01);
|
|
133
|
+
let y = getMaxPower(limit, ftp);
|
|
134
|
+
const zone = getZone(y, ftp);
|
|
135
|
+
if (absValues && ftp)
|
|
136
|
+
y = ftp * y / 100;
|
|
137
|
+
if (y !== undefined && !Number.isNaN(y))
|
|
138
|
+
bars.push({ x, x0, y, y0: 0, zone });
|
|
139
|
+
}
|
|
140
|
+
return bars;
|
|
141
|
+
}
|
|
142
|
+
function getWorkoutGraphSeries(workout, opts) {
|
|
143
|
+
const { ftp, absValues = false } = opts ?? {};
|
|
144
|
+
const wo = coerceToWorkout(workout);
|
|
145
|
+
if (!wo)
|
|
146
|
+
return [];
|
|
147
|
+
const bars = [];
|
|
148
|
+
let stepStart = 0;
|
|
149
|
+
for (let limits = wo.getLimits(0, true); limits !== undefined; limits = wo.getLimits(stepStart + 0.01, true)) {
|
|
150
|
+
const step = limits.step;
|
|
151
|
+
if (step.steady) {
|
|
152
|
+
const bar = buildSteadyBar(step, stepStart, ftp, absValues);
|
|
153
|
+
if (bar)
|
|
154
|
+
bars.push(bar);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
bars.push(...buildRampBars(wo, step, stepStart, ftp, absValues));
|
|
158
|
+
}
|
|
159
|
+
stepStart += step.getDuration();
|
|
160
|
+
}
|
|
161
|
+
return bars;
|
|
162
|
+
}
|
|
@@ -136,6 +136,20 @@ let WorkoutCalendarService = (() => {
|
|
|
136
136
|
}
|
|
137
137
|
return [];
|
|
138
138
|
}
|
|
139
|
+
getScheduledToday() {
|
|
140
|
+
try {
|
|
141
|
+
if (!this.workouts)
|
|
142
|
+
return undefined;
|
|
143
|
+
const now = new Date();
|
|
144
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
145
|
+
const entry = this.workouts.find(w => w.day?.toLocaleDateString() === today.toLocaleDateString());
|
|
146
|
+
return entry ? this.getScheduledWorkout(entry) : undefined;
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
this.logError(err, 'getScheduledToday');
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
139
153
|
setActive(active) {
|
|
140
154
|
try {
|
|
141
155
|
this.listActive = active;
|
|
@@ -17,3 +17,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./list"), exports);
|
|
18
18
|
__exportStar(require("./ride"), exports);
|
|
19
19
|
__exportStar(require("./base/model"), exports);
|
|
20
|
+
__exportStar(require("./base/graph"), exports);
|
|
21
|
+
__exportStar(require("./page"), exports);
|
|
@@ -17,4 +17,3 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./WorkoutCreateCard"), exports);
|
|
18
18
|
__exportStar(require("./WorkoutImportCard"), exports);
|
|
19
19
|
__exportStar(require("./WorkoutCard"), exports);
|
|
20
|
-
__exportStar(require("./types"), exports);
|
|
@@ -16,4 +16,3 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./service"), exports);
|
|
18
18
|
__exportStar(require("./cards"), exports);
|
|
19
|
-
__exportStar(require("./pageService"), exports);
|
|
@@ -39,6 +39,12 @@ const types_1 = require("../../base/types");
|
|
|
39
39
|
const pages_1 = require("../../base/pages");
|
|
40
40
|
const decorators_1 = require("../../base/decorators");
|
|
41
41
|
const appstate_1 = require("../../appstate");
|
|
42
|
+
const observer_1 = require("../../base/types/observer");
|
|
43
|
+
const settings_1 = require("../../settings");
|
|
44
|
+
const calendar_1 = require("../calendar");
|
|
45
|
+
const service_1 = require("./service");
|
|
46
|
+
const DEFAULT_IMPORT_GROUP = 'My Workouts';
|
|
47
|
+
const UPCOMING_COLLAPSED_COUNT = 3;
|
|
42
48
|
let WorkoutListPageService = (() => {
|
|
43
49
|
let _classDecorators = [types_1.Singleton];
|
|
44
50
|
let _classDescriptor;
|
|
@@ -46,34 +52,66 @@ let WorkoutListPageService = (() => {
|
|
|
46
52
|
let _classThis;
|
|
47
53
|
let _classSuper = pages_1.IncyclistPageService;
|
|
48
54
|
let _instanceExtraInitializers = [];
|
|
55
|
+
let _getWorkoutList_decorators;
|
|
56
|
+
let _getWorkoutCalendar_decorators;
|
|
57
|
+
let _getUserSettings_decorators;
|
|
49
58
|
let _getAppState_decorators;
|
|
50
59
|
var WorkoutListPageService = class extends _classSuper {
|
|
51
60
|
static { _classThis = this; }
|
|
52
61
|
static {
|
|
53
62
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
63
|
+
_getWorkoutList_decorators = [decorators_1.Injectable];
|
|
64
|
+
_getWorkoutCalendar_decorators = [decorators_1.Injectable];
|
|
65
|
+
_getUserSettings_decorators = [decorators_1.Injectable];
|
|
54
66
|
_getAppState_decorators = [decorators_1.Injectable];
|
|
67
|
+
__esDecorate(this, null, _getWorkoutList_decorators, { kind: "method", name: "getWorkoutList", static: false, private: false, access: { has: obj => "getWorkoutList" in obj, get: obj => obj.getWorkoutList }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
68
|
+
__esDecorate(this, null, _getWorkoutCalendar_decorators, { kind: "method", name: "getWorkoutCalendar", static: false, private: false, access: { has: obj => "getWorkoutCalendar" in obj, get: obj => obj.getWorkoutCalendar }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
69
|
+
__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);
|
|
55
70
|
__esDecorate(this, null, _getAppState_decorators, { kind: "method", name: "getAppState", static: false, private: false, access: { has: obj => "getAppState" in obj, get: obj => obj.getAppState }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
56
71
|
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
57
72
|
WorkoutListPageService = _classThis = _classDescriptor.value;
|
|
58
73
|
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
59
74
|
__runInitializers(_classThis, _classExtraInitializers);
|
|
60
75
|
}
|
|
76
|
+
groupFilter = (__runInitializers(this, _instanceExtraInitializers), null);
|
|
77
|
+
importPhase;
|
|
78
|
+
importGroup;
|
|
79
|
+
importingFileName;
|
|
80
|
+
importResult;
|
|
81
|
+
importError;
|
|
82
|
+
listObserver;
|
|
83
|
+
listUpdateHandler = this.emitPageUpdate.bind(this);
|
|
84
|
+
calendarUpdateHandler = this.emitPageUpdate.bind(this);
|
|
61
85
|
constructor() {
|
|
62
86
|
super('WorkoutListPage');
|
|
63
|
-
__runInitializers(this, _instanceExtraInitializers);
|
|
64
87
|
}
|
|
65
88
|
openPage() {
|
|
66
89
|
try {
|
|
67
90
|
super.openPage();
|
|
68
91
|
this.logEvent({ message: 'page shown', page: 'Workouts' });
|
|
69
|
-
|
|
92
|
+
const { observer } = this.getWorkoutList().open();
|
|
93
|
+
this.listObserver = observer;
|
|
94
|
+
this.listObserver?.on('started', this.listUpdateHandler);
|
|
95
|
+
this.listObserver?.on('loading', this.listUpdateHandler);
|
|
96
|
+
this.listObserver?.on('loaded', this.listUpdateHandler);
|
|
97
|
+
this.listObserver?.on('updated', this.listUpdateHandler);
|
|
98
|
+
this.getWorkoutCalendar().on('updated', this.calendarUpdateHandler);
|
|
99
|
+
this.emitPageUpdate();
|
|
70
100
|
}
|
|
71
101
|
catch (err) {
|
|
72
102
|
this.logError(err, 'openPage');
|
|
73
103
|
}
|
|
104
|
+
return this.getPageObserver();
|
|
74
105
|
}
|
|
75
106
|
closePage() {
|
|
76
107
|
try {
|
|
108
|
+
this.getWorkoutList().close();
|
|
109
|
+
this.listObserver?.off('started', this.listUpdateHandler);
|
|
110
|
+
this.listObserver?.off('loading', this.listUpdateHandler);
|
|
111
|
+
this.listObserver?.off('loaded', this.listUpdateHandler);
|
|
112
|
+
this.listObserver?.off('updated', this.listUpdateHandler);
|
|
113
|
+
this.getWorkoutCalendar().off('updated', this.calendarUpdateHandler);
|
|
114
|
+
delete this.listObserver;
|
|
77
115
|
this.logEvent({ message: 'page closed', page: 'Workouts' });
|
|
78
116
|
super.closePage();
|
|
79
117
|
}
|
|
@@ -82,10 +120,329 @@ let WorkoutListPageService = (() => {
|
|
|
82
120
|
}
|
|
83
121
|
}
|
|
84
122
|
getPageDisplayProps() {
|
|
85
|
-
|
|
86
|
-
|
|
123
|
+
try {
|
|
124
|
+
if (!this.getAppState().hasFeature('MOBILE_WORKOUTS')) {
|
|
125
|
+
return { pageType: 'placeholder' };
|
|
126
|
+
}
|
|
127
|
+
const service = this.getWorkoutList();
|
|
128
|
+
const loading = service.isStillLoading();
|
|
129
|
+
const upcoming = this.getUpcomingTrainingProps();
|
|
130
|
+
const allWorkouts = this.getAllWorkoutItems();
|
|
131
|
+
const groups = this.getGroupFilterProps();
|
|
132
|
+
const workouts = groups.selected ? allWorkouts.filter(w => w.group === groups.selected) : allWorkouts;
|
|
133
|
+
const selectedId = service.getSelected()?.id ?? null;
|
|
134
|
+
const isEmpty = allWorkouts.length === 0;
|
|
135
|
+
return { pageType: 'list', loading, upcoming, groups, workouts, selectedId, isEmpty };
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
this.logError(err, 'getPageDisplayProps');
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
getWorkoutDetailsProps(id) {
|
|
143
|
+
try {
|
|
144
|
+
const card = this.findWorkoutCard(id);
|
|
145
|
+
if (!card)
|
|
146
|
+
return null;
|
|
147
|
+
const workout = card.getData();
|
|
148
|
+
const settingsProps = card.openSettings();
|
|
149
|
+
const isScheduled = card.getCardType() === 'ScheduledWorkout';
|
|
150
|
+
return {
|
|
151
|
+
id,
|
|
152
|
+
title: card.getTitle(),
|
|
153
|
+
description: workout?.description,
|
|
154
|
+
duration: settingsProps.duration,
|
|
155
|
+
workout,
|
|
156
|
+
ftp: settingsProps.settings?.ftp,
|
|
157
|
+
ftpRequired: settingsProps.ftpRequired,
|
|
158
|
+
useErgMode: settingsProps.settings?.useErgMode,
|
|
159
|
+
canStart: settingsProps.canStart,
|
|
160
|
+
canStartWorkoutOnly: settingsProps.canStartWorkoutOnly,
|
|
161
|
+
groups: settingsProps.categories,
|
|
162
|
+
group: settingsProps.category,
|
|
163
|
+
canDelete: card.canDelete(),
|
|
164
|
+
isScheduled,
|
|
165
|
+
date: isScheduled ? settingsProps.date : undefined
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
this.logError(err, 'getWorkoutDetailsProps');
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
getImportDisplayProps() {
|
|
174
|
+
try {
|
|
175
|
+
const phase = this.importPhase ?? 'landing';
|
|
176
|
+
const knownGroups = this.getKnownGroups();
|
|
177
|
+
const group = this.importGroup ?? this.getLastUsedImportGroup();
|
|
178
|
+
const props = { phase, group, knownGroups };
|
|
179
|
+
if (phase === 'importing')
|
|
180
|
+
props.importing = { fileName: this.importingFileName };
|
|
181
|
+
if (phase === 'result')
|
|
182
|
+
props.result = this.importResult;
|
|
183
|
+
if (phase === 'error')
|
|
184
|
+
props.error = this.importError;
|
|
185
|
+
return props;
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
this.logError(err, 'getImportDisplayProps');
|
|
189
|
+
return { phase: 'landing', group: DEFAULT_IMPORT_GROUP, knownGroups: [] };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
onSelectGroup(group) {
|
|
193
|
+
try {
|
|
194
|
+
this.groupFilter = group;
|
|
195
|
+
this.emitPageUpdate();
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
this.logError(err, 'onSelectGroup');
|
|
87
199
|
}
|
|
88
|
-
|
|
200
|
+
}
|
|
201
|
+
onOpenDetails(id) {
|
|
202
|
+
this.logEvent({ message: 'details opened', id });
|
|
203
|
+
}
|
|
204
|
+
onCloseDetails() {
|
|
205
|
+
this.logEvent({ message: 'details closed' });
|
|
206
|
+
}
|
|
207
|
+
onSetFtp(ftp) {
|
|
208
|
+
try {
|
|
209
|
+
const current = this.getWorkoutList().getStartSettings();
|
|
210
|
+
this.getWorkoutList().setStartSettings({ ...current, ftp });
|
|
211
|
+
this.emitPageUpdate();
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
this.logError(err, 'onSetFtp');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
onSetErgMode(enabled) {
|
|
218
|
+
try {
|
|
219
|
+
const current = this.getWorkoutList().getStartSettings();
|
|
220
|
+
this.getWorkoutList().setStartSettings({ ...current, useErgMode: enabled });
|
|
221
|
+
this.emitPageUpdate();
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
this.logError(err, 'onSetErgMode');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
onChangeGroup(id, group) {
|
|
228
|
+
try {
|
|
229
|
+
const card = this.findWorkoutCard(id);
|
|
230
|
+
card?.move(group);
|
|
231
|
+
this.emitPageUpdate();
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
this.logError(err, 'onChangeGroup');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async onDelete(id) {
|
|
238
|
+
try {
|
|
239
|
+
const observer = this.getWorkoutList().deleteWorkout(id);
|
|
240
|
+
const deleted = await observer.wait();
|
|
241
|
+
this.emitPageUpdate();
|
|
242
|
+
return !!deleted;
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
this.logError(err, 'onDelete');
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
onStart(id, opts) {
|
|
250
|
+
try {
|
|
251
|
+
const card = this.findWorkoutCard(id);
|
|
252
|
+
if (!card)
|
|
253
|
+
return;
|
|
254
|
+
const settings = this.getWorkoutList().getStartSettings();
|
|
255
|
+
card.select({ ...settings, noRoute: opts?.noRoute });
|
|
256
|
+
this.getPageObserver()?.emit('start-ride', { id, noRoute: opts?.noRoute });
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
this.logError(err, 'onStart');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
onMarkForRoute(id) {
|
|
263
|
+
try {
|
|
264
|
+
const card = this.findWorkoutCard(id);
|
|
265
|
+
if (!card)
|
|
266
|
+
return;
|
|
267
|
+
const settings = this.getWorkoutList().getStartSettings();
|
|
268
|
+
card.select({ ...settings, noRoute: false });
|
|
269
|
+
this.emitPageUpdate();
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
this.logError(err, 'onMarkForRoute');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
onClearSelection() {
|
|
276
|
+
try {
|
|
277
|
+
this.getWorkoutList().unselect();
|
|
278
|
+
this.emitPageUpdate();
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
this.logError(err, 'onClearSelection');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
onImportOpen() {
|
|
285
|
+
try {
|
|
286
|
+
this.importPhase = 'landing';
|
|
287
|
+
this.importGroup = this.getLastUsedImportGroup();
|
|
288
|
+
delete this.importError;
|
|
289
|
+
delete this.importResult;
|
|
290
|
+
this.emitImportUpdate();
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
this.logError(err, 'onImportOpen');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
onImportGroupChange(name) {
|
|
297
|
+
try {
|
|
298
|
+
this.importGroup = name;
|
|
299
|
+
this.emitImportUpdate();
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
this.logError(err, 'onImportGroupChange');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
onImportFile(file) {
|
|
306
|
+
const observer = new observer_1.Observer();
|
|
307
|
+
try {
|
|
308
|
+
const group = this.importGroup;
|
|
309
|
+
this.importPhase = 'importing';
|
|
310
|
+
this.importingFileName = file?.name;
|
|
311
|
+
this.emitImportUpdate();
|
|
312
|
+
this.getWorkoutList().import(file, { showImportCards: false })
|
|
313
|
+
.then(([card]) => {
|
|
314
|
+
if (group && group !== DEFAULT_IMPORT_GROUP)
|
|
315
|
+
card.move(group);
|
|
316
|
+
this.persistLastUsedImportGroup(group);
|
|
317
|
+
this.importPhase = 'result';
|
|
318
|
+
this.importResult = { workoutName: card.getTitle(), group };
|
|
319
|
+
observer.emit('success');
|
|
320
|
+
this.emitImportUpdate();
|
|
321
|
+
})
|
|
322
|
+
.catch((err) => {
|
|
323
|
+
this.importPhase = 'error';
|
|
324
|
+
this.importError = err?.message;
|
|
325
|
+
observer.emit('error', err);
|
|
326
|
+
this.emitImportUpdate();
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
this.logError(err, 'onImportFile');
|
|
331
|
+
}
|
|
332
|
+
return observer;
|
|
333
|
+
}
|
|
334
|
+
onImportClose() {
|
|
335
|
+
try {
|
|
336
|
+
delete this.importPhase;
|
|
337
|
+
delete this.importingFileName;
|
|
338
|
+
delete this.importResult;
|
|
339
|
+
delete this.importError;
|
|
340
|
+
this.emitImportUpdate();
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
this.logError(err, 'onImportClose');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
getUpcomingTrainingProps() {
|
|
347
|
+
const calendar = this.getWorkoutCalendar();
|
|
348
|
+
const scheduled = calendar.getScheduledWorkouts();
|
|
349
|
+
if (!scheduled || scheduled.length === 0)
|
|
350
|
+
return null;
|
|
351
|
+
const todayId = calendar.getScheduledToday()?.id ?? null;
|
|
352
|
+
const selectedWorkout = this.getWorkoutList().getSelected();
|
|
353
|
+
const items = scheduled.map(w => ({
|
|
354
|
+
id: w.id,
|
|
355
|
+
title: w.workout?.name,
|
|
356
|
+
date: w.day,
|
|
357
|
+
duration: this.formatDuration(w.workout?.duration),
|
|
358
|
+
isToday: todayId !== null && w.id === todayId,
|
|
359
|
+
selected: selectedWorkout?.id !== undefined && selectedWorkout.id === w.workout?.id,
|
|
360
|
+
workout: w.workout
|
|
361
|
+
}));
|
|
362
|
+
return { items, collapsedCount: UPCOMING_COLLAPSED_COUNT, todayId };
|
|
363
|
+
}
|
|
364
|
+
getGroupFilterProps() {
|
|
365
|
+
const available = this.getKnownGroups();
|
|
366
|
+
return { available, selected: this.groupFilter };
|
|
367
|
+
}
|
|
368
|
+
getKnownGroups() {
|
|
369
|
+
const lists = this.getWorkoutList().getLists(false) ?? [];
|
|
370
|
+
return lists.filter(l => l.getId() !== 'scheduled').map(l => l.getTitle());
|
|
371
|
+
}
|
|
372
|
+
getAllWorkoutItems() {
|
|
373
|
+
const lists = this.getWorkoutList().getLists(false) ?? [];
|
|
374
|
+
const items = [];
|
|
375
|
+
lists.forEach(list => {
|
|
376
|
+
if (list.getId() === 'scheduled')
|
|
377
|
+
return;
|
|
378
|
+
list.getCards().forEach(card => {
|
|
379
|
+
if (card.getCardType() !== 'Workout')
|
|
380
|
+
return;
|
|
381
|
+
const workoutCard = card;
|
|
382
|
+
const props = workoutCard.getDisplayProperties();
|
|
383
|
+
items.push({
|
|
384
|
+
id: workoutCard.getId(),
|
|
385
|
+
title: props.title,
|
|
386
|
+
group: list.getTitle(),
|
|
387
|
+
duration: props.duration,
|
|
388
|
+
selected: props.selected,
|
|
389
|
+
canDelete: props.canDelete,
|
|
390
|
+
workout: props.workout
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
return items;
|
|
395
|
+
}
|
|
396
|
+
findWorkoutCard(id) {
|
|
397
|
+
const lists = this.getWorkoutList().getLists(false) ?? [];
|
|
398
|
+
for (const list of lists) {
|
|
399
|
+
const card = list.getCards().find(c => c.getId() === id);
|
|
400
|
+
if (card)
|
|
401
|
+
return card;
|
|
402
|
+
}
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
getLastUsedImportGroup() {
|
|
406
|
+
return this.getUserSettings().get('preferences.workouts.lastImportGroup', DEFAULT_IMPORT_GROUP);
|
|
407
|
+
}
|
|
408
|
+
persistLastUsedImportGroup(group) {
|
|
409
|
+
try {
|
|
410
|
+
this.getUserSettings().set('preferences.workouts.lastImportGroup', group);
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
this.logError(err, 'persistLastUsedImportGroup');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
formatDuration(duration) {
|
|
417
|
+
if (duration === undefined || duration === null)
|
|
418
|
+
return '';
|
|
419
|
+
if (duration < 120)
|
|
420
|
+
return `${duration.toFixed(0)}s`;
|
|
421
|
+
if (duration % 60 === 0)
|
|
422
|
+
return `${duration / 60}min`;
|
|
423
|
+
const secVal = duration % 60;
|
|
424
|
+
const minVal = (duration - secVal) / 60 % 60;
|
|
425
|
+
const h = Math.floor((duration - secVal - minVal * 60) / 3600);
|
|
426
|
+
const sec = secVal < 10 ? `0${secVal}` : secVal;
|
|
427
|
+
const min = minVal < 10 ? `0${minVal}` : minVal;
|
|
428
|
+
if (h > 0)
|
|
429
|
+
return `${h}:${min}:${sec}`;
|
|
430
|
+
return `${min}:${sec}`;
|
|
431
|
+
}
|
|
432
|
+
emitPageUpdate() {
|
|
433
|
+
this.getPageObserver()?.emit('page-update');
|
|
434
|
+
}
|
|
435
|
+
emitImportUpdate() {
|
|
436
|
+
this.getPageObserver()?.emit('import-update');
|
|
437
|
+
}
|
|
438
|
+
getWorkoutList() {
|
|
439
|
+
return (0, service_1.useWorkoutList)();
|
|
440
|
+
}
|
|
441
|
+
getWorkoutCalendar() {
|
|
442
|
+
return (0, calendar_1.useWorkoutCalendar)();
|
|
443
|
+
}
|
|
444
|
+
getUserSettings() {
|
|
445
|
+
return (0, settings_1.useUserSettings)();
|
|
89
446
|
}
|
|
90
447
|
getAppState() {
|
|
91
448
|
return (0, appstate_1.useAppState)();
|