incyclist-services 1.0.56 → 1.0.58

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.
@@ -1,2 +1,3 @@
1
1
  export * from './rest/index';
2
2
  export * from './rest/types';
3
+ export * from './repository';
package/lib/api/index.js CHANGED
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./rest/index"), exports);
18
18
  __exportStar(require("./rest/types"), exports);
19
+ __exportStar(require("./repository"), exports);
@@ -1,19 +1,21 @@
1
1
  import { EventLogger } from "gd-eventlog";
2
- import { IRepositoryBinding, JSONObject } from "../types";
2
+ import { IJsonRepositoryBinding, JSONObject, JsonAccess } from "../types";
3
3
  export declare class JsonRepository {
4
4
  protected static _instances: {
5
5
  [x: string]: JsonRepository;
6
6
  };
7
- protected static _defaultBinding: IRepositoryBinding;
7
+ protected static _defaultBinding: IJsonRepositoryBinding;
8
8
  static create(repoName: string): JsonRepository;
9
- static setBinding(binding: IRepositoryBinding): void;
10
- protected binding: IRepositoryBinding;
9
+ static setBinding(binding: IJsonRepositoryBinding): void;
10
+ protected binding: IJsonRepositoryBinding;
11
11
  protected name: string;
12
12
  protected db: string;
13
13
  protected logger: EventLogger;
14
- constructor(repoName: string, binding?: IRepositoryBinding);
14
+ protected access: JsonAccess;
15
+ constructor(repoName: string, binding?: IJsonRepositoryBinding);
16
+ getName(): string;
17
+ write(objectName: string, data: JSONObject): Promise<boolean>;
18
+ read(objectName: string): Promise<JSONObject>;
15
19
  protected open(): Promise<boolean>;
16
20
  protected close(): Promise<boolean>;
17
- protected write(objectName: string, data: JSONObject): Promise<boolean>;
18
- protected read(objectName: string): Promise<JSONObject>;
19
21
  }
@@ -26,16 +26,48 @@ class JsonRepository {
26
26
  this.binding = binding || JsonRepository._defaultBinding;
27
27
  this.logger = new gd_eventlog_1.EventLogger(`Repo-${this.name}`);
28
28
  }
