incyclist-services 1.7.87 → 1.7.89
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/appstate/index.js +1 -0
- package/lib/cjs/appstate/toggle-sync.js +174 -0
- package/lib/cjs/routes/base/parsers/tacx/TacxParser.js +1 -4
- package/lib/cjs/routes/base/parsers/utils.js +13 -2
- package/lib/cjs/routes/download/service.js +4 -2
- package/lib/cjs/routes/list/cards/RouteCard.js +1 -1
- package/lib/cjs/routes/list/loaders/db.js +41 -6
- package/lib/cjs/routes/list/loaders/types.js +24 -1
- package/lib/cjs/routes/list/service.js +9 -1
- package/lib/cjs/ui/service.js +7 -0
- package/lib/cjs/workouts/ride/page/service.js +6 -9
- package/lib/cjs/workouts/ride/service.js +29 -22
- package/lib/esm/appstate/index.js +1 -0
- package/lib/esm/appstate/toggle-sync.js +170 -0
- package/lib/esm/routes/base/parsers/tacx/TacxParser.js +2 -5
- package/lib/esm/routes/base/parsers/utils.js +10 -1
- package/lib/esm/routes/download/service.js +4 -2
- package/lib/esm/routes/list/cards/RouteCard.js +1 -1
- package/lib/esm/routes/list/loaders/db.js +36 -1
- package/lib/esm/routes/list/loaders/types.js +22 -0
- package/lib/esm/routes/list/service.js +9 -1
- package/lib/esm/ui/service.js +8 -1
- package/lib/esm/workouts/ride/page/service.js +6 -9
- package/lib/esm/workouts/ride/service.js +29 -22
- package/lib/types/appstate/index.d.ts +1 -0
- package/lib/types/appstate/toggle-sync.d.ts +22 -0
- package/lib/types/routes/base/parsers/utils.d.ts +2 -0
- package/lib/types/routes/list/cards/RouteCard.d.ts +1 -1
- package/lib/types/routes/list/loaders/db.d.ts +2 -0
- package/lib/types/routes/list/loaders/types.d.ts +2 -0
- package/lib/types/ui/service.d.ts +1 -0
- package/lib/types/workouts/ride/page/service.d.ts +0 -1
- package/lib/types/workouts/ride/service.d.ts +1 -0
- package/package.json +1 -1
|
@@ -16,3 +16,4 @@ 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("./types"), exports);
|
|
19
|
+
__exportStar(require("./toggle-sync"), exports);
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
3
|
+
var useValue = arguments.length > 2;
|
|
4
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
5
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
6
|
+
}
|
|
7
|
+
return useValue ? value : void 0;
|
|
8
|
+
};
|
|
9
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
10
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
11
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
12
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
13
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
14
|
+
var _, done = false;
|
|
15
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
16
|
+
var context = {};
|
|
17
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
18
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
19
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
20
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
21
|
+
if (kind === "accessor") {
|
|
22
|
+
if (result === void 0) continue;
|
|
23
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
24
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
25
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
26
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
27
|
+
}
|
|
28
|
+
else if (_ = accept(result)) {
|
|
29
|
+
if (kind === "field") initializers.unshift(_);
|
|
30
|
+
else descriptor[key] = _;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
34
|
+
done = true;
|
|
35
|
+
};
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.useFeatureToggleSync = exports.FeatureToggleSyncService = void 0;
|
|
38
|
+
const api_1 = require("../api");
|
|
39
|
+
const decorators_1 = require("../base/decorators");
|
|
40
|
+
const service_1 = require("../base/service");
|
|
41
|
+
const singleton_1 = require("../base/types/singleton");
|
|
42
|
+
const settings_1 = require("../settings");
|
|
43
|
+
const TOPIC_PREFIX = 'incyclist/features';
|
|
44
|
+
let FeatureToggleSyncService = (() => {
|
|
45
|
+
let _classDecorators = [singleton_1.Singleton];
|
|
46
|
+
let _classDescriptor;
|
|
47
|
+
let _classExtraInitializers = [];
|
|
48
|
+
let _classThis;
|
|
49
|
+
let _classSuper = service_1.IncyclistService;
|
|
50
|
+
let _instanceExtraInitializers = [];
|
|
51
|
+
let _getMessageQueue_decorators;
|
|
52
|
+
let _getUserSettings_decorators;
|
|
53
|
+
var FeatureToggleSyncService = class extends _classSuper {
|
|
54
|
+
static { _classThis = this; }
|
|
55
|
+
static {
|
|
56
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
57
|
+
_getMessageQueue_decorators = [decorators_1.Injectable];
|
|
58
|
+
_getUserSettings_decorators = [decorators_1.Injectable];
|
|
59
|
+
__esDecorate(this, null, _getMessageQueue_decorators, { kind: "method", name: "getMessageQueue", static: false, private: false, access: { has: obj => "getMessageQueue" in obj, get: obj => obj.getMessageQueue }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
60
|
+
__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);
|
|
61
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
62
|
+
FeatureToggleSyncService = _classThis = _classDescriptor.value;
|
|
63
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
64
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
65
|
+
}
|
|
66
|
+
uuid = __runInitializers(this, _instanceExtraInitializers);
|
|
67
|
+
subscribed = false;
|
|
68
|
+
onMessageHandler = this.onMessage.bind(this);
|
|
69
|
+
constructor() {
|
|
70
|
+
super('FeatureToggleSync');
|
|
71
|
+
}
|
|
72
|
+
start(uuid) {
|
|
73
|
+
try {
|
|
74
|
+
if (!uuid)
|
|
75
|
+
return;
|
|
76
|
+
if (this.subscribed && this.uuid === uuid)
|
|
77
|
+
return;
|
|
78
|
+
if (this.subscribed)
|
|
79
|
+
this.stop();
|
|
80
|
+
const mq = this.getMessageQueue();
|
|
81
|
+
if (!mq?.enabled?.()) {
|
|
82
|
+
this.logEvent({ message: 'feature-toggle sync not started', reason: 'mq binding not enabled', uuid });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.uuid = uuid;
|
|
86
|
+
mq.on('mq-message', this.onMessageHandler);
|
|
87
|
+
mq.subscribe(this.getTopic(uuid));
|
|
88
|
+
this.subscribed = true;
|
|
89
|
+
this.logEvent({ message: 'feature-toggle sync started', uuid, topic: this.getTopic(uuid) });
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
this.logError(err, 'start');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
stop() {
|
|
96
|
+
try {
|
|
97
|
+
if (!this.subscribed)
|
|
98
|
+
return;
|
|
99
|
+
const mq = this.getMessageQueue();
|
|
100
|
+
if (mq) {
|
|
101
|
+
mq.off('mq-message', this.onMessageHandler);
|
|
102
|
+
if (this.uuid)
|
|
103
|
+
mq.unsubscribe(this.getTopic(this.uuid));
|
|
104
|
+
}
|
|
105
|
+
this.logEvent({ message: 'feature-toggle sync stopped', uuid: this.uuid });
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
this.logError(err, 'stop');
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
this.subscribed = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
getTopic(uuid) {
|
|
115
|
+
return `${TOPIC_PREFIX}/${uuid}/+`;
|
|
116
|
+
}
|
|
117
|
+
getTopicPrefix() {
|
|
118
|
+
return this.uuid ? `${TOPIC_PREFIX}/${this.uuid}/` : undefined;
|
|
119
|
+
}
|
|
120
|
+
onMessage(topic, message) {
|
|
121
|
+
try {
|
|
122
|
+
const prefix = this.getTopicPrefix();
|
|
123
|
+
if (!prefix || typeof topic !== 'string' || !topic.startsWith(prefix))
|
|
124
|
+
return;
|
|
125
|
+
const toggleName = this.parseToggleName(topic, prefix);
|
|
126
|
+
if (!toggleName) {
|
|
127
|
+
this.logEvent({ message: 'feature-toggle sync: ignoring malformed topic', topic, payload: message });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const payload = this.parsePayload(message);
|
|
131
|
+
if (!payload || typeof payload.value !== 'boolean') {
|
|
132
|
+
this.logEvent({ message: 'feature-toggle sync: ignoring malformed payload', topic, payload: message });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.getUserSettings().set(toggleName, payload.value);
|
|
136
|
+
this.logEvent({ message: 'feature-toggle sync: applied toggle', toggle: toggleName, value: payload.value });
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
this.logError(err, 'onMessage', { topic });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
parseToggleName(topic, prefix) {
|
|
143
|
+
const toggleName = topic.slice(prefix.length);
|
|
144
|
+
if (!toggleName || toggleName.includes('/'))
|
|
145
|
+
return undefined;
|
|
146
|
+
return toggleName;
|
|
147
|
+
}
|
|
148
|
+
parsePayload(message) {
|
|
149
|
+
try {
|
|
150
|
+
let str;
|
|
151
|
+
if (typeof message === 'string')
|
|
152
|
+
str = message;
|
|
153
|
+
else if (message instanceof Uint8Array)
|
|
154
|
+
str = Buffer.from(message.buffer).toString();
|
|
155
|
+
else
|
|
156
|
+
return undefined;
|
|
157
|
+
return JSON.parse(str);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
getMessageQueue() {
|
|
164
|
+
return (0, api_1.getBindings)().mq;
|
|
165
|
+
}
|
|
166
|
+
getUserSettings() {
|
|
167
|
+
return (0, settings_1.useUserSettings)();
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
return FeatureToggleSyncService = _classThis;
|
|
171
|
+
})();
|
|
172
|
+
exports.FeatureToggleSyncService = FeatureToggleSyncService;
|
|
173
|
+
const useFeatureToggleSync = () => new FeatureToggleSyncService();
|
|
174
|
+
exports.useFeatureToggleSync = useFeatureToggleSync;
|
|
@@ -142,10 +142,7 @@ let TacxParser = (() => {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
buildVideoUrl(filePath) {
|
|
145
|
-
|
|
146
|
-
return `video:///${filePath}`;
|
|
147
|
-
}
|
|
148
|
-
return `video://${filePath}`;
|
|
145
|
+
return (0, utils_2.buildVideoUrl)(filePath);
|
|
149
146
|
}
|
|
150
147
|
async buildResult(context) {
|
|
151
148
|
if (!context.rlvData || !context.pgmfData) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUtf8Data = exports.fixIncorrectFileInfo = exports.parseInformations = exports.safeEncode = exports.safeDecode = exports.getReferencedFileInfo = exports.BinaryReader = void 0;
|
|
3
|
+
exports.getUtf8Data = exports.fixIncorrectFileInfo = exports.parseInformations = exports.buildFileUrl = exports.safeEncode = exports.safeDecode = exports.buildVideoUrl = exports.getReferencedFileInfo = exports.BinaryReader = void 0;
|
|
4
4
|
const api_1 = require("../../../api");
|
|
5
5
|
class BinaryReader {
|
|
6
6
|
pos;
|
|
@@ -116,6 +116,16 @@ const isAbsolutePath = (path) => {
|
|
|
116
116
|
return true;
|
|
117
117
|
return false;
|
|
118
118
|
};
|
|
119
|
+
const buildVideoUrl = (filePath) => {
|
|
120
|
+
if (isAbsolutePath(filePath)) {
|
|
121
|
+
if (/^[A-Za-z]:/.test(filePath)) {
|
|
122
|
+
return `video:///${filePath}`;
|
|
123
|
+
}
|
|
124
|
+
return `video://${filePath}`;
|
|
125
|
+
}
|
|
126
|
+
return `video://${filePath}`;
|
|
127
|
+
};
|
|
128
|
+
exports.buildVideoUrl = buildVideoUrl;
|
|
119
129
|
const safeDecode = (path) => {
|
|
120
130
|
try {
|
|
121
131
|
return decodeURIComponent(path);
|
|
@@ -144,6 +154,7 @@ const buildFileUrl = (normalizedPath) => {
|
|
|
144
154
|
}
|
|
145
155
|
return `file://${encodeURI(normalizedPath)}`;
|
|
146
156
|
};
|
|
157
|
+
exports.buildFileUrl = buildFileUrl;
|
|
147
158
|
const buildUrlFromFile = (info, referenced) => {
|
|
148
159
|
if (referenced.url) {
|
|
149
160
|
return referenced.url;
|
|
@@ -159,7 +170,7 @@ const buildUrlFromFile = (info, referenced) => {
|
|
|
159
170
|
return handleFileUrlPath(fileName);
|
|
160
171
|
}
|
|
161
172
|
const normalizedPath = (0, exports.safeDecode)(fileName);
|
|
162
|
-
return buildFileUrl(normalizedPath);
|
|
173
|
+
return (0, exports.buildFileUrl)(normalizedPath);
|
|
163
174
|
};
|
|
164
175
|
const buildAbsolutePathTarget = (fileName, info, scheme) => {
|
|
165
176
|
const inputUrl = info.url;
|
|
@@ -40,6 +40,7 @@ const decorators_1 = require("../../base/decorators");
|
|
|
40
40
|
const service_1 = require("../../base/service");
|
|
41
41
|
const settings_1 = require("../../settings");
|
|
42
42
|
const utils_1 = require("../../utils");
|
|
43
|
+
const utils_2 = require("../base/parsers/utils");
|
|
43
44
|
const api_2 = require("../list/loaders/api");
|
|
44
45
|
const db_1 = require("../list/loaders/db");
|
|
45
46
|
const types_1 = require("./types");
|
|
@@ -217,8 +218,9 @@ let RouteDownloadService = (() => {
|
|
|
217
218
|
session.on('progress', onProgress);
|
|
218
219
|
session.on('error', onError);
|
|
219
220
|
session.once('done', () => {
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
const localUrl = (0, utils_2.buildVideoUrl)(file);
|
|
222
|
+
this.logEvent({ message: 'download finished ', title, id, url, localUrl });
|
|
223
|
+
observer.emit('done', localUrl);
|
|
222
224
|
});
|
|
223
225
|
session.start();
|
|
224
226
|
}
|
|
@@ -362,7 +362,7 @@ let RouteCard = (() => {
|
|
|
362
362
|
}
|
|
363
363
|
if (descr?.hasVideo && !descr.videoUrl) {
|
|
364
364
|
const details = this.getRouteData();
|
|
365
|
-
if (details.video
|
|
365
|
+
if (details.video?.url) {
|
|
366
366
|
canStart = false;
|
|
367
367
|
videoChecking = true;
|
|
368
368
|
videoMissing = this.isVideoMissing();
|
|
@@ -41,10 +41,12 @@ exports.useRoutesDbLoader = exports.RoutesDbLoader = void 0;
|
|
|
41
41
|
const api_1 = require("../../../api");
|
|
42
42
|
const types_1 = require("../../../base/types");
|
|
43
43
|
const observer_1 = require("../../../base/types/observer");
|
|
44
|
+
const route_1 = require("../../base/model/route");
|
|
44
45
|
const LegacyDB_1 = require("./LegacyDB");
|
|
46
|
+
const types_2 = require("./types");
|
|
45
47
|
const DBLoader_1 = require("./DBLoader");
|
|
46
48
|
const utils_1 = require("../../../utils");
|
|
47
|
-
const
|
|
49
|
+
const route_2 = require("../../base/utils/route");
|
|
48
50
|
const clone_1 = __importDefault(require("../../../utils/clone"));
|
|
49
51
|
let RoutesDbLoader = (() => {
|
|
50
52
|
let _classDecorators = [types_1.Singleton];
|
|
@@ -126,8 +128,34 @@ let RoutesDbLoader = (() => {
|
|
|
126
128
|
if (!descr)
|
|
127
129
|
return;
|
|
128
130
|
const details = await this.loadDetailRecord(descr);
|
|
131
|
+
if (!details)
|
|
132
|
+
return details;
|
|
133
|
+
await this.correctPersistedVideoUrl(descr, details);
|
|
129
134
|
return details;
|
|
130
135
|
}
|
|
136
|
+
async correctPersistedVideoUrl(descr, details) {
|
|
137
|
+
const corrected = (0, types_2.correctVideoUrl)(details.video?.url, this.isMobile());
|
|
138
|
+
if (!corrected)
|
|
139
|
+
return false;
|
|
140
|
+
details.video.url = corrected;
|
|
141
|
+
if (descr.videoUrl !== corrected) {
|
|
142
|
+
descr.videoUrl = corrected;
|
|
143
|
+
}
|
|
144
|
+
await this.writeDetailsRecord(descr, details);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
async writeDetailsRecord(descr, details) {
|
|
148
|
+
const repo = this.getDetailsRepo(new route_1.Route(descr));
|
|
149
|
+
if (!repo)
|
|
150
|
+
return;
|
|
151
|
+
const id = descr.legacyId || descr.id;
|
|
152
|
+
try {
|
|
153
|
+
await repo.write(id, details);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
this.logger.logEvent({ message: 'could not persist corrected video URL', id, error: err.message });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
131
159
|
async writeRepo() {
|
|
132
160
|
if (this.saveObserver)
|
|
133
161
|
await this.saveObserver.wait();
|
|
@@ -260,20 +288,27 @@ let RoutesDbLoader = (() => {
|
|
|
260
288
|
if (details.localizedTitle && typeof (details.localizedTitle) === 'string') {
|
|
261
289
|
details.localizedTitle = { en: details.localizedTitle };
|
|
262
290
|
}
|
|
263
|
-
(0,
|
|
264
|
-
(0,
|
|
291
|
+
(0, route_2.validateDistance)(details.points);
|
|
292
|
+
(0, route_2.validateSlopes)(details.points);
|
|
265
293
|
}
|
|
266
294
|
validateDescription(description, details) {
|
|
267
295
|
if (!description.elevation) {
|
|
268
|
-
description.elevation = (0,
|
|
296
|
+
description.elevation = (0, route_2.getTotalElevation)(details);
|
|
269
297
|
}
|
|
270
298
|
}
|
|
271
299
|
async loadDetails(route, alreadyAdded) {
|
|
272
300
|
const details = await this.loadDetailRecord(route);
|
|
273
301
|
let updated = false;
|
|
274
|
-
|
|
302
|
+
const verifyVideoUrl = (route) => {
|
|
303
|
+
const res = this.verifyVideoUrl(route);
|
|
304
|
+
if (res) {
|
|
305
|
+
this.logger.logEvent({ message: 'video URL updated', url: route?.details?.video?.url });
|
|
306
|
+
}
|
|
307
|
+
return res;
|
|
308
|
+
};
|
|
309
|
+
(0, route_2.addDetails)(route, details);
|
|
275
310
|
updated ||= this.verifyRouteHash(route);
|
|
276
|
-
updated ||=
|
|
311
|
+
updated ||= verifyVideoUrl(route);
|
|
277
312
|
if (alreadyAdded)
|
|
278
313
|
this.emitRouteUpdate(route);
|
|
279
314
|
this.emitRouteAdded(route);
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Loader = void 0;
|
|
3
|
+
exports.Loader = exports.correctVideoUrl = void 0;
|
|
4
|
+
const api_1 = require("../../../api");
|
|
4
5
|
const utils_1 = require("../../../utils");
|
|
5
6
|
const valid_1 = require("../../../utils/valid");
|
|
7
|
+
const utils_2 = require("../../base/parsers/utils");
|
|
6
8
|
const route_1 = require("../../base/utils/route");
|
|
9
|
+
const MALFORMED_VIDEO_URL = /^video:\/{4,}/;
|
|
10
|
+
const correctVideoUrl = (url, isMobile = false) => {
|
|
11
|
+
if (!url?.startsWith('video:'))
|
|
12
|
+
return undefined;
|
|
13
|
+
const malformed = MALFORMED_VIDEO_URL.test(url);
|
|
14
|
+
if (!malformed && !isMobile)
|
|
15
|
+
return undefined;
|
|
16
|
+
const rawPath = malformed
|
|
17
|
+
? url.replace(/^video:\/{3}/, '')
|
|
18
|
+
: url.replace(/^video:\/{2}/, '');
|
|
19
|
+
return isMobile ? (0, utils_2.buildFileUrl)(rawPath) : (0, utils_2.buildVideoUrl)(rawPath);
|
|
20
|
+
};
|
|
21
|
+
exports.correctVideoUrl = correctVideoUrl;
|
|
7
22
|
class Loader {
|
|
8
23
|
loadObserver;
|
|
9
24
|
isCompleted(route) {
|
|
@@ -15,6 +30,9 @@ class Loader {
|
|
|
15
30
|
}
|
|
16
31
|
return true;
|
|
17
32
|
}
|
|
33
|
+
isMobile() {
|
|
34
|
+
return (0, api_1.getBindings)()?.appInfo?.getChannel() === 'mobile';
|
|
35
|
+
}
|
|
18
36
|
verifyRouteHash(route) {
|
|
19
37
|
const { description, details } = route;
|
|
20
38
|
const prev = description.routeHash;
|
|
@@ -35,6 +53,11 @@ class Loader {
|
|
|
35
53
|
return;
|
|
36
54
|
if (descr.videoUrl) {
|
|
37
55
|
const details = route.details;
|
|
56
|
+
const corrected = (0, exports.correctVideoUrl)(details.video.url, this.isMobile());
|
|
57
|
+
if (corrected) {
|
|
58
|
+
details.video.url = corrected;
|
|
59
|
+
updated = true;
|
|
60
|
+
}
|
|
38
61
|
if (details.video.url.startsWith('video:') && !descr.isDownloaded && !descr.isLocal) {
|
|
39
62
|
descr.isDownloaded = true;
|
|
40
63
|
updated = true;
|
|
@@ -888,10 +888,18 @@ let RouteListService = (() => {
|
|
|
888
888
|
if (!route.description.country) {
|
|
889
889
|
route.updateCountryFromPoints()
|
|
890
890
|
.then(() => {
|
|
891
|
-
this.logEvent({ message: 'preload route updated
|
|
891
|
+
this.logEvent({ message: 'preload route updated', route: card?.getData()?.title, reason: 'country added' });
|
|
892
892
|
this.db.save(route, false);
|
|
893
893
|
});
|
|
894
894
|
}
|
|
895
|
+
if (route.description.hasVideo && route.description.isDownloaded) {
|
|
896
|
+
card.videoExists().then(exists => {
|
|
897
|
+
if (!exists) {
|
|
898
|
+
this.logEvent({ message: 'preload route updated', route: card?.getData()?.title, reason: 'downloaded file missing' });
|
|
899
|
+
card.resetDownload();
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
}
|
|
895
903
|
})
|
|
896
904
|
.catch(() => {
|
|
897
905
|
});
|
package/lib/cjs/ui/service.js
CHANGED
|
@@ -63,6 +63,7 @@ let UserInterfaceServcie = (() => {
|
|
|
63
63
|
let _getUserSettings_decorators;
|
|
64
64
|
let _getOnlineStatusMonitoring_decorators;
|
|
65
65
|
let _getAppState_decorators;
|
|
66
|
+
let _getFeatureToggleSync_decorators;
|
|
66
67
|
var UserInterfaceServcie = class extends _classSuper {
|
|
67
68
|
static { _classThis = this; }
|
|
68
69
|
static {
|
|
@@ -72,11 +73,13 @@ let UserInterfaceServcie = (() => {
|
|
|
72
73
|
_getUserSettings_decorators = [decorators_1.Injectable];
|
|
73
74
|
_getOnlineStatusMonitoring_decorators = [decorators_1.Injectable];
|
|
74
75
|
_getAppState_decorators = [decorators_1.Injectable];
|
|
76
|
+
_getFeatureToggleSync_decorators = [decorators_1.Injectable];
|
|
75
77
|
__esDecorate(this, null, _getBindings_decorators, { kind: "method", name: "getBindings", static: false, private: false, access: { has: obj => "getBindings" in obj, get: obj => obj.getBindings }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
76
78
|
__esDecorate(this, null, _getMessageQueue_decorators, { kind: "method", name: "getMessageQueue", static: false, private: false, access: { has: obj => "getMessageQueue" in obj, get: obj => obj.getMessageQueue }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
77
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);
|
|
78
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);
|
|
79
81
|
__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);
|
|
82
|
+
__esDecorate(this, null, _getFeatureToggleSync_decorators, { kind: "method", name: "getFeatureToggleSync", static: false, private: false, access: { has: obj => "getFeatureToggleSync" in obj, get: obj => obj.getFeatureToggleSync }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
80
83
|
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
81
84
|
UserInterfaceServcie = _classThis = _classDescriptor.value;
|
|
82
85
|
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
@@ -208,6 +211,7 @@ let UserInterfaceServcie = (() => {
|
|
|
208
211
|
const sent = false;
|
|
209
212
|
const user = this.getUserSettings().getValue('user', {});
|
|
210
213
|
const id = this.getUserSettings().getValue('uuid', undefined);
|
|
214
|
+
this.getFeatureToggleSync().start(id);
|
|
211
215
|
const { username, weight, ftp, gender } = user;
|
|
212
216
|
const topic = `incyclist/session/${this.session}/start`;
|
|
213
217
|
const os = this.getBindings().appInfo?.getOS()?.platform;
|
|
@@ -500,6 +504,9 @@ let UserInterfaceServcie = (() => {
|
|
|
500
504
|
getAppState() {
|
|
501
505
|
return (0, appstate_1.useAppState)();
|
|
502
506
|
}
|
|
507
|
+
getFeatureToggleSync() {
|
|
508
|
+
return (0, appstate_1.useFeatureToggleSync)();
|
|
509
|
+
}
|
|
503
510
|
};
|
|
504
511
|
return UserInterfaceServcie = _classThis;
|
|
505
512
|
})();
|
|
@@ -167,7 +167,7 @@ let WorkoutRidePageService = (() => {
|
|
|
167
167
|
const current = this.getRideDisplay().getDisplayProperties().workout;
|
|
168
168
|
return {
|
|
169
169
|
...base,
|
|
170
|
-
title:
|
|
170
|
+
title: wo.title ?? '',
|
|
171
171
|
graph: this.buildGraphPlan(current, wo.ftp),
|
|
172
172
|
steps: this.buildUpcomingSteps(current, wo.ftp),
|
|
173
173
|
dashboard: this.buildDashboardLine(wo),
|
|
@@ -452,20 +452,17 @@ let WorkoutRidePageService = (() => {
|
|
|
452
452
|
const hasMore = currentIndex + 1 + UPCOMING_STEPS_COUNT < flattened.length;
|
|
453
453
|
return { previous, current: currentStep, upcoming, hasMore };
|
|
454
454
|
}
|
|
455
|
-
getMobileStepTitle(wo) {
|
|
456
|
-
const title = wo.title ?? '';
|
|
457
|
-
const name = wo.workout?.name;
|
|
458
|
-
const prefix = name ? `${name}: ` : '';
|
|
459
|
-
return prefix && title.startsWith(prefix) ? title.slice(prefix.length) : title;
|
|
460
|
-
}
|
|
461
455
|
buildDashboardLine(wo) {
|
|
462
456
|
const limits = this.getWorkoutRide().getCurrentLimits();
|
|
463
|
-
const title =
|
|
457
|
+
const title = wo.title ?? '';
|
|
464
458
|
if (!limits)
|
|
465
459
|
return { text: title, mode: wo.mode ?? null };
|
|
466
460
|
const target = (0, graph_1.getStepTargetText)(limits.step ?? {}, wo.ftp);
|
|
467
461
|
const duration = (0, graph_1.getStepDuration)({ duration: limits.duration });
|
|
468
|
-
const
|
|
462
|
+
const base = `${target} for ${duration}`;
|
|
463
|
+
let text = base;
|
|
464
|
+
if (title && title !== base)
|
|
465
|
+
text = title.startsWith('(') ? `${base}${title}` : `${base} - ${title}`;
|
|
469
466
|
return { text, mode: wo.mode ?? null };
|
|
470
467
|
}
|
|
471
468
|
getStepAbsolutePower(step, ftp) {
|
|
@@ -35,6 +35,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
exports.getWorkoutRide = exports.useWorkoutRide = exports.WorkoutRide = void 0;
|
|
38
|
+
const api_1 = require("../../api");
|
|
38
39
|
const service_1 = require("../../base/service");
|
|
39
40
|
const types_1 = require("../../base/types");
|
|
40
41
|
const observer_1 = require("../../base/types/observer");
|
|
@@ -42,6 +43,7 @@ const devices_1 = require("../../devices");
|
|
|
42
43
|
const settings_1 = require("../../settings");
|
|
43
44
|
const utils_1 = require("../../utils");
|
|
44
45
|
const valid_1 = require("../../utils/valid");
|
|
46
|
+
const graph_1 = require("../base/graph");
|
|
45
47
|
const list_1 = require("../list");
|
|
46
48
|
const decorators_1 = require("../../base/decorators");
|
|
47
49
|
const DEFAULT_FTP = 200;
|
|
@@ -521,35 +523,40 @@ let WorkoutRide = (() => {
|
|
|
521
523
|
}
|
|
522
524
|
return { start, stop };
|
|
523
525
|
}
|
|
526
|
+
getBindings() {
|
|
527
|
+
return (0, api_1.getBindings)();
|
|
528
|
+
}
|
|
524
529
|
getStepTitle(time) {
|
|
525
530
|
if (!this.workout)
|
|
526
531
|
return;
|
|
527
|
-
let title = this.workout.name;
|
|
528
532
|
const limit = this.workout.getLimits(time, true);
|
|
529
533
|
const segment = this.workout.getSegment(time);
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
ch = ' - ';
|
|
534
|
-
}
|
|
535
|
-
if (segment?.text && segment?.repeat > 0) {
|
|
536
|
-
const repeatTime = segment.duration / segment.repeat;
|
|
537
|
-
const repeatCount = Math.floor((time - segment.getStart()) / repeatTime) + 1;
|
|
538
|
-
title += `(${repeatCount}/${segment.repeat})`;
|
|
539
|
-
}
|
|
540
|
-
if (!limit) {
|
|
541
|
-
title += `${ch}free`;
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
if (limit?.text)
|
|
545
|
-
title += `${ch}${limit.text}`;
|
|
546
|
-
if (limit?.text && segment && !segment.text && segment.repeat > 0) {
|
|
534
|
+
const isMobile = this.getBindings()?.appInfo?.getChannel() === 'mobile';
|
|
535
|
+
const repeatSuffix = (segment?.repeat > 0)
|
|
536
|
+
? (() => {
|
|
547
537
|
const repeatTime = segment.duration / segment.repeat;
|
|
548
538
|
const repeatCount = Math.floor((time - segment.getStart()) / repeatTime) + 1;
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
539
|
+
return `(${repeatCount}/${segment.repeat})`;
|
|
540
|
+
})()
|
|
541
|
+
: '';
|
|
542
|
+
const compose = (body) => isMobile ? body : `${this.workout.name}: ${body}`;
|
|
543
|
+
if (!limit)
|
|
544
|
+
return compose('free');
|
|
545
|
+
const segmentName = segment?.text;
|
|
546
|
+
const stepName = limit?.text;
|
|
547
|
+
if (segmentName && stepName)
|
|
548
|
+
return compose(`${segmentName}${repeatSuffix} - ${stepName}`);
|
|
549
|
+
if (segmentName)
|
|
550
|
+
return compose(`${segmentName}${repeatSuffix}`);
|
|
551
|
+
if (stepName)
|
|
552
|
+
return compose(`${stepName}${repeatSuffix}`);
|
|
553
|
+
const rawStep = this.currentLimits?.step ?? limit;
|
|
554
|
+
const verbalDescription = `${(0, graph_1.getStepTargetText)(rawStep, this.settings?.ftp)} for ${(0, graph_1.getStepDuration)(rawStep)}`;
|
|
555
|
+
if (isMobile)
|
|
556
|
+
return repeatSuffix || verbalDescription;
|
|
557
|
+
if (!segment)
|
|
558
|
+
return this.workout.name;
|
|
559
|
+
return compose(`${verbalDescription}${repeatSuffix}`);
|
|
553
560
|
}
|
|
554
561
|
getPowerVal(power, key) {
|
|
555
562
|
if (power === undefined)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import { getBindings } from "../api";
|
|
36
|
+
import { Injectable } from "../base/decorators";
|
|
37
|
+
import { IncyclistService } from "../base/service";
|
|
38
|
+
import { Singleton } from "../base/types/singleton";
|
|
39
|
+
import { useUserSettings } from "../settings";
|
|
40
|
+
const TOPIC_PREFIX = 'incyclist/features';
|
|
41
|
+
let FeatureToggleSyncService = (() => {
|
|
42
|
+
let _classDecorators = [Singleton];
|
|
43
|
+
let _classDescriptor;
|
|
44
|
+
let _classExtraInitializers = [];
|
|
45
|
+
let _classThis;
|
|
46
|
+
let _classSuper = IncyclistService;
|
|
47
|
+
let _instanceExtraInitializers = [];
|
|
48
|
+
let _getMessageQueue_decorators;
|
|
49
|
+
let _getUserSettings_decorators;
|
|
50
|
+
var FeatureToggleSyncService = class extends _classSuper {
|
|
51
|
+
static { _classThis = this; }
|
|
52
|
+
static {
|
|
53
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
54
|
+
_getMessageQueue_decorators = [Injectable];
|
|
55
|
+
_getUserSettings_decorators = [Injectable];
|
|
56
|
+
__esDecorate(this, null, _getMessageQueue_decorators, { kind: "method", name: "getMessageQueue", static: false, private: false, access: { has: obj => "getMessageQueue" in obj, get: obj => obj.getMessageQueue }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
57
|
+
__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);
|
|
58
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
59
|
+
FeatureToggleSyncService = _classThis = _classDescriptor.value;
|
|
60
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
61
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
62
|
+
}
|
|
63
|
+
uuid = __runInitializers(this, _instanceExtraInitializers);
|
|
64
|
+
subscribed = false;
|
|
65
|
+
onMessageHandler = this.onMessage.bind(this);
|
|
66
|
+
constructor() {
|
|
67
|
+
super('FeatureToggleSync');
|
|
68
|
+
}
|
|
69
|
+
start(uuid) {
|
|
70
|
+
try {
|
|
71
|
+
if (!uuid)
|
|
72
|
+
return;
|
|
73
|
+
if (this.subscribed && this.uuid === uuid)
|
|
74
|
+
return;
|
|
75
|
+
if (this.subscribed)
|
|
76
|
+
this.stop();
|
|
77
|
+
const mq = this.getMessageQueue();
|
|
78
|
+
if (!mq?.enabled?.()) {
|
|
79
|
+
this.logEvent({ message: 'feature-toggle sync not started', reason: 'mq binding not enabled', uuid });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.uuid = uuid;
|
|
83
|
+
mq.on('mq-message', this.onMessageHandler);
|
|
84
|
+
mq.subscribe(this.getTopic(uuid));
|
|
85
|
+
this.subscribed = true;
|
|
86
|
+
this.logEvent({ message: 'feature-toggle sync started', uuid, topic: this.getTopic(uuid) });
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
this.logError(err, 'start');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
stop() {
|
|
93
|
+
try {
|
|
94
|
+
if (!this.subscribed)
|
|
95
|
+
return;
|
|
96
|
+
const mq = this.getMessageQueue();
|
|
97
|
+
if (mq) {
|
|
98
|
+
mq.off('mq-message', this.onMessageHandler);
|
|
99
|
+
if (this.uuid)
|
|
100
|
+
mq.unsubscribe(this.getTopic(this.uuid));
|
|
101
|
+
}
|
|
102
|
+
this.logEvent({ message: 'feature-toggle sync stopped', uuid: this.uuid });
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
this.logError(err, 'stop');
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
this.subscribed = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
getTopic(uuid) {
|
|
112
|
+
return `${TOPIC_PREFIX}/${uuid}/+`;
|
|
113
|
+
}
|
|
114
|
+
getTopicPrefix() {
|
|
115
|
+
return this.uuid ? `${TOPIC_PREFIX}/${this.uuid}/` : undefined;
|
|
116
|
+
}
|
|
117
|
+
onMessage(topic, message) {
|
|
118
|
+
try {
|
|
119
|
+
const prefix = this.getTopicPrefix();
|
|
120
|
+
if (!prefix || typeof topic !== 'string' || !topic.startsWith(prefix))
|
|
121
|
+
return;
|
|
122
|
+
const toggleName = this.parseToggleName(topic, prefix);
|
|
123
|
+
if (!toggleName) {
|
|
124
|
+
this.logEvent({ message: 'feature-toggle sync: ignoring malformed topic', topic, payload: message });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const payload = this.parsePayload(message);
|
|
128
|
+
if (!payload || typeof payload.value !== 'boolean') {
|
|
129
|
+
this.logEvent({ message: 'feature-toggle sync: ignoring malformed payload', topic, payload: message });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this.getUserSettings().set(toggleName, payload.value);
|
|
133
|
+
this.logEvent({ message: 'feature-toggle sync: applied toggle', toggle: toggleName, value: payload.value });
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
this.logError(err, 'onMessage', { topic });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
parseToggleName(topic, prefix) {
|
|
140
|
+
const toggleName = topic.slice(prefix.length);
|
|
141
|
+
if (!toggleName || toggleName.includes('/'))
|
|
142
|
+
return undefined;
|
|
143
|
+
return toggleName;
|
|
144
|
+
}
|
|
145
|
+
parsePayload(message) {
|
|
146
|
+
try {
|
|
147
|
+
let str;
|
|
148
|
+
if (typeof message === 'string')
|
|
149
|
+
str = message;
|
|
150
|
+
else if (message instanceof Uint8Array)
|
|
151
|
+
str = Buffer.from(message.buffer).toString();
|
|
152
|
+
else
|
|
153
|
+
return undefined;
|
|
154
|
+
return JSON.parse(str);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
getMessageQueue() {
|
|
161
|
+
return getBindings().mq;
|
|
162
|
+
}
|
|
163
|
+
getUserSettings() {
|
|
164
|
+
return useUserSettings();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return FeatureToggleSyncService = _classThis;
|
|
168
|
+
})();
|
|
169
|
+
export { FeatureToggleSyncService };
|
|
170
|
+
export const useFeatureToggleSync = () => new FeatureToggleSyncService();
|
|
@@ -40,7 +40,7 @@ import { getRouteHash, validateRoute } from "../../utils/route";
|
|
|
40
40
|
import { PGMFFileReader } from "./pgmf";
|
|
41
41
|
import { RLVFileReader } from "./rlv";
|
|
42
42
|
import { TacxFileReader } from "./TacxReader";
|
|
43
|
-
import { fixIncorrectFileInfo } from "../utils";
|
|
43
|
+
import { buildVideoUrl, fixIncorrectFileInfo } from "../utils";
|
|
44
44
|
import { EventLogger } from "gd-eventlog";
|
|
45
45
|
import { Injectable } from "../../../../base/decorators";
|
|
46
46
|
let TacxParser = (() => {
|
|
@@ -136,10 +136,7 @@ let TacxParser = (() => {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
buildVideoUrl(filePath) {
|
|
139
|
-
|
|
140
|
-
return `video:///${filePath}`;
|
|
141
|
-
}
|
|
142
|
-
return `video://${filePath}`;
|
|
139
|
+
return buildVideoUrl(filePath);
|
|
143
140
|
}
|
|
144
141
|
async buildResult(context) {
|
|
145
142
|
if (!context.rlvData || !context.pgmfData) {
|
|
@@ -111,6 +111,15 @@ const isAbsolutePath = (path) => {
|
|
|
111
111
|
return true;
|
|
112
112
|
return false;
|
|
113
113
|
};
|
|
114
|
+
export const buildVideoUrl = (filePath) => {
|
|
115
|
+
if (isAbsolutePath(filePath)) {
|
|
116
|
+
if (/^[A-Za-z]:/.test(filePath)) {
|
|
117
|
+
return `video:///${filePath}`;
|
|
118
|
+
}
|
|
119
|
+
return `video://${filePath}`;
|
|
120
|
+
}
|
|
121
|
+
return `video://${filePath}`;
|
|
122
|
+
};
|
|
114
123
|
export const safeDecode = (path) => {
|
|
115
124
|
try {
|
|
116
125
|
return decodeURIComponent(path);
|
|
@@ -128,7 +137,7 @@ const handleFileUrlPath = (fileName) => {
|
|
|
128
137
|
const cleanPath = safeDecode(fileName.replace(/^file:\/\/\/?/, ''));
|
|
129
138
|
return `file://${encodeURI(cleanPath)}`;
|
|
130
139
|
};
|
|
131
|
-
const buildFileUrl = (normalizedPath) => {
|
|
140
|
+
export const buildFileUrl = (normalizedPath) => {
|
|
132
141
|
if (isAbsolutePath(normalizedPath)) {
|
|
133
142
|
if (/^[A-Za-z]:/.exec(normalizedPath)) {
|
|
134
143
|
return `file:///${encodeURI(normalizedPath)}`;
|
|
@@ -37,6 +37,7 @@ import { Injectable, Singleton } from "../../base/decorators";
|
|
|
37
37
|
import { IncyclistService } from "../../base/service";
|
|
38
38
|
import { useUserSettings } from "../../settings";
|
|
39
39
|
import { waitNextTick } from "../../utils";
|
|
40
|
+
import { buildVideoUrl } from "../base/parsers/utils";
|
|
40
41
|
import { RoutesApiLoader } from "../list/loaders/api";
|
|
41
42
|
import { RoutesDbLoader } from "../list/loaders/db";
|
|
42
43
|
import { DownloadObserver } from "./types";
|
|
@@ -214,8 +215,9 @@ let RouteDownloadService = (() => {
|
|
|
214
215
|
session.on('progress', onProgress);
|
|
215
216
|
session.on('error', onError);
|
|
216
217
|
session.once('done', () => {
|
|
217
|
-
|
|
218
|
-
|
|
218
|
+
const localUrl = buildVideoUrl(file);
|
|
219
|
+
this.logEvent({ message: 'download finished ', title, id, url, localUrl });
|
|
220
|
+
observer.emit('done', localUrl);
|
|
219
221
|
});
|
|
220
222
|
session.start();
|
|
221
223
|
}
|
|
@@ -359,7 +359,7 @@ let RouteCard = (() => {
|
|
|
359
359
|
}
|
|
360
360
|
if (descr?.hasVideo && !descr.videoUrl) {
|
|
361
361
|
const details = this.getRouteData();
|
|
362
|
-
if (details.video
|
|
362
|
+
if (details.video?.url) {
|
|
363
363
|
canStart = false;
|
|
364
364
|
videoChecking = true;
|
|
365
365
|
videoMissing = this.isVideoMissing();
|
|
@@ -35,7 +35,9 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
35
35
|
import { JsonRepository } from "../../../api";
|
|
36
36
|
import { Singleton } from "../../../base/types";
|
|
37
37
|
import { PromiseObserver } from "../../../base/types/observer";
|
|
38
|
+
import { Route } from "../../base/model/route";
|
|
38
39
|
import { RoutesLegacyDbLoader } from "./LegacyDB";
|
|
40
|
+
import { correctVideoUrl } from "./types";
|
|
39
41
|
import { DBLoader } from "./DBLoader";
|
|
40
42
|
import { waitNextTick } from "../../../utils";
|
|
41
43
|
import { addDetails, getTotalElevation, validateDistance, validateSlopes } from "../../base/utils/route";
|
|
@@ -120,8 +122,34 @@ let RoutesDbLoader = (() => {
|
|
|
120
122
|
if (!descr)
|
|
121
123
|
return;
|
|
122
124
|
const details = await this.loadDetailRecord(descr);
|
|
125
|
+
if (!details)
|
|
126
|
+
return details;
|
|
127
|
+
await this.correctPersistedVideoUrl(descr, details);
|
|
123
128
|
return details;
|
|
124
129
|
}
|
|
130
|
+
async correctPersistedVideoUrl(descr, details) {
|
|
131
|
+
const corrected = correctVideoUrl(details.video?.url, this.isMobile());
|
|
132
|
+
if (!corrected)
|
|
133
|
+
return false;
|
|
134
|
+
details.video.url = corrected;
|
|
135
|
+
if (descr.videoUrl !== corrected) {
|
|
136
|
+
descr.videoUrl = corrected;
|
|
137
|
+
}
|
|
138
|
+
await this.writeDetailsRecord(descr, details);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
async writeDetailsRecord(descr, details) {
|
|
142
|
+
const repo = this.getDetailsRepo(new Route(descr));
|
|
143
|
+
if (!repo)
|
|
144
|
+
return;
|
|
145
|
+
const id = descr.legacyId || descr.id;
|
|
146
|
+
try {
|
|
147
|
+
await repo.write(id, details);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
this.logger.logEvent({ message: 'could not persist corrected video URL', id, error: err.message });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
125
153
|
async writeRepo() {
|
|
126
154
|
if (this.saveObserver)
|
|
127
155
|
await this.saveObserver.wait();
|
|
@@ -265,9 +293,16 @@ let RoutesDbLoader = (() => {
|
|
|
265
293
|
async loadDetails(route, alreadyAdded) {
|
|
266
294
|
const details = await this.loadDetailRecord(route);
|
|
267
295
|
let updated = false;
|
|
296
|
+
const verifyVideoUrl = (route) => {
|
|
297
|
+
const res = this.verifyVideoUrl(route);
|
|
298
|
+
if (res) {
|
|
299
|
+
this.logger.logEvent({ message: 'video URL updated', url: route?.details?.video?.url });
|
|
300
|
+
}
|
|
301
|
+
return res;
|
|
302
|
+
};
|
|
268
303
|
addDetails(route, details);
|
|
269
304
|
updated ||= this.verifyRouteHash(route);
|
|
270
|
-
updated ||=
|
|
305
|
+
updated ||= verifyVideoUrl(route);
|
|
271
306
|
if (alreadyAdded)
|
|
272
307
|
this.emitRouteUpdate(route);
|
|
273
308
|
this.emitRouteAdded(route);
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
import { getBindings } from "../../../api";
|
|
1
2
|
import { waitNextTick } from "../../../utils";
|
|
2
3
|
import { valid } from "../../../utils/valid";
|
|
4
|
+
import { buildFileUrl, buildVideoUrl } from "../../base/parsers/utils";
|
|
3
5
|
import { getRouteHash } from "../../base/utils/route";
|
|
6
|
+
const MALFORMED_VIDEO_URL = /^video:\/{4,}/;
|
|
7
|
+
export const correctVideoUrl = (url, isMobile = false) => {
|
|
8
|
+
if (!url?.startsWith('video:'))
|
|
9
|
+
return undefined;
|
|
10
|
+
const malformed = MALFORMED_VIDEO_URL.test(url);
|
|
11
|
+
if (!malformed && !isMobile)
|
|
12
|
+
return undefined;
|
|
13
|
+
const rawPath = malformed
|
|
14
|
+
? url.replace(/^video:\/{3}/, '')
|
|
15
|
+
: url.replace(/^video:\/{2}/, '');
|
|
16
|
+
return isMobile ? buildFileUrl(rawPath) : buildVideoUrl(rawPath);
|
|
17
|
+
};
|
|
4
18
|
export class Loader {
|
|
5
19
|
loadObserver;
|
|
6
20
|
isCompleted(route) {
|
|
@@ -12,6 +26,9 @@ export class Loader {
|
|
|
12
26
|
}
|
|
13
27
|
return true;
|
|
14
28
|
}
|
|
29
|
+
isMobile() {
|
|
30
|
+
return getBindings()?.appInfo?.getChannel() === 'mobile';
|
|
31
|
+
}
|
|
15
32
|
verifyRouteHash(route) {
|
|
16
33
|
const { description, details } = route;
|
|
17
34
|
const prev = description.routeHash;
|
|
@@ -32,6 +49,11 @@ export class Loader {
|
|
|
32
49
|
return;
|
|
33
50
|
if (descr.videoUrl) {
|
|
34
51
|
const details = route.details;
|
|
52
|
+
const corrected = correctVideoUrl(details.video.url, this.isMobile());
|
|
53
|
+
if (corrected) {
|
|
54
|
+
details.video.url = corrected;
|
|
55
|
+
updated = true;
|
|
56
|
+
}
|
|
35
57
|
if (details.video.url.startsWith('video:') && !descr.isDownloaded && !descr.isLocal) {
|
|
36
58
|
descr.isDownloaded = true;
|
|
37
59
|
updated = true;
|
|
@@ -882,10 +882,18 @@ let RouteListService = (() => {
|
|
|
882
882
|
if (!route.description.country) {
|
|
883
883
|
route.updateCountryFromPoints()
|
|
884
884
|
.then(() => {
|
|
885
|
-
this.logEvent({ message: 'preload route updated
|
|
885
|
+
this.logEvent({ message: 'preload route updated', route: card?.getData()?.title, reason: 'country added' });
|
|
886
886
|
this.db.save(route, false);
|
|
887
887
|
});
|
|
888
888
|
}
|
|
889
|
+
if (route.description.hasVideo && route.description.isDownloaded) {
|
|
890
|
+
card.videoExists().then(exists => {
|
|
891
|
+
if (!exists) {
|
|
892
|
+
this.logEvent({ message: 'preload route updated', route: card?.getData()?.title, reason: 'downloaded file missing' });
|
|
893
|
+
card.resetDownload();
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
889
897
|
})
|
|
890
898
|
.catch(() => {
|
|
891
899
|
});
|
package/lib/esm/ui/service.js
CHANGED
|
@@ -44,7 +44,7 @@ import { useFreeRideService, useRouteList } from "../routes";
|
|
|
44
44
|
import { useWorkoutList } from "../workouts";
|
|
45
45
|
import { useActiveRides, useActivityList } from "../activities";
|
|
46
46
|
import { useOnlineStatusMonitoring } from "../monitoring";
|
|
47
|
-
import { useAppState } from "../appstate";
|
|
47
|
+
import { useAppState, useFeatureToggleSync } from "../appstate";
|
|
48
48
|
import { IncyclistPageService } from "../base/pages";
|
|
49
49
|
import { waitNextTick } from "../utils";
|
|
50
50
|
const BACKGROUND_PAUSE_TIMEOUT_MS = 300000;
|
|
@@ -60,6 +60,7 @@ let UserInterfaceServcie = (() => {
|
|
|
60
60
|
let _getUserSettings_decorators;
|
|
61
61
|
let _getOnlineStatusMonitoring_decorators;
|
|
62
62
|
let _getAppState_decorators;
|
|
63
|
+
let _getFeatureToggleSync_decorators;
|
|
63
64
|
var UserInterfaceServcie = class extends _classSuper {
|
|
64
65
|
static { _classThis = this; }
|
|
65
66
|
static {
|
|
@@ -69,11 +70,13 @@ let UserInterfaceServcie = (() => {
|
|
|
69
70
|
_getUserSettings_decorators = [Injectable];
|
|
70
71
|
_getOnlineStatusMonitoring_decorators = [Injectable];
|
|
71
72
|
_getAppState_decorators = [Injectable];
|
|
73
|
+
_getFeatureToggleSync_decorators = [Injectable];
|
|
72
74
|
__esDecorate(this, null, _getBindings_decorators, { kind: "method", name: "getBindings", static: false, private: false, access: { has: obj => "getBindings" in obj, get: obj => obj.getBindings }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
73
75
|
__esDecorate(this, null, _getMessageQueue_decorators, { kind: "method", name: "getMessageQueue", static: false, private: false, access: { has: obj => "getMessageQueue" in obj, get: obj => obj.getMessageQueue }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
74
76
|
__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);
|
|
75
77
|
__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);
|
|
76
78
|
__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);
|
|
79
|
+
__esDecorate(this, null, _getFeatureToggleSync_decorators, { kind: "method", name: "getFeatureToggleSync", static: false, private: false, access: { has: obj => "getFeatureToggleSync" in obj, get: obj => obj.getFeatureToggleSync }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
77
80
|
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
78
81
|
UserInterfaceServcie = _classThis = _classDescriptor.value;
|
|
79
82
|
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
@@ -205,6 +208,7 @@ let UserInterfaceServcie = (() => {
|
|
|
205
208
|
const sent = false;
|
|
206
209
|
const user = this.getUserSettings().getValue('user', {});
|
|
207
210
|
const id = this.getUserSettings().getValue('uuid', undefined);
|
|
211
|
+
this.getFeatureToggleSync().start(id);
|
|
208
212
|
const { username, weight, ftp, gender } = user;
|
|
209
213
|
const topic = `incyclist/session/${this.session}/start`;
|
|
210
214
|
const os = this.getBindings().appInfo?.getOS()?.platform;
|
|
@@ -497,6 +501,9 @@ let UserInterfaceServcie = (() => {
|
|
|
497
501
|
getAppState() {
|
|
498
502
|
return useAppState();
|
|
499
503
|
}
|
|
504
|
+
getFeatureToggleSync() {
|
|
505
|
+
return useFeatureToggleSync();
|
|
506
|
+
}
|
|
500
507
|
};
|
|
501
508
|
return UserInterfaceServcie = _classThis;
|
|
502
509
|
})();
|
|
@@ -164,7 +164,7 @@ let WorkoutRidePageService = (() => {
|
|
|
164
164
|
const current = this.getRideDisplay().getDisplayProperties().workout;
|
|
165
165
|
return {
|
|
166
166
|
...base,
|
|
167
|
-
title:
|
|
167
|
+
title: wo.title ?? '',
|
|
168
168
|
graph: this.buildGraphPlan(current, wo.ftp),
|
|
169
169
|
steps: this.buildUpcomingSteps(current, wo.ftp),
|
|
170
170
|
dashboard: this.buildDashboardLine(wo),
|
|
@@ -449,20 +449,17 @@ let WorkoutRidePageService = (() => {
|
|
|
449
449
|
const hasMore = currentIndex + 1 + UPCOMING_STEPS_COUNT < flattened.length;
|
|
450
450
|
return { previous, current: currentStep, upcoming, hasMore };
|
|
451
451
|
}
|
|
452
|
-
getMobileStepTitle(wo) {
|
|
453
|
-
const title = wo.title ?? '';
|
|
454
|
-
const name = wo.workout?.name;
|
|
455
|
-
const prefix = name ? `${name}: ` : '';
|
|
456
|
-
return prefix && title.startsWith(prefix) ? title.slice(prefix.length) : title;
|
|
457
|
-
}
|
|
458
452
|
buildDashboardLine(wo) {
|
|
459
453
|
const limits = this.getWorkoutRide().getCurrentLimits();
|
|
460
|
-
const title =
|
|
454
|
+
const title = wo.title ?? '';
|
|
461
455
|
if (!limits)
|
|
462
456
|
return { text: title, mode: wo.mode ?? null };
|
|
463
457
|
const target = getStepTargetText(limits.step ?? {}, wo.ftp);
|
|
464
458
|
const duration = getStepDuration({ duration: limits.duration });
|
|
465
|
-
const
|
|
459
|
+
const base = `${target} for ${duration}`;
|
|
460
|
+
let text = base;
|
|
461
|
+
if (title && title !== base)
|
|
462
|
+
text = title.startsWith('(') ? `${base}${title}` : `${base} - ${title}`;
|
|
466
463
|
return { text, mode: wo.mode ?? null };
|
|
467
464
|
}
|
|
468
465
|
getStepAbsolutePower(step, ftp) {
|
|
@@ -32,6 +32,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
32
32
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
33
|
done = true;
|
|
34
34
|
};
|
|
35
|
+
import { getBindings } from "../../api";
|
|
35
36
|
import { IncyclistService } from "../../base/service";
|
|
36
37
|
import { Singleton } from "../../base/types";
|
|
37
38
|
import { Observer } from "../../base/types/observer";
|
|
@@ -39,6 +40,7 @@ import { useDeviceRide } from "../../devices";
|
|
|
39
40
|
import { useUserSettings } from "../../settings";
|
|
40
41
|
import { waitNextTick } from "../../utils";
|
|
41
42
|
import { valid } from "../../utils/valid";
|
|
43
|
+
import { getStepDuration, getStepTargetText } from "../base/graph";
|
|
42
44
|
import { useWorkoutList } from "../list";
|
|
43
45
|
import { Injectable } from "../../base/decorators";
|
|
44
46
|
const DEFAULT_FTP = 200;
|
|
@@ -518,35 +520,40 @@ let WorkoutRide = (() => {
|
|
|
518
520
|
}
|
|
519
521
|
return { start, stop };
|
|
520
522
|
}
|
|
523
|
+
getBindings() {
|
|
524
|
+
return getBindings();
|
|
525
|
+
}
|
|
521
526
|
getStepTitle(time) {
|
|
522
527
|
if (!this.workout)
|
|
523
528
|
return;
|
|
524
|
-
let title = this.workout.name;
|
|
525
529
|
const limit = this.workout.getLimits(time, true);
|
|
526
530
|
const segment = this.workout.getSegment(time);
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
ch = ' - ';
|
|
531
|
-
}
|
|
532
|
-
if (segment?.text && segment?.repeat > 0) {
|
|
533
|
-
const repeatTime = segment.duration / segment.repeat;
|
|
534
|
-
const repeatCount = Math.floor((time - segment.getStart()) / repeatTime) + 1;
|
|
535
|
-
title += `(${repeatCount}/${segment.repeat})`;
|
|
536
|
-
}
|
|
537
|
-
if (!limit) {
|
|
538
|
-
title += `${ch}free`;
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
|
-
if (limit?.text)
|
|
542
|
-
title += `${ch}${limit.text}`;
|
|
543
|
-
if (limit?.text && segment && !segment.text && segment.repeat > 0) {
|
|
531
|
+
const isMobile = this.getBindings()?.appInfo?.getChannel() === 'mobile';
|
|
532
|
+
const repeatSuffix = (segment?.repeat > 0)
|
|
533
|
+
? (() => {
|
|
544
534
|
const repeatTime = segment.duration / segment.repeat;
|
|
545
535
|
const repeatCount = Math.floor((time - segment.getStart()) / repeatTime) + 1;
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
|
|
536
|
+
return `(${repeatCount}/${segment.repeat})`;
|
|
537
|
+
})()
|
|
538
|
+
: '';
|
|
539
|
+
const compose = (body) => isMobile ? body : `${this.workout.name}: ${body}`;
|
|
540
|
+
if (!limit)
|
|
541
|
+
return compose('free');
|
|
542
|
+
const segmentName = segment?.text;
|
|
543
|
+
const stepName = limit?.text;
|
|
544
|
+
if (segmentName && stepName)
|
|
545
|
+
return compose(`${segmentName}${repeatSuffix} - ${stepName}`);
|
|
546
|
+
if (segmentName)
|
|
547
|
+
return compose(`${segmentName}${repeatSuffix}`);
|
|
548
|
+
if (stepName)
|
|
549
|
+
return compose(`${stepName}${repeatSuffix}`);
|
|
550
|
+
const rawStep = this.currentLimits?.step ?? limit;
|
|
551
|
+
const verbalDescription = `${getStepTargetText(rawStep, this.settings?.ftp)} for ${getStepDuration(rawStep)}`;
|
|
552
|
+
if (isMobile)
|
|
553
|
+
return repeatSuffix || verbalDescription;
|
|
554
|
+
if (!segment)
|
|
555
|
+
return this.workout.name;
|
|
556
|
+
return compose(`${verbalDescription}${repeatSuffix}`);
|
|
550
557
|
}
|
|
551
558
|
getPowerVal(power, key) {
|
|
552
559
|
if (power === undefined)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { IMessageQueueBinding } from "../api/mq";
|
|
2
|
+
import { IncyclistService } from "../base/service";
|
|
3
|
+
import { UserSettingsService } from "../settings/service/service";
|
|
4
|
+
import { FeatureToggle } from "./types";
|
|
5
|
+
export declare class FeatureToggleSyncService extends IncyclistService {
|
|
6
|
+
protected uuid: string;
|
|
7
|
+
protected subscribed: boolean;
|
|
8
|
+
protected onMessageHandler: any;
|
|
9
|
+
constructor();
|
|
10
|
+
start(uuid: string): void;
|
|
11
|
+
stop(): void;
|
|
12
|
+
protected getTopic(uuid: string): string;
|
|
13
|
+
protected getTopicPrefix(): string | undefined;
|
|
14
|
+
protected onMessage(topic: string, message: string | Uint8Array): void;
|
|
15
|
+
protected parseToggleName(topic: string, prefix: string): FeatureToggle | undefined;
|
|
16
|
+
protected parsePayload(message: string | Uint8Array): {
|
|
17
|
+
value?: boolean;
|
|
18
|
+
} | undefined;
|
|
19
|
+
protected getMessageQueue(): IMessageQueueBinding;
|
|
20
|
+
protected getUserSettings(): UserSettingsService;
|
|
21
|
+
}
|
|
22
|
+
export declare const useFeatureToggleSync: () => FeatureToggleSyncService;
|
|
@@ -21,8 +21,10 @@ export declare const getReferencedFileInfo: (info: FileInfo, referenced: {
|
|
|
21
21
|
file?: string;
|
|
22
22
|
url?: string;
|
|
23
23
|
}, scheme?: string) => string;
|
|
24
|
+
export declare const buildVideoUrl: (filePath: string) => string;
|
|
24
25
|
export declare const safeDecode: (path: string) => string;
|
|
25
26
|
export declare const safeEncode: (path: string) => string;
|
|
27
|
+
export declare const buildFileUrl: (normalizedPath: string) => string;
|
|
26
28
|
export declare const parseInformations: (informations?: Array<JSONObject>) => Array<RouteInfoText>;
|
|
27
29
|
export declare const fixIncorrectFileInfo: (file: Partial<FileInfo>) => void;
|
|
28
30
|
export declare const getUtf8Data: (res: string | Buffer) => string;
|
|
@@ -107,7 +107,7 @@ export declare class RouteCard extends BaseCard implements Card<Route> {
|
|
|
107
107
|
protected deleteRouteUserSettings(): void;
|
|
108
108
|
protected deleteFromUIList(): void;
|
|
109
109
|
protected markDeleted(): Promise<void>;
|
|
110
|
-
|
|
110
|
+
resetDownload(): Promise<void>;
|
|
111
111
|
protected deleteRoute(): Promise<void>;
|
|
112
112
|
protected logError(err: Error, fn: string): void;
|
|
113
113
|
protected getSettings(): RouteSettings;
|
|
@@ -19,6 +19,8 @@ export declare class RoutesDbLoader extends DBLoader<RouteInfoDBEntry> {
|
|
|
19
19
|
delete(route: Route): Promise<void>;
|
|
20
20
|
getDescription(id: string): RouteInfo;
|
|
21
21
|
getDetails(id: string): Promise<RouteApiDetail>;
|
|
22
|
+
protected correctPersistedVideoUrl(descr: RouteInfo, details: RouteApiDetail): Promise<boolean>;
|
|
23
|
+
protected writeDetailsRecord(descr: RouteInfo, details: RouteApiDetail): Promise<void>;
|
|
22
24
|
protected writeRepo(): Promise<void>;
|
|
23
25
|
protected write(): void;
|
|
24
26
|
protected getLegacyLoader(): RoutesLegacyDbLoader;
|
|
@@ -2,6 +2,7 @@ import { Observer } from "../../../base/types/observer";
|
|
|
2
2
|
import { RouteApiDescription } from "../../base/api/types";
|
|
3
3
|
import { Route } from "../../base/model/route";
|
|
4
4
|
import { RouteInfo } from "../../base/types";
|
|
5
|
+
export declare const correctVideoUrl: (url?: string, isMobile?: boolean) => string | undefined;
|
|
5
6
|
export interface RouteInfoDBEntry extends RouteInfo {
|
|
6
7
|
pointsEncoded?: string;
|
|
7
8
|
}
|
|
@@ -21,6 +22,7 @@ export declare abstract class Loader<T extends MinimalDescription> {
|
|
|
21
22
|
abstract save(route: Route): Promise<void>;
|
|
22
23
|
protected abstract buildRouteInfo(descr: T): RouteInfo;
|
|
23
24
|
protected isCompleted(route: Route): boolean;
|
|
25
|
+
protected isMobile(): boolean;
|
|
24
26
|
protected verifyRouteHash(route: Route): boolean;
|
|
25
27
|
protected verifyVideoUrl(route: Route): boolean;
|
|
26
28
|
protected verifyCountry(route: Route): Promise<void>;
|
|
@@ -56,5 +56,6 @@ export declare class UserInterfaceServcie extends IncyclistService {
|
|
|
56
56
|
protected getUserSettings(): import("../settings").UserSettingsService;
|
|
57
57
|
protected getOnlineStatusMonitoring(): OnlineStateMonitoringService;
|
|
58
58
|
protected getAppState(): import("../appstate").AppStateService;
|
|
59
|
+
protected getFeatureToggleSync(): import("../appstate").FeatureToggleSyncService;
|
|
59
60
|
}
|
|
60
61
|
export declare const useIncyclist: () => UserInterfaceServcie;
|
|
@@ -57,7 +57,6 @@ export declare class WorkoutRidePageService extends IncyclistPageService impleme
|
|
|
57
57
|
protected getLoadIncrement(): number;
|
|
58
58
|
protected buildGraphPlan(current: Workout | undefined, ftp: number): WorkoutGraphPlan;
|
|
59
59
|
protected buildUpcomingSteps(current: Workout | undefined, ftp: number): WorkoutUpcomingSteps;
|
|
60
|
-
protected getMobileStepTitle(wo: WorkoutDisplayProperties): string;
|
|
61
60
|
protected buildDashboardLine(wo: WorkoutDisplayProperties): WorkoutDashboardLine;
|
|
62
61
|
protected getStepAbsolutePower(step: StepDefinition, ftp?: number): number | null;
|
|
63
62
|
protected getStepFlags(): {
|
|
@@ -56,6 +56,7 @@ export declare class WorkoutRide extends IncyclistService {
|
|
|
56
56
|
start: any;
|
|
57
57
|
stop: any;
|
|
58
58
|
};
|
|
59
|
+
protected getBindings(): import("../../api").IncyclistBindings;
|
|
59
60
|
protected getStepTitle(time: number): string;
|
|
60
61
|
protected getPowerVal(power: PowerLimit, key: 'min' | 'max'): number;
|
|
61
62
|
protected getFtpFromUserSettings(): any;
|