incyclist-services 1.3.21 → 1.3.22

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.
@@ -28,7 +28,7 @@ class IncyclistRoutesApi {
28
28
  }
29
29
  getApi() {
30
30
  if (!this.api) {
31
- this.api = api_1.RestApiClient.getClient();
31
+ this.api = api_1.IncyclistRestApiClient.getClient();
32
32
  return this.api;
33
33
  }
34
34
  return this.api;
@@ -23,6 +23,8 @@ export interface RouteApiDescription extends RouteBase {
23
23
  type?: RouteType;
24
24
  localizedTitle?: LocalizedText;
25
25
  previewUrl?: string;
26
+ downloadUrl?: string;
27
+ requiresDownload?: boolean;
26
28
  isDeleted?: boolean;
27
29
  selectableSegments?: Array<RouteSegment>;
28
30
  }
@@ -55,6 +57,7 @@ export type RouteApiDetail = {
55
57
  distance?: number;
56
58
  elevation?: number;
57
59
  points?: Array<RoutePoint>;
60
+ requiresDownload?: boolean;
58
61
  downloadUrl?: string;
59
62
  downloadType?: string;
60
63
  gpxDisabled?: boolean;
@@ -1,5 +1,6 @@
1
1
  import { JSONObject } from '../../../utils';
2
2
  import { RouteApiDetail } from '../api/types';
3
+ import { RoutePoint } from '../types';
3
4
  import { XMLParser, XmlParserContext } from './xml';
4
5
  export declare class IncyclistXMLParser extends XMLParser {
5
6
  static SCHEME: string;
@@ -7,4 +8,5 @@ export declare class IncyclistXMLParser extends XMLParser {
7
8
  protected loadPoints(context: XmlParserContext): Promise<void>;
8
9
  protected processCuts(data: JSONObject, route: RouteApiDetail): void;
9
10
  protected parseVideo(context: XmlParserContext): Promise<void>;
11
+ protected processElevationShift(elevationShift: number, points: RoutePoint[]): void;
10
12
  }
@@ -120,6 +120,37 @@ class IncyclistXMLParser extends xml_1.XMLParser {
120
120
  if (data['cuts']) {
121
121
  this.processCuts(data, route);
122
122
  }
123
+ if (data['elevation-shift'])
124
+ this.processElevationShift(Number(data['elevation-shift']), route.points);
125
+ });
126
+ }
127
+ processElevationShift(elevationShift, points) {
128
+ if (!elevationShift || !points || points.length < 2 || points.length < elevationShift)
129
+ return;
130
+ const cuts = points.filter(p => p.isCut) || [];
131
+ const lastPoints = [];
132
+ cuts.forEach(cut => {
133
+ lastPoints.push(points[cut.cnt - 1]);
134
+ });
135
+ lastPoints.push(points[points.length - 1]);
136
+ let idx = 0;
137
+ let prev = undefined;
138
+ lastPoints.forEach(lp => {
139
+ const lastIdx = lp.cnt;
140
+ const lastSlope = lp.slope;
141
+ let lastElevation = lp.elevation;
142
+ while (idx + elevationShift <= lastIdx) {
143
+ points[idx].elevation = points[idx + elevationShift].elevation;
144
+ points[idx].slope = points[idx + elevationShift].slope;
145
+ points[idx].elevationGain = points[idx + elevationShift].elevationGain;
146
+ idx++;
147
+ }
148
+ while (idx <= lastIdx) {
149
+ points[idx].elevation = lastElevation + lastSlope * points[idx].distance / 100;
150
+ points[idx].elevationGain += (points[idx].elevation - lastElevation);
151
+ lastElevation = points[idx].elevation;
152
+ idx++;
153
+ }
123
154
  });
124
155
  }
125
156
  }
@@ -1,7 +1,7 @@
1
1
  import { FileInfo } from "../../../api";
2
2
  import { LatLng } from "../../../utils/geo";
3
3
  export type RouteType = 'gpx' | 'video';
4
- export type RouteCategory = 'Imported' | 'Free' | 'Demo' | 'personal';
4
+ export type RouteCategory = 'Imported' | 'Free' | 'Demo' | 'personal' | 'alternatives' | 'selected';
5
5
  export type RouteState = 'prepared' | 'loading' | 'loaded' | 'error';
