incyclist-services 1.0.55 → 1.0.57

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;
@@ -52,10 +52,12 @@ export interface InternalPairingState extends PairingState {
52
52
  scanTo?: NodeJS.Timeout;
53
53
  tsPrevStart?: number;
54
54
  check?: {
55
- promise: Promise<boolean>;
55
+ preparing?: number;
56
+ promise?: Promise<boolean>;
56
57
  };
57
58
  scan?: {
58
- promise: Promise<DeviceSettings[]>;
59
+ preparing?: number;
60
+ promise?: Promise<DeviceSettings[]>;
59
61
  adapters?: Array<{
60
62
  udid: string;
61
63
  adapter: IncyclistDeviceAdapter;
@@ -12,6 +12,8 @@ export interface Services {
12
12
  }
13
13
  export declare class DevicePairingService extends IncyclistService {
14
14
  protected static _instance: DevicePairingService;
15
+ protected static checkCounter: number;
16
+ protected static scanCounter: number;
15
17
  protected configuration: DeviceConfigurationService;
16
18
  protected access: DeviceAccessService;
17
19
  protected rideService: DeviceRideService;
@@ -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
  });
@@ -431,7 +432,6 @@ class DevicePairingService extends service_2.IncyclistService {
431
432
  const current = getInterfaceSettings(ifName, prev);
432
433
  const changed = (current.state !== ifDetails.state || current.isScanning !== ifDetails.isScanning || current.enabled !== ifDetails.enabled);
433
434
  if (!changed) {
434
- console.log('~~~ DEUBUG: if change, no change', ifName, ifDetails);
435
435
  return;
436
436
  }
437
437
  this.logEvent({ message: 'interface state changed', interface: ifName, state: ifDetails.state });
@@ -765,6 +765,10 @@ class DevicePairingService extends service_2.IncyclistService {
765
765
  }
766
766
  startPairing(adapters, props) {
767
767
  return __awaiter(this, void 0, void 0, function* () {
768
+ if (this.isPairing())
769
+ return;
770
+ const preparing = DevicePairingService.checkCounter++;
771
+ this.state.check = { preparing };
768
772
  this.emit('pairing-start');
769
773
  const requiredInterfaces = this.getPairingInterfaces();
770
774
  const busyRequired = this.state.interfaces
@@ -773,8 +777,10 @@ class DevicePairingService extends service_2.IncyclistService {
773
777
  const isReady = busyRequired === undefined;
774
778
  if (!isReady) {
775
779
  setTimeout(() => {
776
- if (!this.isPairing() && !this.isScanning())
780
+ if ((!this.isPairing() || this.state.check.preparing === preparing) && !this.isScanning()) {
781
+ delete this.state.check;
777
782
  this.run();
783
+ }
778
784
  }, 1000);
779
785
  return;
780
786
  }
@@ -782,6 +788,9 @@ class DevicePairingService extends service_2.IncyclistService {
782
788
  this.processConnectedDevices(adapters);
783
789
  const selected = this.state.capabilities.map(c => c.selected);
784
790
  const target = adapters.filter(ai => !ai.adapter.isStarted() && selected.includes(ai.udid));
791
+ if (this.isPairing() && this.state.check.preparing !== preparing) {
792
+ return;
793
+ }
785
794
  this.initPairingCallbacks();
786
795
  const selectedAdapters = target.map(ai => (Object.assign(Object.assign({}, ai), { isStarted: false })));
787
796
  const promise = this.rideService.startAdapters(selectedAdapters, 'pair');
@@ -819,18 +828,26 @@ class DevicePairingService extends service_2.IncyclistService {
819
828
  return __awaiter(this, void 0, void 0, function* () {
820
829
  const interfaces = this.state.interfaces.filter(i => this.isInterfaceEnabled(i) && i.state === 'connected')
821
830
  .map(i => i.name);
831
+ if (this.isScanning())
832
+ return;
833
+ const preparing = DevicePairingService.scanCounter++;
834
+ this.state.scan = { preparing };
822
835
  if (interfaces.length === 0) {
823
836
  if (this.state.scanTo)
824
837
  return;
825
838
  this.state.scanTo = setTimeout(() => {
826
839
  delete this.state.scanTo;
827
- if (!this.isPairing() && !this.isScanning())
840
+ if (!this.isPairing() && (!this.isScanning() || this.state.scan.preparing === preparing)) {
841
+ delete this.state.scan;
828
842
  this.run();
843
+ }
829
844
  }, 1000);
830
845
  return;
831
846
  }
832
847
  this.emit('scanning-start');
833
848
  this.state.tsPrevStart = Date.now();
849
+ if (this.isScanning() && this.state.scan.preparing !== preparing)
850
+ return;
834
851
  this.logEvent({ message: 'Start Scanning', interfaces: interfaces.join(','), props });
835
852
  this.initScanningCallbacks();
836
853
  const timeout = props.enforcedScan ? 1000 * 60 * 60 : undefined;
@@ -1139,5 +1156,7 @@ class DevicePairingService extends service_2.IncyclistService {
1139
1156
  }
1140
1157
  }
1141
1158
  exports.DevicePairingService = DevicePairingService;
1159
+ DevicePairingService.checkCounter = 0;
1160
+ DevicePairingService.scanCounter = 0;
1142
1161
  const useDevicePairing = () => DevicePairingService.getInstance();
1143
1162
  exports.useDevicePairing = useDevicePairing;
@@ -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;
@@ -1,20 +1,25 @@
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 } 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
12
  protected routes: 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
+ constructor(binding?: IJsonRepositoryBinding);
17
+ setBinding(binding: IJsonRepositoryBinding): void;
18
+ protected getVideosRepo(): JsonRepository;
19
+ protected getRoutesRepo(): JsonRepository;
16
20
  preload(carouselSize?: number): Promise<void>;
17
21
  protected _preload(carouselSize?: number, detailsOnly?: boolean): Promise<void>;
22
+ protected preloadList(list: List, carouselSize?: number, detailsOnly?: boolean): Promise<void>;
18
23
  load(props: {
19
24
  list?: List;
20
25
  loadFrom?: number;
@@ -22,9 +27,12 @@ export declare class RouteListService extends IncyclistService {
22
27
  routeId?: string;
23
28
  }): Promise<void>;
24
29
  openRouteSelection(pageId: string, props: RouteListStartProps): RouteListData;
25
- closeRouteSelection(listId: string): void;
30
+ closeRouteSelection(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;
@@ -54,13 +62,18 @@ export declare class RouteListService extends IncyclistService {
54
62
  protected getRouteDescription(list: List, id: string): RouteInfo;
55
63
  protected mergeDescription(local: RouteInfo, fromApi: RouteInfo): void;
56
64
  protected mergeWithDetails(route: Route): void;
57
- protected convertDescription(descr: RouteApiDescription): RouteInfo;
65
+ protected convertDescription(descr: RouteApiDescription, isLocal?: boolean): RouteInfo;
58
66
  protected getListFromApiDesciption(description: RouteApiDescription): "selected" | "myRoutes" | "alternatives";
59
67
  protected addRouteToList(list: List, route: Route): void;
60
68
  protected getRouteList(list: List): Array<Route>;
69
+ protected getRouteLists(): Array<{
70
+ list: List;
71
+ title: string;
72
+ }>;
61
73
  protected getRoute(criteria: string | filterFn): Route;
62
74
  protected stopAllRides(): void;
63
- protected addDescriptions(descriptions: Array<RouteApiDescription>): void;
75
+ protected addDescriptionsFromRepo(descriptions: Array<RouteApiDescription>, repo?: JsonRepository): void;
76
+ protected addDescriptionsFromServer(descriptions: Array<RouteApiDescription>): void;
64
77
  protected updateRouteTitle(data: RouteInfo, route: {
65
78
  descr?: RouteApiDescription;
66
79
  }): void;
@@ -69,9 +82,12 @@ export declare class RouteListService extends IncyclistService {
69
82
  }): void;
70
83
  protected getCountryPrefix(title?: string): string | undefined;
71
84
  protected removeCountryPrefix(title?: string): string;
85
+ protected loadRouteDescriptionsFromRepo(): Promise<void>;
86
+ protected loadRouteDescriptionsFromServer(): Promise<void>;
72
87
  protected loadRouteDescriptions(): Promise<void>;
73
88
  protected loadRouteDetais(route: Route): Promise<void>;
74
89
  protected emitRouteUpdated(route: Route): void;
90
+ protected getListState(page: Page, list: List): import("./types").RouteListDateEntry;
75
91
  private getRouteFromStartProps;
76
92
  }
77
93
  export declare const useRouteList: () => RouteListService;
@@ -18,6 +18,8 @@ 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"));
21
23
  class RouteListService extends service_1.IncyclistService {
22
24
  static getInstance(binding) {
23
25
  if (!RouteListService._instance)
@@ -30,20 +32,33 @@ class RouteListService extends service_1.IncyclistService {
30
32
  initialized: false,
31
33
  pages: []
32
34
  };
33
- this.setBinding(binding);
35
+ if (binding)
36
+ api_2.JsonRepository.setBinding(binding);
34
37
  this.api = api_1.default.getInstance();
35
38
  this.initRouteLists();
36
39
  }
37
40
  setBinding(binding) {
38
41
  if (!binding)
39
42
  return;
40
- this.binding = binding;
43
+ api_2.JsonRepository.setBinding(binding);
41
44
  this.state.initialized = true;
42
45
  }
46
+ getVideosRepo() {
47
+ if (this.videosRepo)
48
+ return this.videosRepo;
49
+ this.videosRepo = api_2.JsonRepository.create('videos');
50
+ return this.videosRepo;
51
+ }
52
+ getRoutesRepo() {
53
+ if (this.routesRepo)
54
+ return this.routesRepo;
55
+ this.routesRepo = api_2.JsonRepository.create('routes');
56
+ return this.routesRepo;
57
+ }
43
58
  preload(carouselSize = 5) {
44
59
  return __awaiter(this, void 0, void 0, function* () {
45
60
  if (this.state.loading)
46
- yield this.state.loading.promise;
61
+ return;
47
62
  const promise = this._preload(carouselSize);
48
63
  this.state.loading = { promise };
49
64
  yield promise;
@@ -52,17 +67,35 @@ class RouteListService extends service_1.IncyclistService {
52
67
  }
53
68
  _preload(carouselSize = 5, detailsOnly = false) {
54
69
  return __awaiter(this, void 0, void 0, function* () {
55
- this.logEvent({ message: 'preloading routes --' });
56
70
  if (!detailsOnly) {
57
71
  yield this.loadRouteDescriptions();
58
72
  }
73
+ this.emitPageUpdate();
59
74
  const promises = [];
75
+ const promisesApi = [];
76
+ const lists = this.getRouteLists().map(li => li.list);
77
+ lists.forEach((list) => {
78
+ this.preloadList(list, carouselSize, detailsOnly);
79
+ });
80
+ this.logEvent({ message: 'preloading routes finished' });
81
+ });
82
+ }
83
+ preloadList(list, carouselSize = 5, detailsOnly = false) {
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ const promises = [];
86
+ const promisesApi = [];
87
+ this.emitPageUpdate();
60
88
  this.routes.forEach(rle => {
61
89
  var _a;
62
90
  if (((_a = rle.routes) === null || _a === void 0 ? void 0 : _a.length) > 0) {
63
91
  this.sort(rle.routes);
64
- rle.routes.forEach((r, idx) => {
65
- if (idx < Math.min(carouselSize, rle.routes.length)) {
92
+ let cntApi = 0;
93
+ rle.routes.forEach((r) => {
94
+ if (!r.data.isLocal) {
95
+ cntApi++;
96
+ return;
97
+ }
98
+ if (cntApi <= Math.min(carouselSize, rle.routes.length)) {
66
99
  const loader = this.loadRouteDetais(r);
67
100
  promises.push(loader);
68
101
  }
@@ -97,20 +130,23 @@ class RouteListService extends service_1.IncyclistService {
97
130
  }
98
131
  openRouteSelection(pageId, props) {
99
132
  const { language, visibleCards, visibleLists } = props;
100
- this.logEvent({ message: 'start', pageId, language, visibleCards, visibleLists });
133
+ this.logEvent({ message: 'openRouteSelection', pageId, language, visibleCards, visibleLists });
101
134
  const existing = this.getPage(pageId);
102
135
  if (existing) {
103
136
  this.closeRouteSelection(pageId);
104
137
  }
105
138
  this.stopAllRides();
139
+ const page = this.registerPage(pageId, props);
106
140
  if (props.visibleCards) {
107
- this._preload(props.visibleCards, true);
141
+ this.preload(props.visibleCards);
142
+ }
143
+ else {
144
+ this.preload(5);
108
145
  }
109
- const page = this.registerPage(pageId, props);
110
146
  return page.state;
111
147
  }
112
- closeRouteSelection(listId) {
113
- const idx = this.getPage(listId, true);
148
+ closeRouteSelection(pageId) {
149
+ const idx = this.getPage(pageId, true);
114
150
  if (idx !== -1) {
115
151
  this.state.pages.splice(idx, 1);
116
152
  }
@@ -146,6 +182,60 @@ class RouteListService extends service_1.IncyclistService {
146
182
  return {};
147
183
  }
148
184
  }
185
+ setCardUpdateHandler(pageId, r, list, idx, onRouteStateChanged, onCarouselStateChanged) {
186
+ const route = this.getRouteFromStartProps(r);
187
+ const page = this.getPage(pageId);
188
+ if (!page)
189
+ return;
190
+ page.onRouteUpdate[route.id] = { idx, onRouteStateChanged };
191
+ page.onCarouselStateChanged[route.id] = { idx, onCarouselStateChanged };
192
+ }
193
+ onCarouselInitialized(pageId, list, startIdx, visibleSize) {
194
+ const page = this.getPage(pageId);
195
+ console.log('~~~ DEBUG:onCarouselInitialied', pageId, list, this.getListState(page, list), startIdx, visibleSize);
196
+ if (!page)
197
+ return;
198
+ const listState = this.getListState(page, list);
199
+ if (listState) {
200
+ listState.startIdx = startIdx;
201
+ listState.endIdx = startIdx + visibleSize - 1;
202
+ }
203
+ const routes = this.getRouteList(list);
204
+ routes.forEach((route) => {
205
+ const { idx, onCarouselStateChanged } = page.onCarouselStateChanged[route.id] || {};
206
+ const visible = listState !== undefined ? idx >= listState.startIdx && idx <= listState.endIdx : undefined;
207
+ if (route.data.title === 'Malaga City Tour')
208
+ console.log('~~~ DEBUG:Initialized -checked detail', visible, idx, listState.startIdx, listState.endIdx, route.data);
209
+ if (onCarouselStateChanged)
210
+ onCarouselStateChanged({ initialized: true, visible });
211
+ if (visible && (route.data.state === 'prepared' || route.data.state === 'error')) {
212
+ this.loadRouteDetais(route);
213
+ }
214
+ });
215
+ }
216
+ onCarouselUpdated(pageId, list, startIdx, visibleSize) {
217
+ const page = this.getPage(pageId);
218
+ console.log('~~~ DEBUG:onCarouselUpdated', pageId, list, this.getListState(page, list), startIdx, visibleSize);
219
+ if (!page)
220
+ return;
221
+ const listState = this.getListState(page, list);
222
+ if (listState) {
223
+ listState.startIdx = startIdx;
224
+ listState.endIdx = startIdx + visibleSize - 1;
225
+ }
226
+ const routes = this.getRouteList(list);
227
+ routes.forEach((route) => {
228
+ const { idx, onCarouselStateChanged } = page.onCarouselStateChanged[route.id] || {};
229
+ const visible = listState !== undefined ? idx >= listState.startIdx && idx <= listState.endIdx : undefined;
230
+ if (route.data.title === 'Malaga City Tour')
231
+ console.log('~~~ DEBUG:Initialized -checked detail', visible, idx, listState.startIdx, listState.endIdx, route.data);
232
+ if (onCarouselStateChanged)
233
+ onCarouselStateChanged({ initialized: true, visible });
234
+ if (visible && route.data.state === 'prepared' || route.data.state === 'error') {
235
+ this.loadRouteDetais(route);
236
+ }
237
+ });
238
+ }
149
239
  acceptStartSettings(r, startSettings) {
150
240
  var _a;
151
241
  const route = this.getRouteFromStartProps(r);
@@ -240,8 +330,8 @@ class RouteListService extends service_1.IncyclistService {
240
330
  }
241
331
  emitPageUpdate() {
242
332
  this.state.pages.forEach(p => {
243
- const state = this.getPageState(p.id);
244
- p.onStatusUpdate(state);
333
+ p.state = this.getPageState(p.id);
334
+ p.onStatusUpdate(p.state);
245
335
  });
246
336
  }
247
337
  initRouteLists() {
@@ -269,7 +359,7 @@ class RouteListService extends service_1.IncyclistService {
269
359
  registerPage(pageId, props) {
270
360
  const { onStatusUpdate, language = 'en' } = props;
271
361
  const state = this.getPageState(pageId, language);
272
- const page = { id: pageId, onStatusUpdate, language, state };
362
+ const page = { id: pageId, onStatusUpdate, onRouteUpdate: {}, onCarouselStateChanged: {}, language, state };
273
363
  this.state.pages.push(page);
274
364
  return page;
275
365
  }
@@ -342,11 +432,12 @@ class RouteListService extends service_1.IncyclistService {
342
432
  }
343
433
  route.data = local;
344
434
  }
345
- convertDescription(descr) {
435
+ convertDescription(descr, isLocal) {
346
436
  const { id, title, localizedTitle, country, distance, elevation, category, provider, video, points } = descr;
347
437
  const data = { state: 'prepared', id, title, localizedTitle, country, distance, elevation, provider, category };
348
438
  data.hasVideo = false;
349
439
  data.hasGpx = false;
440
+ data.isLocal = isLocal || false;
350
441
  this.updateRouteCountry(data, { descr });
351
442
  this.updateRouteTitle(data, { descr });
352
443
  if (points)
@@ -366,10 +457,15 @@ class RouteListService extends service_1.IncyclistService {
366
457
  var _a;
367
458
  let list;
368
459
  if (description.type === 'gpx') {
369
- list = description.private ? 'myRoutes' : 'selected';
460
+ if (description.category === 'personal')
461
+ return 'myRoutes';
462
+ list = description.category ? 'alternatives' : 'selected';
463
+ console.log('~~~ DEBUG', description.title, description.private, list);
370
464
  return list;
371
465
  }
372
466
  if (description.type === 'video') {
467
+ if (description.category === undefined || description.category === 'personal')
468
+ return 'myRoutes';
373
469
  if ((_a = description.video) === null || _a === void 0 ? void 0 : _a.url) {
374
470
  return 'selected';
375
471
  }
@@ -384,6 +480,14 @@ class RouteListService extends service_1.IncyclistService {
384
480
  var _a;
385
481
  return (_a = this.routes.find(rle => rle.list === list)) === null || _a === void 0 ? void 0 : _a.routes;
386
482
  }
483
+ getRouteLists() {
484
+ const lists = [
485
+ { list: 'myRoutes', title: 'My Routes' },
486
+ { list: 'selected', title: 'Selected For Me' },
487
+ { list: 'alternatives', title: 'Alternatives' }
488
+ ];
489
+ return lists;
490
+ }
387
491
  getRoute(criteria) {
388
492
  let compareFn = criteria;
389
493
  if (typeof criteria === 'string')
@@ -401,9 +505,38 @@ class RouteListService extends service_1.IncyclistService {
401
505
  lists[i].routes.forEach(r => r.startState = 'idle');
402
506
  }
403
507
  }
404
- addDescriptions(descriptions) {
508
+ addDescriptionsFromRepo(descriptions, repo) {
509
+ const routes = descriptions;
510
+ if (repo) {
511
+ const type = repo.getName() === 'videos' ? 'video' : 'gpx';
512
+ routes.forEach(r => {
513
+ r.type = type;
514
+ });
515
+ }
516
+ const converted = routes
517
+ .map(this.convertDescription.bind(this));
518
+ converted.forEach((data, idx) => {
519
+ const list = this.getListFromApiDesciption(descriptions[idx]);
520
+ const existing = this.getRouteDescription(list, data.id);
521
+ if (existing) {
522
+ this.mergeDescription(existing, data);
523
+ }
524
+ else {
525
+ data.state = 'prepared';
526
+ data.isLocal = true;
527
+ const item = { id: data.id, data };
528
+ this.addRouteToList(list, item);
529
+ }
530
+ });
531
+ }
532
+ addDescriptionsFromServer(descriptions) {
405
533
  const converted = descriptions.map(this.convertDescription.bind(this));
406
534
  converted.forEach((data, idx) => {
535
+ console.log('~~~ DEBUG:add route from Server', data.title);
536
+ const existing = this.getRoute(data.id);
537
+ if (existing) {
538
+ return;
539
+ }
407
540
  const list = this.getListFromApiDesciption(descriptions[idx]);
408
541
  if (!list) {
409
542
  this.logEvent({ message: 'Error', error: 'no list found for route', id: data.id, title: data.title });
@@ -415,8 +548,10 @@ class RouteListService extends service_1.IncyclistService {
415
548
  }
416
549
  else {
417
550
  data.state = 'prepared';
551
+ data.isLocal = false;
418
552
  const item = { id: data.id, data };
419
553
  this.addRouteToList(list, item);
554
+ this.emitPageUpdate();
420
555
  }
421
556
  });
422
557
  }
@@ -457,39 +592,104 @@ class RouteListService extends service_1.IncyclistService {
457
592
  return title.substring(3);
458
593
  }
459
594
  }
460
- loadRouteDescriptions() {
595
+ loadRouteDescriptionsFromRepo() {
596
+ return __awaiter(this, void 0, void 0, function* () {
597
+ console.log('~~~ DEBUG:loadRouteDescriptionsFromRepo');
598
+ const enrichRepo = (v, type) => {
599
+ const data = v;
600
+ return Object.assign({ type }, data);
601
+ };
602
+ const videos = ((yield this.getVideosRepo().read('routes')) || []);
603
+ console.log('~~~ DEBUG,videos:', videos);
604
+ this.addDescriptionsFromRepo(videos, this.getVideosRepo());
605
+ const routes = ((yield this.getRoutesRepo().read('routes')) || []);
606
+ console.log('~~~ DEBUG,routes:', routes);
607
+ this.addDescriptionsFromRepo(routes, this.getVideosRepo());
608
+ });
609
+ }
610
+ loadRouteDescriptionsFromServer() {
461
611
  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')))
612
+ console.log('~~~ DEBUG:loadRouteDescriptionsFromServer');
613
+ const enrichApi = (v, type) => (Object.assign({ type }, v));
614
+ const api = [
615
+ this.api.getRouteDescriptions({ type: 'gpx' }).then(v => v.map(e => enrichApi(e, 'gpx'))),
616
+ this.api.getRouteDescriptions({ type: 'video' }).then(v => v.map(e => enrichApi(e, 'video')))
466
617
  ];
467
- const res = yield Promise.allSettled(promises);
618
+ const res = yield Promise.allSettled(api);
619
+ console.log('~~~ DEBUG:loadRouteDescriptionsFromServer done', (new Date()).toISOString(), res);
620
+ this.emitPageUpdate();
468
621
  res.forEach(p => {
469
622
  if (p.status === 'fulfilled') {
470
- this.addDescriptions(p.value);
623
+ this.addDescriptionsFromServer(p.value);
471
624
  }
472
625
  });
473
626
  });
474
627
  }
628
+ loadRouteDescriptions() {
629
+ return __awaiter(this, void 0, void 0, function* () {
630
+ yield this.loadRouteDescriptionsFromRepo();
631
+ console.log('~~~ DEBUG, routes after Repo', this.routes);
632
+ this.emitPageUpdate();
633
+ this.loadRouteDescriptionsFromServer().then(() => {
634
+ console.log('~~~ DEBUG, routes after API', this.routes);
635
+ });
636
+ });
637
+ }
475
638
  loadRouteDetais(route) {
476
639
  return __awaiter(this, void 0, void 0, function* () {
640
+ console.log('~~~ DEBUG:loadDetails()', (0, clone_1.default)(route.data));
477
641
  if (route.data.state === 'loaded')
478
642
  return;
479
643
  try {
480
644
  route.data.state = 'loading';
481
- route.details = yield this.api.getRouteDetails(route.id);
645
+ this.emitRouteUpdated(route);
646
+ if (route.data.isLocal) {
647
+ route.details = route.data.hasVideo ?
648
+ yield this.videosRepo.read(route.id)
649
+ :
650
+ yield this.routesRepo.read(route.id);
651
+ api_1.default.verify(route.details);
652
+ }
653
+ else {
654
+ route.details = yield this.api.getRouteDetails(route.id);
655
+ }
656
+ if (route.data.title === 'Arnbach')
657
+ console.log('~~~ DEBUG: loaded details', route.details);
658
+ if (route.details.id !== route.data.id) {
659
+ route.data.state = 'error';
660
+ delete route.details;
661
+ return;
662
+ }
482
663
  this.mergeWithDetails(route);
483
- route.data.state = 'loading';
664
+ route.data.state = 'loaded';
484
665
  this.emitRouteUpdated(route);
485
666
  }
486
667
  catch (e) {
668
+ console.log('~~~ DEBUG:Error', e);
487
669
  route.data.state = 'error';
670
+ this.emitRouteUpdated(route);
488
671
  }
489
672
  });
490
673
  }
491
674
  emitRouteUpdated(route) {
492
675
  this.emit('route-update', route.id, route.data);
676
+ this.state.pages.forEach(page => {
677
+ try {
678
+ const { onRouteStateChanged } = page.onRouteUpdate[route.id] || {};
679
+ const { language } = page;
680
+ if (onRouteStateChanged) {
681
+ const data = (0, localization_1.getLocalizedData)(route.data, language);
682
+ onRouteStateChanged(data);
683
+ }
684
+ }
685
+ catch (err) {
686
+ console.log('~~~ DEBUG:Error', err);
687
+ }
688
+ });
689
+ }
690
+ getListState(page, list) {
691
+ console.log('~~~ DEBUG.getListState', list, page === null || page === void 0 ? void 0 : page.state, this.state);
692
+ return page === null || page === void 0 ? void 0 : page.state.lists.find(pl => pl.list === list);
493
693
  }
494
694
  getRouteFromStartProps(r) {
495
695
  try {
@@ -23,6 +23,7 @@ export type RouteInfo = {
23
23
  elevation?: number;
24
24
  category?: RouteCategory;
25
25
  provider?: RouteProvider;
26
+ isLocal?: boolean;
26
27
  hasGpx?: boolean;
27
28
  hasVideo?: boolean;
28
29
  isDemo?: boolean;
@@ -37,7 +38,14 @@ export type RouteListDateEntry = {
37
38
  list: List;
38
39
  listHeader: string;
39
40
  routes: Array<RouteInfo>;
41
+ startIdx?: number;
42
+ endIdx?: number;
40
43
  };
44
+ export type onRouteStatusUpdateCallback = (route: RouteInfo) => void;
45
+ export type onCarouselStateChangedCallback = (state: {
46
+ initialized: boolean;
47
+ visible: boolean;
48
+ }) => void;
41
49
  export type RouteListData = {
42
50
  pageId: string;
43
51
  lists: Array<RouteListDateEntry>;
@@ -59,6 +67,18 @@ export type Page = {
59
67
  settingsState?: RouteSettingsState;
60
68
  language: string;
61
69
  onStatusUpdate: RouteListStatusUpdateCallback;
70
+ onRouteUpdate: {
71
+ [index: string]: {
72
+ idx: number;
73
+ onRouteStateChanged: onRouteStatusUpdateCallback;
74
+ };
75
+ };
76
+ onCarouselStateChanged: {
77
+ [index: string]: {
78
+ idx: number;
79
+ onCarouselStateChanged: onCarouselStateChangedCallback;
80
+ };
81
+ };
62
82
  };
63
83
  export interface LoadingState {
64
84
  promise: any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.0.55",
3
+ "version": "1.0.57",
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.22",
42
+ "incyclist-devices": "^2.1.24",
43
43
  "uuid": "^9.0.0"
44
44
  }
45
45
  }