29
+ getName() {
30
+ return this.name;
31
+ }
32
+ write(objectName, data) {
33
+ return __awaiter(this, void 0, void 0, function* () {
34
+ yield this.open();
35
+ const success = yield this.access.write(objectName, data);
36
+ if (!success) {
37
+ }
38
+ return success;
39
+ });
40
+ }
41
+ read(objectName) {
42
+ return __awaiter(this, void 0, void 0, function* () {
43
+ yield this.open();
44
+ try {
45
+ const data = yield this.access.read(objectName);
46
+ if (!data) {
47
+ }
48
+ return data;
49
+ }
50
+ catch (err) {
51
+ }
52
+ });
53
+ }
29
54
  open() {
30
55
  return __awaiter(this, void 0, void 0, function* () {
56
+ if (this.access)
57
+ return true;
31
58
  if (!this.binding)
32
59
  throw new Error('no binding specified');
33
60
  try {
34
61
  const existing = yield this.binding.get(this.name);
35
62
  if (existing) {
63
+ this.access = existing;
36
64
  return true;
37
65
  }
38
- return yield this.binding.create(this.name);
66
+ const access = yield this.binding.create(this.name);
67
+ if (!access)
68
+ return false;
69
+ this.access = access;
70
+ return true;
39
71
  }
40
72
  catch (err) {
41
73
  return false;
@@ -47,29 +79,10 @@ class JsonRepository {
47
79
  if (!this.binding)
48
80
  throw new Error('no binding specified');
49
81
  this.binding.release(this.name);
82
+ this.access = null;
50
83
  return true;
51
84
  });
52
85
  }
53
- write(objectName, data) {
54
- if (!this.binding)
55
- throw new Error('no binding specified');
56
- const success = this.binding.write(objectName, data);
57
- if (!success) {
58
- }
59
- return success;
60
- }
61
- read(objectName) {
62
- if (!this.binding)
63
- throw new Error('no binding specified');
64
- try {
65
- const data = this.binding.read(objectName);
66
- if (!data) {
67
- }
68
- return data;
69
- }
70
- catch (err) {
71
- }
72
- }
73
86
  }
74
87
  exports.JsonRepository = JsonRepository;
75
88
  JsonRepository._instances = {};
@@ -1,10 +1,18 @@
1
1
  export type JSONObject = string | number | boolean | {
2
2
  [x: string]: JSONObject;
3
3
  } | Array<JSONObject>;
4
- export interface IRepositoryBinding {
5
- create(name: string): Promise<boolean>;
6
- get(name: string): Promise<boolean>;
7
- release(name: string): Promise<boolean>;
4
+ export type JsonAccess = {
8
5
  read(resourceName: string): Promise<JSONObject>;
9
6
  write(resourceName: string, data: JSONObject): Promise<boolean>;
7
+ delete(resourceName: string): Promise<boolean>;
8
+ };
9
+ export interface IJsonRepositoryBinding {
10
+ create(name: string): Promise<JsonAccess | null>;
11
+ get(name: string): Promise<JsonAccess | null>;
12
+ release(name: string): Promise<boolean>;
13
+ }
14
+ export declare abstract class AbstractJsonRepositoryBinding implements IJsonRepositoryBinding {
15
+ abstract create(name: string): Promise<JsonAccess | null>;
16
+ abstract get(name: string): Promise<JsonAccess | null>;
17
+ abstract release(name: string): Promise<boolean>;
10
18
  }
@@ -1,2 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AbstractJsonRepositoryBinding = void 0;
4
+ class AbstractJsonRepositoryBinding {
5
+ }
6
+ exports.AbstractJsonRepositoryBinding = AbstractJsonRepositoryBinding;
@@ -198,12 +198,13 @@ class DevicePairingService extends service_2.IncyclistService {
198
198
  const c = this.getCapability(capability);
199
199
  if (cntSelected === 1 && c.selected === udid) {
200
200
  const adapter = this.getDeviceAdapter(udid);
201
- adapter.stop();
201
+ if (adapter)
202
+ adapter.stop();
202
203
  }
203
204
  if (deleteAll) {
204
205
  const adapter = this.getDeviceAdapter(udid);
205
206
  const capabilities = adapter.getCapabilities();
206
- capabilities.forEach((c, idx) => {
207
+ capabilities === null || capabilities === void 0 ? void 0 : capabilities.forEach((c, idx) => {
207
208
  const shouldEmit = (idx === capabilities.length - 1);
208
209
  this.deleteCapabilityDevice(c, udid, shouldEmit);
209
210
  });
@@ -732,7 +733,7 @@ class DevicePairingService extends service_2.IncyclistService {
732
733
  run(props = {}) {
733
734
  return __awaiter(this, void 0, void 0, function* () {
734
735
  this.emit('run');
735
- if (this.isPairing() || this.isScanning() || !this.state.waiting) {
736
+ if ((this.isPairing() || this.isScanning()) && !this.state.waiting) {
736
737
  yield this._stop();
737
738
  }
738
739
  if (this.state.stopRequested || this.state.stopped) {
@@ -847,6 +848,10 @@ class DevicePairingService extends service_2.IncyclistService {
847
848
  this.state.tsPrevStart = Date.now();
848
849
  if (this.isScanning() && this.state.scan.preparing !== preparing)
849
850
  return;
851
+ this.logEvent({ message: 'Stopping Adapters', interfaces: interfaces.join(','), props });
852
+ const stopPromises = [];
853
+ interfaces.forEach(i => stopPromises.push(this.stopAdaptersOnInterface(i)));
854
+ yield Promise.allSettled(stopPromises);
850
855
  this.logEvent({ message: 'Start Scanning', interfaces: interfaces.join(','), props });
851
856
  this.initScanningCallbacks();
852
857
  const timeout = props.enforcedScan ? 1000 * 60 * 60 : undefined;
@@ -1016,6 +1021,8 @@ class DevicePairingService extends service_2.IncyclistService {
1016
1021
  stopAdaptersWithCapability(capability, udid) {
1017
1022
  return __awaiter(this, void 0, void 0, function* () {
1018
1023
  const c = this.getCapability(capability);
1024
+ if (!c)
1025
+ return;
1019
1026
  const { adapters = [], capabilities } = this.state;
1020
1027
  const maxRetries = 3;
1021
1028
  let target = udid ?
@@ -32,6 +32,10 @@ export declare class DeviceRideService extends EventEmitter {
32
32
  startCheck(filter: RideServiceCheckFilter): Promise<void>;
33
33
  getAdapters(filter: RideServiceCheckFilter): AdapterRideInfo[];
34
34
  setSerialPortInUse(adapter: IncyclistDeviceAdapter): void;
35
+ protected checkAntSameDeviceID(adapters: AdapterRideInfo[]): Array<{
36
+ udid: string;
37
+ info: AdapterInfo;
38
+ }>;
35
39
  startAdapters(adapters: AdapterRideInfo[], startType: 'start' | 'check' | 'pair', props?: RideServiceDeviceProperties): Promise<boolean>;
36
40
  start(props: RideServiceDeviceProperties): Promise<boolean>;
37
41
  startRetry(props: RideServiceDeviceProperties): Promise<boolean>;
@@ -336,11 +336,39 @@ class DeviceRideService extends events_1.default {
336
336
  serial.setInUse(device.getPort());
337
337
  }
338
338
  }
339
+ checkAntSameDeviceID(adapters) {
340
+ const antDevices = adapters.map((ai, idx) => (Object.assign(Object.assign({}, ai), { idx })))
341
+ .filter(ai => ai.adapter.getInterface() === 'ant')
342
+ .map(ai => ({ idx: ai.idx, deviceID: ai.adapter.getID(), capabilities: ai.capabilities, udid: ai.udid }));
343
+ const antDeviceIds = antDevices.map(di => di.deviceID);
344
+ const duplicateIds = antDeviceIds.filter((item, index) => antDeviceIds.indexOf(item) !== index);
345
+ const score = (capabilities) => {
346
+ let value = 0;
347
+ if (capabilities.includes(incyclist_devices_1.IncyclistCapability.Control))
348
+ value += 100;
349
+ if (capabilities.includes(incyclist_devices_1.IncyclistCapability.Power))
350
+ value += 50;
351
+ value += capabilities.length;
352
+ return value;
353
+ };
354
+ const duplicateDevices = antDevices.filter(di => duplicateIds.includes(di.deviceID)).sort((a, b) => score(b.capabilities) - score(a.capabilities));
355
+ const leading = duplicateDevices[0];
356
+ const duplicateAdapters = [];
357
+ duplicateDevices.forEach((di, i) => {
358
+ if (i == 0)
359
+ return;
360
+ duplicateAdapters.push({ udid: leading.udid, info: adapters[di.idx] });
361
+ });
362
+ return duplicateAdapters;
363
+ }
339
364
  startAdapters(adapters, startType, props) {
340
365
  return __awaiter(this, void 0, void 0, function* () {
341
366
  const { forceErgMode, startPos, realityFactor, rideMode, route } = props || {};
367
+ const duplicates = this.checkAntSameDeviceID(adapters);
342
368
  this.startPromises = adapters === null || adapters === void 0 ? void 0 : adapters.map((ai) => __awaiter(this, void 0, void 0, function* () {
343
369
  var _a;
370
+ if (duplicates.find(dai => dai.info.udid === ai.udid))
371
+ return;
344
372
  const startProps = (0, clone_1.default)(props || {});
345
373
  if (startType === 'check' || startType === 'pair') {
346
374
  startProps.timeout = 10000;
@@ -389,6 +417,9 @@ class DeviceRideService extends events_1.default {
389
417
  .then((success) => __awaiter(this, void 0, void 0, function* () {
390
418
  if (success) {
391
419
  this.emit(`${startType}-success`, this.getAdapterStateInfo(ai));
420
+ if (duplicates.find(dai => dai.udid === ai.udid)) {
421
+ duplicates.forEach(dai => { this.emit(`${startType}-success`, this.getAdapterStateInfo(dai.info)); });
422
+ }
392
423
  this.logEvent(Object.assign({ message: `${startType} ${sType} request finished` }, logProps));
393
424
  if (startType === 'check')
394
425
  yield ai.adapter.pause().catch(console.log);
@@ -400,6 +431,9 @@ class DeviceRideService extends events_1.default {
400
431
  }
401
432
  else {
402
433
  this.emit(`${startType}-error`, this.getAdapterStateInfo(ai));
434
+ if (duplicates.find(dai => dai.udid === ai.udid)) {
435
+ duplicates.forEach(dai => { this.emit(`${startType}-error`, this.getAdapterStateInfo(dai.info)); });
436
+ }
403
437
  this.logEvent(Object.assign({ message: `${startType} ${sType} request failed` }, logProps));
404
438
  if (startType === 'check' || startType === 'pair')
405
439
  yield ai.adapter.stop().catch(console.log);
@@ -411,6 +445,9 @@ class DeviceRideService extends events_1.default {
411
445
  ai.isStarted = false;
412
446
  this.logEvent(Object.assign(Object.assign({ message: `${startType} ${sType} request failed` }, logProps), { reason: err.message }));
413
447
  this.emit(`${startType}-error`, this.getAdapterStateInfo(ai), err);
448
+ if (duplicates.find(dai => dai.udid === ai.udid)) {
449
+ duplicates.forEach(dai => { this.emit(`${startType}-error`, this.getAdapterStateInfo(dai.info)); });
450
+ }
414
451
  if (startType === 'check' || startType === 'pair') {
415
452
  yield ai.adapter.stop().catch(console.log);
416
453
  }
@@ -529,10 +566,29 @@ class DeviceRideService extends events_1.default {
529
566
  this.verifySelected(selectedDevices, incyclist_devices_1.IncyclistCapability.Speed);
530
567
  this.verifySelected(selectedDevices, incyclist_devices_1.IncyclistCapability.Cadence);
531
568
  this.verifySelected(selectedDevices, incyclist_devices_1.IncyclistCapability.Power);
532
- const enabledCapabilities = this.enforceSimulator ?
533
- [incyclist_devices_1.IncyclistCapability.Control, incyclist_devices_1.IncyclistCapability.Power, incyclist_devices_1.IncyclistCapability.Speed, incyclist_devices_1.IncyclistCapability.HeartRate, incyclist_devices_1.IncyclistCapability.Cadence]
534
- :
535
- selectedDevices.filter(sd => sd.selected === adapterInfo.udid).map(c => c.capability);
569
+ const duplicates = this.checkAntSameDeviceID(adapters);
570
+ const toBeReplaced = duplicates.filter(dai => dai.udid === adapterInfo.udid).map(dai => dai.info.udid);
571
+ let enabledCapabilities = [];
572
+ if (this.simulatorEnforced) {
573
+ enabledCapabilities = [incyclist_devices_1.IncyclistCapability.Control, incyclist_devices_1.IncyclistCapability.Power, incyclist_devices_1.IncyclistCapability.Speed, incyclist_devices_1.IncyclistCapability.HeartRate, incyclist_devices_1.IncyclistCapability.Cadence];
574
+ }
575
+ else if (duplicates.length > 0 && duplicates.find(d => d.udid === adapterInfo.udid)) {
576
+ const selected = (0, clone_1.default)(selectedDevices);
577
+ selected.forEach(cd => {
578
+ if (toBeReplaced.includes(cd.selected))
579
+ cd.selected = adapterInfo.udid;
580
+ });
581
+ selected.forEach;
582
+ selected.forEach(sd => {
583
+ const duplicate = duplicates.find(dai => dai.info.udid === sd.selected);
584
+ if (duplicate)
585
+ sd.selected = duplicate.udid;
586
+ });
587
+ enabledCapabilities = selected.map(c => c.capability);
588
+ }
589
+ else {
590
+ enabledCapabilities = selectedDevices.filter(sd => sd.selected === adapterInfo.udid).map(c => c.capability);
591
+ }
536
592
  this.logEvent({ message: 'Data Update', device: adapterInfo.adapter.getName(), data, enabledCapabilities });
537
593
  enabledCapabilities.forEach(capability => {
538
594
  switch (capability) {
@@ -559,6 +615,11 @@ class DeviceRideService extends events_1.default {
559
615
  }
560
616
  });
561
617
  this.emit('data', this.data, adapterInfo.udid);
618
+ if (toBeReplaced) {
619
+ toBeReplaced.forEach(udid => {
620
+ this.emit('data', this.data, udid);
621
+ });
622
+ }
562
623
  }
563
624
  sendUpdate(request) {
564
625
  const adapters = this.getAdapterList();
@@ -12,7 +12,8 @@ export default class IncyclistRoutesApi {
12
12
  protected getApi(): AxiosInstance;
13
13
  protected getBaseUrl(): any;
14
14
  getRouteDescriptions(query: RouteDescriptionQuery): Promise<Array<RouteApiDescription>>;
15
- fixMissingRouteDistances(points: Array<RoutePoint>): void;
15
+ static verify(route: any): void;
16
+ protected static fixMissingRouteDistances(points: Array<RoutePoint>): void;
16
17
  getRouteDetails(routeId: string): Promise<RouteApiDetail>;
17
18
  reload(): Promise<any>;
18
19
  protected _get(url: string, ...args: any[]): Promise<import("axios").AxiosResponse<any, any>>;
@@ -25,7 +25,6 @@ class IncyclistRoutesApi {
25
25
  logError(err, fn, logInfo) {
26
26
  const args = logInfo || {};
27
27
  this.logger.logEvent(Object.assign({ message: 'Error', error: err.message, fn }, args));
28
- console.log(err);
29
28
  }
30
29
  getApi() {
31
30
  if (!this.api) {
@@ -53,7 +52,14 @@ class IncyclistRoutesApi {
53
52
  }
54
53
  });
55
54
  }
56
- fixMissingRouteDistances(points) {
55
+ static verify(route) {
56
+ if (route['decoded']) {
57
+ route.points = route['decoded'];
58
+ delete route['decoded'];
59
+ IncyclistRoutesApi.fixMissingRouteDistances(route.points);
60
+ }
61
+ }
62
+ static fixMissingRouteDistances(points) {
57
63
  let routeDistance = 0;
58
64
  points.forEach((p) => {
59
65
  if (!p.routeDistance)
@@ -65,11 +71,7 @@ class IncyclistRoutesApi {
65
71
  return __awaiter(this, void 0, void 0, function* () {
66
72
  try {
67
73
  const res = yield this._get(`/${routeId}`);
68
- if (res.data['decoded']) {
69
- res.data.points = res.data['decoded'];
70
- delete res.data['decoded'];
71
- this.fixMissingRouteDistances(res.data.points);
72
- }
74
+ IncyclistRoutesApi.verify(res.data);
73
75
  return res.data;
74
76
  }
75
77
  catch (err) {
@@ -25,6 +25,23 @@ export type RouteApiDescription = {
25
25
  type?: RouteType;
26
26
  localizedTitle?: LocalizedText;
27
27
  };
28
+ export type LegacyRouteGpxRepoDescription = {
29
+ id: string;
30
+ hash?: string;
31
+ routeHash?: string;
32
+ title: string;
33
+ private?: boolean;
34
+ distance?: number;
35
+ elevation?: number;
36
+ author?: {
37
+ id: string;
38
+ };
39
+ points?: {
40
+ id: string;
41
+ };
42
+ __v: number;
43
+ category?: RouteCategory;
44
+ };
28
45
  export type RouteApiDetail = {
29
46
  id: string;
30
47
  routeId?: string;
@@ -1,5 +1,5 @@
1
1
  export type RouteType = 'gpx' | 'video';
2
- export type RouteCategory = 'Free' | 'Demo';
2
+ export type RouteCategory = 'Free' | 'Demo' | 'personal';
3
3
  export type RouteState = 'prepared' | 'loading' | 'loaded' | 'error';
4
4
  export type RouteProvider = {
5
5
  name: string;
@@ -35,7 +35,7 @@ export type RoutePoint = {
35
35
  lng: number;
36
36
  routeDistance: number;
37
37
  elevation: number;
38
- slope: number;
38
+ slope?: number;
39
39
  };
40
40
  export type RouteSegment = {
41
41
  start: number;
@@ -0,0 +1,8 @@
1
+ import { RouteState } from "../base/types";
2
+ import { CardInfo, CardType } from "./types";
3
+ export declare class FreeRideCard implements CardInfo {
4
+ type: CardType;
5
+ state: RouteState;
6
+ title?: string;
7
+ constructor(state: any, title?: any);
8
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FreeRideCard = void 0;
4
+ class FreeRideCard {
5
+ constructor(state, title) {
6
+ this.type = 'free-ride';
7
+ this.state = state;
8
+ this.title = title || 'Free Ride';
9
+ }
10
+ }
11
+ exports.FreeRideCard = FreeRideCard;
@@ -1,20 +1,24 @@
1
- import { Page, IRouteListBinding, InternalRouteListState, List, RouteInfo, Route, RouteListEntry, RouteListStartProps, RouteListData, RouteStartSettings } from "./types";
1
+ import { Page, InternalRouteListState, List, RouteInfo, Route, RouteListEntry, RouteListStartProps, RouteListData, RouteStartSettings, onRouteStatusUpdateCallback, onCarouselStateChangedCallback, RoutesDB } from "./types";
2
2
  import IncyclistRoutesApi from "../base/api";
3
3
  import { RouteApiDescription, RouteApiDetail } from "../base/api/types";
4
4
  import { IncyclistService } from "../../base/service";
5
+ import { IJsonRepositoryBinding, JsonRepository } from "../../api";
5
6
  type filterFn = (route: Route, idx: number, obj: Route[]) => boolean;
6
7
  export declare class RouteListService extends IncyclistService {
7
8
  protected static _instance: any;
8
- static getInstance(binding?: IRouteListBinding): RouteListService;
9
+ static getInstance(binding?: IJsonRepositoryBinding): RouteListService;
9
10
  protected baseDir: string;
10
11
  protected state: InternalRouteListState;
11
- protected binding: IRouteListBinding;
12
- protected routes: Array<RouteListEntry>;
12
+ protected lists: Array<RouteListEntry>;
13
+ protected videosRepo: JsonRepository;
14
+ protected routesRepo: JsonRepository;
13
15
  protected api: IncyclistRoutesApi;
14
- constructor(binding?: IRouteListBinding);
15
- setBinding(binding: IRouteListBinding): void;
16
+ protected routeDescriptions: RoutesDB;
17
+ constructor(binding?: IJsonRepositoryBinding);
18
+ setBinding(binding: IJsonRepositoryBinding): void;
19
+ protected getVideosRepo(): JsonRepository;
20
+ protected getRoutesRepo(): JsonRepository;
16
21
  preload(carouselSize?: number): Promise<void>;
17
- protected _preload(carouselSize?: number, detailsOnly?: boolean): Promise<void>;
18
22
  load(props: {
19
23
  list?: List;
20
24
  loadFrom?: number;
@@ -22,9 +26,13 @@ export declare class RouteListService extends IncyclistService {
22
26
  routeId?: string;
23
27
  }): Promise<void>;
24
28
  openRouteSelection(pageId: string, props: RouteListStartProps): RouteListData;
25
- closeRouteSelection(listId: string): void;
29
+ closeRouteSelection(pageId: string): void;
30
+ onResize(pageId: string): void;
26
31
  openStartSettings(r: RouteInfo | string): RouteStartSettings;
27
32
  cancelStartSettings(r: RouteInfo | string): {};
33
+ setCardUpdateHandler(pageId: string, r: RouteInfo | string, list: List, idx: number, onRouteStateChanged: onRouteStatusUpdateCallback, onCarouselStateChanged: onCarouselStateChangedCallback): void;
34
+ onCarouselInitialized(pageId: string, list: List, startIdx: number, visibleSize: number): void;
35
+ onCarouselUpdated(pageId: string, list: List, startIdx: number, visibleSize: number): void;
28
36
  acceptStartSettings(r: RouteInfo | string, startSettings: RouteStartSettings): {};
29
37
  getSelectedRoute(): RouteApiDetail;
30
38
  getStartSettings(routeId?: string): RouteStartSettings;
@@ -32,6 +40,8 @@ export declare class RouteListService extends IncyclistService {
32
40
  stopRide(r?: RouteInfo | string): {};
33
41
  startDownload(routeId: any, onStatusUpdate: any): void;
34
42
  cancelDownload(routeId: any): void;
43
+ protected _preload(carouselSize?: number, detailsOnly?: boolean): Promise<void>;
44
+ protected preloadList(list: List, carouselSize?: number): Promise<void>;
35
45
  protected loadStartSettingsFromUserSettings(route: Route): {
36
46
  startPos: number;
37
47
  realityFactor: number;
@@ -54,13 +64,20 @@ export declare class RouteListService extends IncyclistService {
54
64
  protected getRouteDescription(list: List, id: string): RouteInfo;
55
65
  protected mergeDescription(local: RouteInfo, fromApi: RouteInfo): void;
56
66
  protected mergeWithDetails(route: Route): void;
57
- protected convertDescription(descr: RouteApiDescription): RouteInfo;
67
+ protected convertDescription(descr: RouteApiDescription, isLocal?: boolean): RouteInfo;
58
68
  protected getListFromApiDesciption(description: RouteApiDescription): "selected" | "myRoutes" | "alternatives";
59
69
  protected addRouteToList(list: List, route: Route): void;
60
70
  protected getRouteList(list: List): Array<Route>;
71
+ protected getRouteLists(): Array<{
72
+ list: List;
73
+ title: string;
74
+ }>;
61
75
  protected getRoute(criteria: string | filterFn): Route;
76
+ protected getAllRoutes(): Array<Route>;
62
77
  protected stopAllRides(): void;
63
- protected addDescriptions(descriptions: Array<RouteApiDescription>): void;
78
+ protected addDescriptionsFromDB(descriptions: RoutesDB): void;
79
+ protected addDescriptionsFromRepo(descriptions: Array<RouteApiDescription>, repo?: JsonRepository): void;
80
+ protected addDescriptionsFromServer(descriptions: Array<RouteApiDescription>): void;
64
81
  protected updateRouteTitle(data: RouteInfo, route: {
65
82
  descr?: RouteApiDescription;
66
83
  }): void;
@@ -69,10 +86,18 @@ export declare class RouteListService extends IncyclistService {
69
86
  }): void;
70
87
  protected getCountryPrefix(title?: string): string | undefined;
71
88
  protected removeCountryPrefix(title?: string): string;
89
+ protected loadRouteDescriptionsFromRepo(): Promise<void>;
90
+ protected saveRouteDescriptions(): Promise<void>;
91
+ protected loadRouteDescriptionsFromServer(): Promise<void>;
72
92
  protected loadRouteDescriptions(): Promise<void>;
73
- protected loadRouteDetais(route: Route): Promise<void>;
93
+ protected loadRouteDetais(route: Route): Promise<RouteInfo>;
94
+ saveRouteDetails(route: any): Promise<void>;
74
95
  protected emitRouteUpdated(route: Route): void;
96
+ protected getListState(page: Page, list: List): import("./types").RouteListDateEntry;
75
97
  private getRouteFromStartProps;
98
+ private isDescriptionsLoaded;
99
+ private decodePoints;
100
+ private encodePoints;
76
101
  }
77
102
  export declare const useRouteList: () => RouteListService;
78
103
  export {};
@@ -18,6 +18,9 @@ const localization_1 = require("../base/utils/localization");
18
18
  const service_1 = require("../../base/service");
19
19
  const route_1 = require("../base/utils/route");
20
20
  const settings_1 = require("../../settings");
21
+ const api_2 = require("../../api");
22
+ const clone_1 = __importDefault(require("../../utils/clone"));
23
+ const FreeRideCard_1 = require("./FreeRideCard");
21
24
  class RouteListService extends service_1.IncyclistService {
22
25
  static getInstance(binding) {
23
26
  if (!RouteListService._instance)
@@ -28,52 +31,44 @@ class RouteListService extends service_1.IncyclistService {
28
31
  super('RouteList');
29
32
  this.state = {
30
33
  initialized: false,
31
- pages: []
34
+ pages: [],
35
+ preloadDone: false
32
36
  };
33
- this.setBinding(binding);
37
+ if (binding)
38
+ api_2.JsonRepository.setBinding(binding);
34
39
  this.api = api_1.default.getInstance();
35
40
  this.initRouteLists();
41
+ this.routeDescriptions = {};
36
42
  }
37
43
  setBinding(binding) {
38
44
  if (!binding)
39
45
  return;
40
- this.binding = binding;
46
+ api_2.JsonRepository.setBinding(binding);
41
47
  this.state.initialized = true;
42
48
  }
49
+ getVideosRepo() {
50
+ if (this.videosRepo)
51
+ return this.videosRepo;
52
+ this.videosRepo = api_2.JsonRepository.create('videos');
53
+ return this.videosRepo;
54
+ }
55
+ getRoutesRepo() {
56
+ if (this.routesRepo)
57
+ return this.routesRepo;
58
+ this.routesRepo = api_2.JsonRepository.create('routes');
59
+ return this.routesRepo;
60
+ }
43
61
  preload(carouselSize = 5) {
44
62
  return __awaiter(this, void 0, void 0, function* () {
45
- if (this.state.loading)
46
- yield this.state.loading.promise;
63
+ if (this.state.loading || this.state.preloadDone)
64
+ return;
47
65
  const promise = this._preload(carouselSize);
48
66
  this.state.loading = { promise };
49
67
  yield promise;
68
+ this.state.preloadDone = true;
50
69
  this.state.loading = null;
51
70
  });
52
71
  }
53
- _preload(carouselSize = 5, detailsOnly = false) {
54
- return __awaiter(this, void 0, void 0, function* () {
55
- this.logEvent({ message: 'preloading routes --' });
56
- if (!detailsOnly) {
57
- yield this.loadRouteDescriptions();
58
- }
59
- const promises = [];
60
- this.routes.forEach(rle => {
61
- var _a;
62
- if (((_a = rle.routes) === null || _a === void 0 ? void 0 : _a.length) > 0) {
63
- this.sort(rle.routes);
64
- rle.routes.forEach((r, idx) => {
65
- if (idx < Math.min(carouselSize, rle.routes.length)) {
66
- const loader = this.loadRouteDetais(r);
67
- promises.push(loader);
68
- }
69
- });
70
- }
71
- });
72
- yield Promise.allSettled(promises);
73
- this.emitPageUpdate();
74
- this.logEvent({ message: 'preloading routes finished' });
75
- });
76
- }
77
72
  load(props) {
78
73
  return __awaiter(this, void 0, void 0, function* () {
79
74
  const { routeId, list, loadFrom, loadTo } = props;
@@ -86,7 +81,7 @@ class RouteListService extends service_1.IncyclistService {
86
81
  if (list)
87
82
  lists.push(list);
88
83
  else
89
- lists = this.routes.map(rle => rle.list);
84
+ lists = this.lists.map(rle => rle.list);
90
85
  lists.forEach(l => {
91
86
  const routes = this.getRouteList(l);
92
87
  for (let i = loadFrom; i < loadTo; i++) {
@@ -97,24 +92,39 @@ class RouteListService extends service_1.IncyclistService {
97
92
  }
98
93
  openRouteSelection(pageId, props) {
99
94
  const { language, visibleCards, visibleLists } = props;
100
- this.logEvent({ message: 'start', pageId, language, visibleCards, visibleLists });
95
+ this.logEvent({ message: 'openRouteSelection', pageId, language, visibleCards, visibleLists });
101
96
  const existing = this.getPage(pageId);
102
97
  if (existing) {
103
98
  this.closeRouteSelection(pageId);
104
99
  }
105
100
  this.stopAllRides();
106
- if (props.visibleCards) {
107
- this._preload(props.visibleCards, true);
108
- }
109
101
  const page = this.registerPage(pageId, props);
102
+ if (!this.state.preloadDone)
103
+ this.preload(props.visibleCards || 5);
104
+ else
105
+ this.onResize(pageId);
110
106
  return page.state;
111
107
  }
112
- closeRouteSelection(listId) {
113
- const idx = this.getPage(listId, true);
108
+ closeRouteSelection(pageId) {
109
+ const idx = this.getPage(pageId, true);
114
110
  if (idx !== -1) {
115
111
  this.state.pages.splice(idx, 1);
116
112
  }
117
113
  }
114
+ onResize(pageId) {
115
+ const page = this.getPage(pageId);
116
+ const routeIds = Object.keys(page.onCarouselStateChanged);
117
+ routeIds.forEach(id => {
118
+ var _a;
119
+ const route = this.getRoute(id);
120
+ const stateChange = (_a = page.onCarouselStateChanged[id]) === null || _a === void 0 ? void 0 : _a.onCarouselStateChanged;
121
+ if (stateChange)
122
+ stateChange({ initialized: true, visible: false });
123
+ const update = page.onRouteUpdate[id].onRouteStateChanged;
124
+ if (update)
125
+ update(route.data);
126
+ });
127
+ }
118
128
  openStartSettings(r) {
119
129
  var _a;
120
130
  const route = this.getRouteFromStartProps(r);
@@ -146,6 +156,56 @@ class RouteListService extends service_1.IncyclistService {
146
156
  return {};
147
157
  }
148
158
  }
159
+ setCardUpdateHandler(pageId, r, list, idx, onRouteStateChanged, onCarouselStateChanged) {
160
+ const route = this.getRouteFromStartProps(r);
161
+ if (!route)
162
+ return;
163
+ const page = this.getPage(pageId);
164
+ if (!page)
165
+ return;
166
+ page.onRouteUpdate[route.id] = { idx, onRouteStateChanged };
167
+ page.onCarouselStateChanged[route.id] = { idx, onCarouselStateChanged };
168
+ }
169
+ onCarouselInitialized(pageId, list, startIdx, visibleSize) {
170
+ const page = this.getPage(pageId);
171
+ if (!page)
172
+ return;
173
+ const listState = this.getListState(page, list);
174
+ if (listState) {
175
+ listState.startIdx = startIdx;
176
+ listState.endIdx = startIdx + visibleSize - 1;
177
+ }
178
+ const routes = this.getRouteList(list);
179
+ routes.forEach((route) => {
180
+ const { idx, onCarouselStateChanged } = page.onCarouselStateChanged[route.id] || {};
181
+ const visible = listState !== undefined ? idx >= listState.startIdx && idx <= listState.endIdx : undefined;
182
+ if (onCarouselStateChanged)
183
+ onCarouselStateChanged({ initialized: true, visible });
184
+ if (visible && (route.data.state === 'prepared' || route.data.state === 'error')) {
185
+ this.loadRouteDetais(route);
186
+ }
187
+ });
188
+ }
189
+ onCarouselUpdated(pageId, list, startIdx, visibleSize) {
190
+ const page = this.getPage(pageId);
191
+ if (!page)
192
+ return;
193
+ const listState = this.getListState(page, list);
194
+ if (listState) {
195
+ listState.startIdx = startIdx;
196
+ listState.endIdx = startIdx + visibleSize - 1;
197
+ }
198
+ const routes = this.getRouteList(list);
199
+ routes.forEach((route) => {
200
+ const { idx, onCarouselStateChanged } = page.onCarouselStateChanged[route.id] || {};
201
+ const visible = listState !== undefined ? idx >= listState.startIdx && idx <= listState.endIdx : undefined;
202
+ if (onCarouselStateChanged)
203
+ onCarouselStateChanged({ initialized: true, visible });
204
+ if (visible && route.data.state === 'prepared' || route.data.state === 'error') {
205
+ this.loadRouteDetais(route);
206
+ }
207
+ });
208
+ }
149
209
  acceptStartSettings(r, startSettings) {
150
210
  var _a;
151
211
  const route = this.getRouteFromStartProps(r);
@@ -192,6 +252,44 @@ class RouteListService extends service_1.IncyclistService {
192
252
  }
193
253
  cancelDownload(routeId) {
194
254
  }
255
+ _preload(carouselSize = 5, detailsOnly = false) {
256
+ return __awaiter(this, void 0, void 0, function* () {
257
+ if (!detailsOnly) {
258
+ yield this.loadRouteDescriptions();
259
+ }
260
+ this.emitPageUpdate();
261
+ const lists = this.getRouteLists().map(li => li.list);
262
+ lists.forEach((list) => {
263
+ this.preloadList(list, carouselSize);
264
+ });
265
+ this.logEvent({ message: 'preloading routes finished' });
266
+ });
267
+ }
268
+ preloadList(list, carouselSize = 5) {
269
+ var _a;
270
+ return __awaiter(this, void 0, void 0, function* () {
271
+ this.emitPageUpdate();
272
+ const promises = [];
273
+ const rle = this.lists.find(l => l.list === list);
274
+ if (((_a = rle.routes) === null || _a === void 0 ? void 0 : _a.length) > 0) {
275
+ this.sort(rle.routes);
276
+ let cntApi = 0;
277
+ rle.routes.forEach((r) => {
278
+ if (!r.data.isLocal) {
279
+ cntApi++;
280
+ }
281
+ if (r.data.isLocal || cntApi <= Math.min(carouselSize, rle.routes.length)) {
282
+ promises.push(this.loadRouteDetais(r));
283
+ }
284
+ });
285
+ }
286
+ console.log('~~~ DEBUG: during preload', promises.length);
287
+ Promise.allSettled(promises).then((res) => {
288
+ console.log('~~~ DEBUG: after preload', (0, clone_1.default)(this.state), (0, clone_1.default)(this.routeDescriptions), res);
289
+ this.saveRouteDescriptions();
290
+ });
291
+ });
292
+ }
195
293
  loadStartSettingsFromUserSettings(route) {
196
294
  let startPos = 0, realityFactor = 100, segment;
197
295
  const { data } = route;
@@ -211,6 +309,10 @@ class RouteListService extends service_1.IncyclistService {
211
309
  if (prevRoutePosition !== null) {
212
310
  startPos = prevRoutePosition;
213
311
  }
312
+ if (data.videoFormat === 'avi') {
313
+ startPos = 0;
314
+ segment = undefined;
315
+ }
214
316
  return { startPos, realityFactor, segment };
215
317
  }
216
318
  writeStartSettingsToUserSettings(route, startSettings) {
@@ -240,15 +342,15 @@ class RouteListService extends service_1.IncyclistService {
240
342
  }
241
343
  emitPageUpdate() {
242
344
  this.state.pages.forEach(p => {
243
- const state = this.getPageState(p.id);
244
- p.onStatusUpdate(state);
345
+ p.state = this.getPageState(p.id);
346
+ p.onStatusUpdate(p.state);
245
347
  });
246
348
  }
247
349
  initRouteLists() {
248
- this.routes = [];
249
- this.routes.push({ list: "myRoutes", routes: [] });
250
- this.routes.push({ list: "selected", routes: [] });
251
- this.routes.push({ list: "alternatives", routes: [] });
350
+ this.lists = [];
351
+ this.lists.push({ list: "myRoutes", routes: [] });
352
+ this.lists.push({ list: "selected", routes: [] });
353
+ this.lists.push({ list: "alternatives", routes: [] });
252
354
  }
253
355
  getPageState(pageId, lang) {
254
356
  var _a;
@@ -257,6 +359,11 @@ class RouteListService extends service_1.IncyclistService {
257
359
  const language = lang || ((_a = this.getPage(pageId)) === null || _a === void 0 ? void 0 : _a.language) || 'en';
258
360
  const add = (list, listHeader) => {
259
361
  const data = this.getRouteList(list);
362
+ if (list === 'myRoutes' && !data.find(r => r.data.type === 'free-ride')) {
363
+ const card = new FreeRideCard_1.FreeRideCard('loaded');
364
+ const route = { data: card, id: 'free-ride', };
365
+ data.unshift(route);
366
+ }
260
367
  if ((data === null || data === void 0 ? void 0 : data.length) > 0) {
261
368
  lists.push(this.getRouteListDataEntry(list, data, language, listHeader));
262
369
  }
@@ -264,12 +371,13 @@ class RouteListService extends service_1.IncyclistService {
264
371
  add('myRoutes', 'My Routes');
265
372
  add('selected', 'Selected For Me');
266
373
  add('alternatives', 'Alternatives');
374
+ console.log('~~~ DEBUG:getPageState', state);
267
375
  return state;
268
376
  }
269
377
  registerPage(pageId, props) {
270
378
  const { onStatusUpdate, language = 'en' } = props;
271
379
  const state = this.getPageState(pageId, language);
272
- const page = { id: pageId, onStatusUpdate, language, state };
380
+ const page = { id: pageId, onStatusUpdate, onRouteUpdate: {}, onCarouselStateChanged: {}, language, state };
273
381
  this.state.pages.push(page);
274
382
  return page;
275
383
  }
@@ -317,7 +425,9 @@ class RouteListService extends service_1.IncyclistService {
317
425
  local.distance = local.distance || details.distance;
318
426
  local.elevation = local.elevation || details.elevation;
319
427
  local.localizedTitle = local.localizedTitle || details.localizedTitle;
320
- local.points = details.points;
428
+ if (!local.points) {
429
+ local.points = details.points;
430
+ }
321
431
  if (!local.hasGpx && details.points) {
322
432
  local.hasGpx = true;
323
433
  }
@@ -342,11 +452,12 @@ class RouteListService extends service_1.IncyclistService {
342
452
  }
343
453
  route.data = local;
344
454
  }
345
- convertDescription(descr) {
455
+ convertDescription(descr, isLocal) {
346
456
  const { id, title, localizedTitle, country, distance, elevation, category, provider, video, points } = descr;
347
- const data = { state: 'prepared', id, title, localizedTitle, country, distance, elevation, provider, category };
457
+ const data = { type: 'route', state: 'prepared', id, title, localizedTitle, country, distance, elevation, provider, category };
348
458
  data.hasVideo = false;
349
459
  data.hasGpx = false;
460
+ data.isLocal = isLocal || false;
350
461
  this.updateRouteCountry(data, { descr });
351
462
  this.updateRouteTitle(data, { descr });
352
463
  if (points)
@@ -366,10 +477,14 @@ class RouteListService extends service_1.IncyclistService {
366
477
  var _a;
367
478
  let list;
368
479
  if (description.type === 'gpx') {
369
- list = description.private ? 'myRoutes' : 'selected';
480
+ if (description.category === 'personal')
481
+ return 'myRoutes';
482
+ list = description.category ? 'alternatives' : 'selected';
370
483
  return list;
371
484
  }
372
485
  if (description.type === 'video') {
486
+ if (description.category === undefined || description.category === 'personal')
487
+ return 'myRoutes';
373
488
  if ((_a = description.video) === null || _a === void 0 ? void 0 : _a.url) {
374
489
  return 'selected';
375
490
  }
@@ -382,28 +497,82 @@ class RouteListService extends service_1.IncyclistService {
382
497
  }
383
498
  getRouteList(list) {
384
499
  var _a;
385
- return (_a = this.routes.find(rle => rle.list === list)) === null || _a === void 0 ? void 0 : _a.routes;
500
+ return (_a = this.lists.find(rle => rle.list === list)) === null || _a === void 0 ? void 0 : _a.routes;
501
+ }
502
+ getRouteLists() {
503
+ const lists = [
504
+ { list: 'myRoutes', title: 'My Routes' },
505
+ { list: 'selected', title: 'Selected For Me' },
506
+ { list: 'alternatives', title: 'Alternatives' }
507
+ ];
508
+ return lists;
386
509
  }
387
510
  getRoute(criteria) {
388
511
  let compareFn = criteria;
389
512
  if (typeof criteria === 'string')
390
513
  compareFn = v => v.id === criteria;
391
- const lists = this.routes;
514
+ const lists = this.lists;
392
515
  let route = null;
393
516
  for (let i = 0; i < lists.length && !route; i++) {
394
517
  route = lists[i].routes.find(compareFn);
395
518
  }
396
519
  return route;
397
520
  }
521
+ getAllRoutes() {
522
+ const lists = this.lists;
523
+ const routes = [];
524
+ for (let i = 0; i < lists.length; i++) {
525
+ routes.push(...lists[i].routes);
526
+ }
527
+ return routes;
528
+ }
398
529
  stopAllRides() {
399
- const lists = this.routes;
530
+ const lists = this.lists;
400
531
  for (let i = 0; i < lists.length; i++) {
401
532
  lists[i].routes.forEach(r => r.startState = 'idle');
402
533
  }
403
534
  }
404
- addDescriptions(descriptions) {
535
+ addDescriptionsFromDB(descriptions) {
536
+ const ids = Object.keys(descriptions);
537
+ const routes = ids.map(id => descriptions[id]);
538
+ this.routeDescriptions = descriptions;
539
+ routes.forEach((data) => {
540
+ data.state = "prepared";
541
+ this.addRouteToList(data.list, { data, id: data.id });
542
+ });
543
+ }
544
+ addDescriptionsFromRepo(descriptions, repo) {
545
+ const routes = descriptions;
546
+ if (repo) {
547
+ const type = repo.getName() === 'videos' ? 'video' : 'gpx';
548
+ routes.forEach(r => {
549
+ r.type = type;
550
+ });
551
+ }
552
+ const converted = routes
553
+ .map(this.convertDescription.bind(this));
554
+ converted.forEach((data, idx) => {
555
+ const list = this.getListFromApiDesciption(descriptions[idx]);
556
+ const existing = this.getRouteDescription(list, data.id);
557
+ if (existing) {
558
+ this.mergeDescription(existing, data);
559
+ }
560
+ else {
561
+ data.state = 'prepared';
562
+ data.isLocal = true;
563
+ const item = { id: data.id, data };
564
+ this.addRouteToList(list, item);
565
+ this.routeDescriptions[data.id] = Object.assign(Object.assign({}, data), { list });
566
+ }
567
+ });
568
+ }
569
+ addDescriptionsFromServer(descriptions) {
405
570
  const converted = descriptions.map(this.convertDescription.bind(this));
406
571
  converted.forEach((data, idx) => {
572
+ const existing = this.getRoute(data.id);
573
+ if (existing) {
574
+ return;
575
+ }
407
576
  const list = this.getListFromApiDesciption(descriptions[idx]);
408
577
  if (!list) {
409
578
  this.logEvent({ message: 'Error', error: 'no list found for route', id: data.id, title: data.title });
@@ -415,8 +584,11 @@ class RouteListService extends service_1.IncyclistService {
415
584
  }
416
585
  else {
417
586
  data.state = 'prepared';
587
+ data.isLocal = false;
418
588
  const item = { id: data.id, data };
419
589
  this.addRouteToList(list, item);
590
+ this.routeDescriptions[data.id] = Object.assign(Object.assign({}, data), { list });
591
+ this.emitPageUpdate();
420
592
  }
421
593
  });
422
594
  }
@@ -457,39 +629,153 @@ class RouteListService extends service_1.IncyclistService {
457
629
  return title.substring(3);
458
630
  }
459
631
  }
460
- loadRouteDescriptions() {
632
+ loadRouteDescriptionsFromRepo() {
633
+ return __awaiter(this, void 0, void 0, function* () {
634
+ if (Object.keys(this.routeDescriptions).length > 0) {
635
+ this.emitPageUpdate();
636
+ return;
637
+ }
638
+ const all = yield this.getRoutesRepo().read('settings');
639
+ if (!all) {
640
+ const videos = ((yield this.getVideosRepo().read('routes')) || []);
641
+ this.addDescriptionsFromRepo(videos, this.getVideosRepo());
642
+ const routes = ((yield this.getRoutesRepo().read('routes')) || []);
643
+ this.addDescriptionsFromRepo(routes, this.getRoutesRepo());
644
+ }
645
+ else {
646
+ const ids = Object.keys(all);
647
+ ids.forEach(id => {
648
+ const r = all[id];
649
+ if (typeof r.points === 'string') {
650
+ this.decodePoints(r);
651
+ }
652
+ if (!Array.isArray(r.points))
653
+ delete r.points;
654
+ });
655
+ console.log('~~~ DEBUG:', all);
656
+ this.addDescriptionsFromDB(all);
657
+ }
658
+ this.saveRouteDescriptions();
659
+ });
660
+ }
661
+ saveRouteDescriptions() {
461
662
  return __awaiter(this, void 0, void 0, function* () {
462
- const enrich = (v, type) => (Object.assign({ type }, v));
463
- const promises = [
464
- this.api.getRouteDescriptions({ type: 'gpx' }).then(v => v.map(e => enrich(e, 'gpx'))),
465
- this.api.getRouteDescriptions({ type: 'video' }).then(v => v.map(e => enrich(e, 'video')))
663
+ const ids = Object.keys(this.routeDescriptions);
664
+ const data = ids.map(id => {
665
+ const r = this.routeDescriptions[id];
666
+ if (r.points)
667
+ this.encodePoints(r);
668
+ return Object.assign({}, r);
669
+ });
670
+ yield this.getRoutesRepo().write('settings', data);
671
+ });
672
+ }
673
+ loadRouteDescriptionsFromServer() {
674
+ return __awaiter(this, void 0, void 0, function* () {
675
+ const enrichApi = (v, type) => (Object.assign({ type }, v));
676
+ const api = [
677
+ this.api.getRouteDescriptions({ type: 'gpx' }).then(v => v.map(e => enrichApi(e, 'gpx'))),
678
+ this.api.getRouteDescriptions({ type: 'video' }).then(v => v.map(e => enrichApi(e, 'video')))
466
679
  ];
467
- const res = yield Promise.allSettled(promises);
680
+ const res = yield Promise.allSettled(api);
468
681
  res.forEach(p => {
469
682
  if (p.status === 'fulfilled') {
470
- this.addDescriptions(p.value);
683
+ this.addDescriptionsFromServer(p.value);
471
684
  }
472
685
  });
473
686
  });
474
687
  }
688
+ loadRouteDescriptions() {
689
+ return __awaiter(this, void 0, void 0, function* () {
690
+ console.log('~~~ DEBUG:loadRouteDescriptions', Object.keys(this.routeDescriptions));
691
+ yield this.loadRouteDescriptionsFromRepo();
692
+ this.emitPageUpdate();
693
+ if (this.isDescriptionsLoaded()) {
694
+ yield this.loadRouteDescriptionsFromServer();
695
+ this.emitPageUpdate();
696
+ this.saveRouteDescriptions();
697
+ }
698
+ else {
699
+ this.loadRouteDescriptionsFromServer().then(() => {
700
+ this.emitPageUpdate();
701
+ });
702
+ }
703
+ });
704
+ }
475
705
  loadRouteDetais(route) {
476
706
  return __awaiter(this, void 0, void 0, function* () {
477
- if (route.data.state === 'loaded')
707
+ if (route.data.state === 'loaded' || route.data.state === 'loading')
478
708
  return;
479
709
  try {
480
710
  route.data.state = 'loading';
481
- route.details = yield this.api.getRouteDetails(route.id);
711
+ this.emitRouteUpdated(route);
712
+ try {
713
+ route.details = route.data.hasVideo ?
714
+ yield this.getVideosRepo().read(route.id)
715
+ :
716
+ yield this.getRoutesRepo().read(route.id);
717
+ api_1.default.verify(route.details);
718
+ }
719
+ catch (err) {
720
+ console.log('~~~ DEBUG:error', route.data.title, route.data.isLocal, err);
721
+ if (route.data.isLocal)
722
+ throw err;
723
+ }
724
+ if (!route.details) {
725
+ route.details = yield this.api.getRouteDetails(route.id);
726
+ api_1.default.verify(route.details);
727
+ this.saveRouteDetails(route);
728
+ }
729
+ if (route.details.id !== route.data.id) {
730
+ route.data.state = 'error';
731
+ delete route.details;
732
+ return;
733
+ }
482
734
  this.mergeWithDetails(route);
483
- route.data.state = 'loading';
735
+ route.data.state = 'loaded';
484
736
  this.emitRouteUpdated(route);
737
+ const { list } = this.routeDescriptions[route.id];
738
+ this.routeDescriptions[route.id] = Object.assign(Object.assign({}, route.data), { list });
739
+ return route.data;
485
740
  }
486
741
  catch (e) {
487
742
  route.data.state = 'error';
743
+ this.emitRouteUpdated(route);
744
+ }
745
+ });
746
+ }
747
+ saveRouteDetails(route) {
748
+ return __awaiter(this, void 0, void 0, function* () {
749
+ try {
750
+ route.details = route.data.hasVideo ?
751
+ yield this.videosRepo.write(route.id, route.details)
752
+ :
753
+ yield this.routesRepo.write(route.id, route.details);
754
+ }
755
+ catch (err) {
756
+ if (route.data.isLocal)
757
+ throw err;
488
758
  }
489
759
  });
490
760
  }
491
761
  emitRouteUpdated(route) {
492
762
  this.emit('route-update', route.id, route.data);
763
+ this.state.pages.forEach(page => {
764
+ try {
765
+ const { onRouteStateChanged } = page.onRouteUpdate[route.id] || {};
766
+ const { language } = page;
767
+ if (onRouteStateChanged) {
768
+ const data = (0, localization_1.getLocalizedData)(route.data, language);
769
+ onRouteStateChanged(data);
770
+ }
771
+ }
772
+ catch (err) {
773
+ console.log('~~~ DEBUG:Error', err);
774
+ }
775
+ });
776
+ }
777
+ getListState(page, list) {
778
+ return page === null || page === void 0 ? void 0 : page.state.lists.find(pl => pl.list === list);
493
779
  }
494
780
  getRouteFromStartProps(r) {
495
781
  try {
@@ -507,6 +793,15 @@ class RouteListService extends service_1.IncyclistService {
507
793
  return;
508
794
  }
509
795
  }
796
+ isDescriptionsLoaded() {
797
+ return Object.keys(this.routeDescriptions).length === 0;
798
+ }
799
+ decodePoints(r) {
800
+ return;
801
+ }
802
+ encodePoints(r) {
803
+ return;
804
+ }
510
805
  }
511
806
  exports.RouteListService = RouteListService;
512
807
  const useRouteList = () => RouteListService.getInstance();
@@ -12,10 +12,14 @@ export type RouteListStartProps = {
12
12
  export type LocalizedText = {
13
13
  [index: string]: string;
14
14
  };
15
- export type RouteInfo = {
15
+ export type CardType = 'route' | 'free-ride' | 'import';
16
+ export interface CardInfo {
17
+ type: CardType;
16
18
  state: RouteState;
17
- id?: string;
18
19
  title?: string;
20
+ }
21
+ export interface RouteInfo extends CardInfo {
22
+ id?: string;
19
23
  localizedTitle?: LocalizedText;
20
24
  country?: string;
21
25
  isLoop?: boolean;
@@ -23,6 +27,7 @@ export type RouteInfo = {
23
27
  elevation?: number;
24
28
  category?: RouteCategory;
25
29
  provider?: RouteProvider;
30
+ isLocal?: boolean;
26
31
  hasGpx?: boolean;
27
32
  hasVideo?: boolean;
28
33
  isDemo?: boolean;
@@ -30,14 +35,34 @@ export type RouteInfo = {
30
35
  videoFormat?: string;
31
36
  videoUrl?: string;
32
37
  previewUrl?: string;
33
- points?: Array<RoutePoint>;
38
+ points?: Array<RoutePoint> | string;
34
39
  segments?: Array<RouteSegment>;
40
+ }
41
+ export interface RouteDBEntry extends RouteInfo {
42
+ list: List;
43
+ settings?: {
44
+ position?: number;
45
+ segment?: string;
46
+ realityFactor?: number;
47
+ };
48
+ cntStarts?: number;
49
+ lastStart?: number;
50
+ }
51
+ export type RoutesDB = {
52
+ [index: string]: RouteDBEntry;
35
53
  };
36
54
  export type RouteListDateEntry = {
37
55
  list: List;
38
56
  listHeader: string;
39
57
  routes: Array<RouteInfo>;
58
+ startIdx?: number;
59
+ endIdx?: number;
40
60
  };
61
+ export type onRouteStatusUpdateCallback = (route: RouteInfo) => void;
62
+ export type onCarouselStateChangedCallback = (state: {
63
+ initialized: boolean;
64
+ visible: boolean;
65
+ }) => void;
41
66
  export type RouteListData = {
42
67
  pageId: string;
43
68
  lists: Array<RouteListDateEntry>;
@@ -59,6 +84,18 @@ export type Page = {
59
84
  settingsState?: RouteSettingsState;
60
85
  language: string;
61
86
  onStatusUpdate: RouteListStatusUpdateCallback;
87
+ onRouteUpdate: {
88
+ [index: string]: {
89
+ idx: number;
90
+ onRouteStateChanged: onRouteStatusUpdateCallback;
91
+ };
92
+ };
93
+ onCarouselStateChanged: {
94
+ [index: string]: {
95
+ idx: number;
96
+ onCarouselStateChanged: onCarouselStateChangedCallback;
97
+ };
98
+ };
62
99
  };
63
100
  export interface LoadingState {
64
101
  promise: any;
@@ -67,17 +104,20 @@ export interface InternalRouteListState {
67
104
  initialized: boolean;
68
105
  pages: Array<Page>;
69
106
  loading?: LoadingState;
107
+ preloadDone: boolean;
70
108
  }
71
109
  export type RouteStartState = 'idle' | 'preparing' | 'selected' | 'started';
72
- export type Route = {
110
+ export type Card<T> = {
73
111
  id: string;
74
- data: RouteInfo;
112
+ data: T;
75
113
  details?: RouteApiDetail;
76
114
  startSettings?: RouteStartSettings;
77
115
  startState?: RouteStartState;
78
116
  };
79
- export type RouteListEntry = {
117
+ export type CardListEntry<T> = {
80
118
  list: List;
81
- routes: Array<Route>;
119
+ routes: Array<Card<T>>;
82
120
  };
121
+ export type Route = Card<RouteInfo>;
122
+ export type RouteListEntry = CardListEntry<RouteInfo>;
83
123
  export type List = 'myRoutes' | 'alternatives' | 'selected';
package/lib/utils/geo.js CHANGED
@@ -47,9 +47,11 @@ const getPointAfter = (p1, p2, offset) => {
47
47
  };
48
48
  exports.getPointAfter = getPointAfter;
49
49
  const getLatLng = (position) => {
50
- if (Array.isArray(position))
50
+ if (Array.isArray(position)) {
51
51
  return { lat: position[0], lng: position[1] };
52
- if (position.lat !== undefined && position.lng !== undefined)
52
+ }
53
+ if (position.lat !== undefined && position.lng !== undefined) {
53
54
  return position;
55
+ }
54
56
  };
55
57
  exports.getLatLng = getLatLng;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.0.56",
3
+ "version": "1.0.58",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.26"
6
6
  },
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "axios": "^1.6.1",
42
- "incyclist-devices": "^2.1.23",
42
+ "incyclist-devices": "^2.1.24",
43
43
  "uuid": "^9.0.0"
44
44
  }
45
45
  }