6
6
  export type RouteProvider = {
7
7
  name: string;
@@ -102,6 +102,7 @@ export interface RouteInfo extends RouteBase {
102
102
  segments?: Array<RouteSegment>;
103
103
  tsImported?: number;
104
104
  tsLastStart?: number;
105
+ tsReleased?: number;
105
106
  next?: string;
106
107
  legacyId?: string;
107
108
  isDownloaded?: boolean;
@@ -104,8 +104,10 @@ const addDetails = (route, details) => {
104
104
  route.description.points = details.points;
105
105
  route.description.distance = (_a = details.distance) !== null && _a !== void 0 ? _a : points[points.length - 1].routeDistance;
106
106
  if (route.description.hasVideo) {
107
- route.description.requiresDownload = (0, valid_1.valid)(details.downloadUrl);
108
- route.description.downloadUrl = details.downloadUrl;
107
+ if (!(0, valid_1.valid)(route.description.requiresDownload))
108
+ route.description.requiresDownload = details.requiresDownload === undefined ? (0, valid_1.valid)(details.downloadUrl) : details.requiresDownload;
109
+ if (!(0, valid_1.valid)(route.description.downloadUrl))
110
+ route.description.downloadUrl = details.downloadUrl;
109
111
  if (!(0, valid_1.valid)(route.description.videoUrl)) {
110
112
  route.description.videoUrl = details.video.url;
111
113
  }
@@ -19,6 +19,7 @@ export interface SummaryCardDisplayProps extends RouteInfo {
19
19
  observer: Observer;
20
20
  initialized: boolean;
21
21
  loading?: boolean;
22
+ isNew?: boolean;
22
23
  }
23
24
  export interface DetailCardDisplayProps {
24
25
  }
@@ -24,6 +24,7 @@ const localization_1 = require("../../base/utils/localization");
24
24
  const gd_eventlog_1 = require("gd-eventlog");
25
25
  const route_1 = require("../../base/utils/route");
26
26
  const workouts_1 = require("../../../workouts");
27
+ const utils_2 = require("../utils");
27
28
  class ConvertObserver extends observer_1.Observer {
28
29
  constructor() {
29
30
  super();
@@ -161,8 +162,9 @@ class RouteCard extends base_1.BaseCard {
161
162
  if (points && !Array.isArray(points)) {
162
163
  points = undefined;
163
164
  }
165
+ let isNew = (0, utils_2.checkIsNew)(descr);
164
166
  const loading = this.deleteObserver !== undefined;
165
- return Object.assign(Object.assign({}, descr), { initialized: this.initialized, loaded: true, ready: true, state: 'loaded', visible: this.visible, canDelete: this.canDelete(), points, loading, title: this.getTitle(), observer: this.cardObserver });
167
+ return Object.assign(Object.assign({}, descr), { initialized: this.initialized, loaded: true, ready: true, state: 'loaded', visible: this.visible, isNew, canDelete: this.canDelete(), points, loading, title: this.getTitle(), observer: this.cardObserver });
166
168
  }
167
169
  catch (err) {
168
170
  this.logError(err, 'getDisplayProperties');
@@ -2,13 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AlternativeRoutes = void 0;
4
4
  const cardlist_1 = require("../../../base/cardlist");
5
+ const utils_1 = require("../utils");
5
6
  const score = (r) => {
6
7
  let val = 0;
7
8
  const tsAction = (Date.now() - Math.max(r.tsImported || 0, r.tsLastStart || 0)) / 1000 / 3600 / 24;
8
- if (r.hasVideo || (1 - tsAction) > 0)
9
+ const isNew = (0, utils_1.checkIsNew)(r);
10
+ if (r.hasVideo)
9
11
  val += 1;
10
- if (!r.isDemo)
12
+ if ((1 - tsAction) > 0)
11
13
  val += 1;
14
+ if (!r.isDemo)
15
+ val += 0.1;
16
+ if (isNew)
17
+ val++;
12
18
  return val;
13
19
  };
14
20
  const sortFn = (a, b) => {
@@ -2,12 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SelectedRoutes = void 0;
4
4
  const cardlist_1 = require("../../../base/cardlist");
5
+ const utils_1 = require("../utils");
5
6
  const score = (r, idx) => {
6
7
  let val = 0;
7
- const tsAction = (Date.now() - Math.max(r.tsImported || 0, r.tsLastStart || 0)) / 1000 / 3600 / 24;
8
+ const isNew = (0, utils_1.checkIsNew)(r);
9
+ const tsUserImport = (r.isLocal) ? r.tsImported || 0 : 0;
10
+ const tsAction = (Date.now() - Math.max(tsUserImport, r.tsLastStart || 0)) / 1000 / 3600 / 24;
8
11
  val += (1 - idx / 1000);
9
- if (r.hasVideo || (1 - tsAction) > 0)
10
- val += 1;
12
+ if (r.hasVideo)
13
+ val++;
14
+ if ((1 - tsAction) > 0)
15
+ val += 2.1;
16
+ if (isNew)
17
+ val++;
11
18
  return val;
12
19
  };
13
20
  const sortFn = (a, b) => {
@@ -15,6 +15,7 @@ export declare abstract class DBLoader<T> extends Loader<T> {
15
15
  load(): Observer;
16
16
  stopLoad(): void;
17
17
  protected _load(): Promise<void>;
18
+ protected abstract verifyImportDate(routes: Array<T>): any;
18
19
  protected getVideosRepo(): JsonRepository;
19
20
  protected getRoutesRepo(): JsonRepository;
20
21
  protected abstract loadDetails(route: Route, allreadyAdded?: boolean): Promise<void>;
@@ -34,6 +34,7 @@ class DBLoader extends types_1.Loader {
34
34
  return __awaiter(this, void 0, void 0, function* () {
35
35
  var _a;
36
36
  this.routeDescriptions = yield this.loadDescriptions();
37
+ this.verifyImportDate(this.routeDescriptions);
37
38
  const promises = [];
38
39
  (_a = this.routeDescriptions) === null || _a === void 0 ? void 0 : _a.forEach(descr => {
39
40
  const route = new route_1.Route(this.buildRouteInfo(descr));
@@ -14,4 +14,5 @@ export declare class RoutesLegacyDbLoader extends DBLoader<RouteDBApiDescription
14
14
  protected checkLegacy(descr: RouteDBApiDescription): RouteDBApiDescription;
15
15
  protected loadDescriptions(): Promise<Array<RouteDBApiDescription>>;
16
16
  protected loadDetails(): Promise<void>;
17
+ protected verifyImportDate(routes: RouteDBApiDescription[]): void;
17
18
  }
@@ -143,6 +143,8 @@ let RoutesLegacyDbLoader = (() => {
143
143
  throw new Error('not implemented');
144
144
  });
145
145
  }
146
+ verifyImportDate(routes) {
147
+ }
146
148
  };
147
149
  __setFunctionName(_classThis, "RoutesLegacyDbLoader");
148
150
  (() => {
@@ -1,3 +1,4 @@
1
+ import { EventLogger } from "gd-eventlog";
1
2
  import { Observer, PromiseObserver } from "../../../base/types/observer";
2
3
  import IncyclistRoutesApi from "../../base/api";
3
4
  import { RouteApiDescription, RouteApiDetail } from "../../base/api/types";
@@ -9,6 +10,7 @@ export declare class RoutesApiLoader extends Loader<RouteApiDescription> {
9
10
  protected api: IncyclistRoutesApi;
10
11
  protected loadObserver: Observer;
11
12
  protected saveObserver: PromiseObserver<void>;
13
+ protected logger: EventLogger;
12
14
  constructor();
13
15
  load(): Observer;
14
16
  stopLoad(): void;
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.RoutesApiLoader = void 0;
16
+ const gd_eventlog_1 = require("gd-eventlog");
16
17
  const observer_1 = require("../../../base/types/observer");
17
18
  const api_1 = __importDefault(require("../../base/api"));
18
19
  const route_1 = require("../../base/model/route");
@@ -24,6 +25,7 @@ class RoutesApiLoader extends types_1.Loader {
24
25
  constructor() {
25
26
  super();
26
27
  this.api = api_1.default.getInstance();
28
+ this.logger = new gd_eventlog_1.EventLogger('RoutesApi');
27
29
  }
28
30
  load() {
29
31
  if (this.loadObserver)
@@ -42,6 +44,7 @@ class RoutesApiLoader extends types_1.Loader {
42
44
  for (let j = 0; j < p.value.length; j++) {
43
45
  const descr = p.value[j];
44
46
  let description;
47
+ let isUpdated = false;
45
48
  const existing = this.getDescriptionFromDB(descr.routeId || descr.id);
46
49
  const { isDeleted = false } = descr;
47
50
  if (existing
@@ -55,6 +58,15 @@ class RoutesApiLoader extends types_1.Loader {
55
58
  }
56
59
  else {
57
60
  description = this.buildRouteInfo(descr);
61
+ if (!existing) {
62
+ this.logger.logEvent({ message: 'route added', id: description.id, title: description.title, ts: description.tsImported });
63
+ description.tsImported = Date.now();
64
+ }
65
+ else {
66
+ description.tsImported = existing.tsImported;
67
+ this.logger.logEvent({ message: 'route updated', id: description.id, title: description.title, from: existing.version, to: description.version, ts: Date.now(), tsImported: description.tsImported });
68
+ isUpdated = true;
69
+ }
58
70
  }
59
71
  if (!description) {
60
72
  continue;
@@ -70,6 +82,9 @@ class RoutesApiLoader extends types_1.Loader {
70
82
  }
71
83
  else {
72
84
  this.save(route, true);
85
+ if (isUpdated) {
86
+ this.loadObserver.emit('route.updated', route);
87
+ }
73
88
  }
74
89
  }
75
90
  }
@@ -154,8 +169,8 @@ class RoutesApiLoader extends types_1.Loader {
154
169
  });
155
170
  }
156
171
  buildRouteInfo(descr, isLocal) {
157
- const { id, routeId, title, localizedTitle, country, distance, elevation, category, provider, video, points, previewUrl, routeHash, version, isDeleted } = descr;
158
- const data = { title, localizedTitle, country, distance, elevation, provider, category, previewUrl, routeHash, version, isDeleted };
172
+ const { id, routeId, title, localizedTitle, country, distance, elevation, category, provider, video, requiresDownload, points, previewUrl, downloadUrl, routeHash, version, isDeleted } = descr;
173
+ const data = { title, localizedTitle, country, distance, elevation, provider, category, previewUrl, requiresDownload, downloadUrl, routeHash, version, isDeleted };
159
174
  data.hasVideo = false;
160
175
  data.hasGpx = false;
161
176
  data.isLocal = isLocal || false;
@@ -30,6 +30,7 @@ export declare class RoutesDbLoader extends DBLoader<RouteInfoDBEntry> {
30
30
  protected loadFromLegacy(): Promise<Array<RouteInfoDBEntry>>;
31
31
  protected loadDescriptions(): Promise<Array<RouteInfoDBEntry>>;
32
32
  protected removeDuplicates(routes: Array<RouteInfoDBEntry>): any[];
33
+ protected verifyImportDate(routes: Array<RouteInfoDBEntry>): void;
33
34
  protected loadDetailRecord(target: Route | RouteInfo): Promise<RouteApiDetail>;
34
35
  protected loadDetails(route: Route, alreadyAdded?: boolean): Promise<void>;
35
36
  protected getVideoRepo(): JsonRepository;
@@ -192,6 +192,19 @@ let RoutesDbLoader = (() => {
192
192
  });
193
193
  return cleaned;
194
194
  }
195
+ verifyImportDate(routes) {
196
+ let changed = false;
197
+ routes.forEach(r => {
198
+ if (!r.tsImported) {
199
+ r.tsImported = Date.now();
200
+ changed = true;
201
+ }
202
+ });
203
+ if (changed) {
204
+ this.isDirty = true;
205
+ this.write();
206
+ }
207
+ }
195
208
  loadDetailRecord(target) {
196
209
  return __awaiter(this, void 0, void 0, function* () {
197
210
  var _a;
@@ -80,6 +80,7 @@ export declare class RouteListService extends IncyclistService {
80
80
  protected addFromApi(route: Route): Promise<void>;
81
81
  protected update(route: Route): Promise<void>;
82
82
  protected loadRoutes(): Promise<void>;
83
+ protected checkUIUpdateWithNoRepoStats(): Promise<void>;
83
84
  protected loadRoutesFromRepo(): Promise<void>;
84
85
  protected loadRoutesFromApi(): Promise<void>;
85
86
  protected selectList(route: Route): CardList<Route>;
@@ -70,6 +70,8 @@ const api_3 = __importDefault(require("../base/api"));
70
70
  const ActiveImportCard_1 = require("./cards/ActiveImportCard");
71
71
  const selected_1 = require("./lists/selected");
72
72
  const alternatives_1 = require("./lists/alternatives");
73
+ const utils_1 = require("./utils");
74
+ const utils_2 = require("incyclist-devices/lib/utils/utils");
73
75
  let RouteListService = (() => {
74
76
  let _classDecorators = [types_1.Singleton];
75
77
  let _classDescriptor;
@@ -270,6 +272,7 @@ let RouteListService = (() => {
270
272
  };
271
273
  this.logEvent({ message: 'preload route list completed', cards });
272
274
  this.initialized = true;
275
+ (0, utils_1.updateRepoStats)();
273
276
  this.emitLists('loaded');
274
277
  process.nextTick(() => { delete this.preloadObserver; });
275
278
  })
@@ -497,9 +500,24 @@ let RouteListService = (() => {
497
500
  loadRoutes() {
498
501
  return __awaiter(this, void 0, void 0, function* () {
499
502
  yield this.loadRoutesFromRepo();
503
+ yield this.checkUIUpdateWithNoRepoStats();
500
504
  yield this.loadRoutesFromApi();
501
505
  });
502
506
  }
507
+ checkUIUpdateWithNoRepoStats() {
508
+ return __awaiter(this, void 0, void 0, function* () {
509
+ const repoUpdate = (0, utils_1.getRepoUpdates)();
510
+ if (this.routes.length > 0 && repoUpdate.initial === undefined) {
511
+ const ts = Date.now() - 60000;
512
+ this.routes.forEach(r => {
513
+ r.description.tsImported = ts;
514
+ this.update(r);
515
+ });
516
+ (0, utils_1.updateRepoStats)(ts);
517
+ yield (0, utils_2.sleep)(5);
518
+ }
519
+ });
520
+ }
503
521
  loadRoutesFromRepo() {
504
522
  return __awaiter(this, void 0, void 0, function* () {
505
523
  return new Promise(done => {
@@ -528,12 +546,18 @@ let RouteListService = (() => {
528
546
  if (!description.hasVideo) {
529
547
  if (category === 'personal' || description.isLocal)
530
548
  return this.myRoutes;
549
+ if (description.category == 'alternatives')
550
+ return this.alternatives;
551
+ if (description.category == 'selected')
552
+ return this.selectedRoutes;
531
553
  return description.category ? this.alternatives : this.selectedRoutes;
532
554
  }
533
555
  else {
534
556
  if (description.isLocal || description.isDownloaded)
535
557
  return this.myRoutes;
536
- if (description.isDemo || (description.requiresDownload && !description.isDownloaded))
558
+ if (description.category == 'selected')
559
+ return this.selectedRoutes;
560
+ if (description.category == 'alternatives' || description.isDemo || (description.requiresDownload && !description.isDownloaded))
537
561
  return this.alternatives;
538
562
  if (category === undefined || category === 'personal' || category === 'imported')
539
563
  return this.myRoutes;
@@ -41,3 +41,8 @@ export interface SearchFilterOptions {
41
41
  contentTypes: Array<string>;
42
42
  routeTypes: Array<string>;
43
43
  }
44
+ export interface RoutesRepoUpdates {
45
+ prev?: number;
46
+ current?: number;
47
+ initial?: number;
48
+ }
@@ -0,0 +1,5 @@
1
+ import { RouteInfo } from "../base/types";
2
+ import { RoutesRepoUpdates } from "./types";
3
+ export declare const updateRepoStats: (ts?: number) => void;
4
+ export declare const getRepoUpdates: () => RoutesRepoUpdates;
5
+ export declare const checkIsNew: (descr: RouteInfo) => boolean;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkIsNew = exports.getRepoUpdates = exports.updateRepoStats = void 0;
4
+ const settings_1 = require("../../settings");
5
+ const DURATION_2_WEEKS = 1000 * 60 * 60 * 24 * 14;
6
+ const DURATION_30_SECONDS = 1000 * 30;
7
+ const updateRepoStats = (ts) => {
8
+ const settings = (0, settings_1.useUserSettings)();
9
+ const updates = settings.get('repo.routesUpdates', {});
10
+ if (updates.current)
11
+ updates.prev = updates.current;
12
+ else
13
+ updates.initial = ts ? ts : Date.now();
14
+ if (ts)
15
+ updates.initial = ts;
16
+ updates.current = Date.now();
17
+ settings.set('repo.routesUpdates', updates);
18
+ };
19
+ exports.updateRepoStats = updateRepoStats;
20
+ const getRepoUpdates = () => {
21
+ const settings = (0, settings_1.useUserSettings)();
22
+ const updates = settings.get('repo.routesUpdates', {});
23
+ return updates;
24
+ };
25
+ exports.getRepoUpdates = getRepoUpdates;
26
+ const checkIsNew = (descr) => {
27
+ let isNew = false;
28
+ const repoUpdates = (0, exports.getRepoUpdates)();
29
+ if (descr.tsImported === repoUpdates.initial)
30
+ return false;
31
+ if (repoUpdates.prev) {
32
+ if (!descr.isLocal && !descr.tsLastStart && (descr.tsImported - repoUpdates.initial) > DURATION_30_SECONDS && Date.now() - descr.tsImported < DURATION_2_WEEKS) {
33
+ isNew = true;
34
+ }
35
+ }
36
+ return isNew;
37
+ };
38
+ exports.checkIsNew = checkIsNew;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.3.21",
3
+ "version": "1.3.22",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.26"
6
6
  },