incyclist-services 1.7.87 → 1.7.88

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.
@@ -142,10 +142,7 @@ let TacxParser = (() => {
142
142
  }
143
143
  }
144
144
  buildVideoUrl(filePath) {
145
- if (filePath.startsWith('/') || /^[A-Za-z]:/.test(filePath)) {
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
- this.logEvent({ message: 'download finished ', title, id, url });
221
- observer.emit('done', `video:///${file}`);
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.url) {
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 route_1 = require("../../base/utils/route");
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, route_1.validateDistance)(details.points);
264
- (0, route_1.validateSlopes)(details.points);
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, route_1.getTotalElevation)(details);
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
- (0, route_1.addDetails)(route, details);
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 ||= this.verifyVideoUrl(route);
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 || !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;
@@ -892,6 +892,13 @@ let RouteListService = (() => {
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
+ card.resetDownload();
899
+ }
900
+ });
901
+ }
895
902
  })
896
903
  .catch(() => {
897
904
  });
@@ -167,7 +167,7 @@ let WorkoutRidePageService = (() => {
167
167
  const current = this.getRideDisplay().getDisplayProperties().workout;
168
168
  return {
169
169
  ...base,
170
- title: this.getMobileStepTitle(wo),
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 = this.getMobileStepTitle(wo);
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 text = title ? `${target} for ${duration} - ${title}` : `${target} for ${duration}`;
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
- let ch = ': ';
531
- if (segment?.text) {
532
- title += `${ch}${segment.text}`;
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
- title += `(${repeatCount}/${segment.repeat})`;
550
- }
551
- }
552
- return title;
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)
@@ -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
- if (filePath.startsWith('/') || /^[A-Za-z]:/.test(filePath)) {
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
- this.logEvent({ message: 'download finished ', title, id, url });
218
- observer.emit('done', `video:///${file}`);
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.url) {
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 ||= this.verifyVideoUrl(route);
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 || !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;
@@ -886,6 +886,13 @@ let RouteListService = (() => {
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
+ card.resetDownload();
893
+ }
894
+ });
895
+ }
889
896
  })
890
897
  .catch(() => {
891
898
  });
@@ -164,7 +164,7 @@ let WorkoutRidePageService = (() => {
164
164
  const current = this.getRideDisplay().getDisplayProperties().workout;
165
165
  return {
166
166
  ...base,
167
- title: this.getMobileStepTitle(wo),
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 = this.getMobileStepTitle(wo);
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 text = title ? `${target} for ${duration} - ${title}` : `${target} for ${duration}`;
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
- let ch = ': ';
528
- if (segment?.text) {
529
- title += `${ch}${segment.text}`;
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
- title += `(${repeatCount}/${segment.repeat})`;
547
- }
548
- }
549
- return title;
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)
@@ -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
- protected resetDownload(): Promise<void>;
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>;
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.7.87",
3
+ "version": "1.7.88",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.27"
6
6
  },