incyclist-services 1.4.18 → 1.4.20

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.
@@ -8,6 +8,8 @@ export declare class VeloHeroUpload extends IncyclistService implements IActivit
8
8
  constructor();
9
9
  init(): boolean;
10
10
  isConnected(): boolean;
11
+ isConnecting(): boolean;
12
+ disconnect(): void;
11
13
  upload(activity: ActivityDetails, format?: string): Promise<boolean>;
12
14
  getUrl(id: string): string;
13
15
  protected ensureInitialized(): any;
@@ -77,6 +77,12 @@ let VeloHeroUpload = (() => {
77
77
  isConnected() {
78
78
  return this.getVeloHeroAppConnection().isConnected();
79
79
  }
80
+ isConnecting() {
81
+ return this.getVeloHeroAppConnection().isConnecting();
82
+ }
83
+ disconnect() {
84
+ return this.getVeloHeroAppConnection().disconnect();
85
+ }
80
86
  upload(activity_1) {
81
87
  return __awaiter(this, arguments, void 0, function* (activity, format = 'TCX') {
82
88
  var _a;
@@ -1,16 +1,18 @@
1
+ import { EventLogger } from 'gd-eventlog';
1
2
  import { AppApiBase } from '../base';
2
3
  import { KomootAuthConfig, KomootCoordinate, KomootGetTourRequestFilters, KomootGetTourRequestParams, KomootLoginResponse, KomootTourSummary } from './types';
3
4
  export declare class KomootApi extends AppApiBase {
4
5
  protected username: string;
5
6
  protected password: string;
6
7
  protected userid: string;
7
- protected iid: number;
8
+ protected logger: EventLogger;
8
9
  constructor();
9
10
  login(username: string, password: string, userid: string): Promise<KomootLoginResponse>;
10
11
  setAuth(auth: KomootAuthConfig | null): void;
11
12
  isAuthenticated(): boolean;
12
13
  getTours(params?: KomootGetTourRequestParams, filters?: KomootGetTourRequestFilters): Promise<Array<KomootTourSummary>>;
13
14
  getTourCoordinates(id: number): Promise<Array<KomootCoordinate>>;
15
+ protected applyFilter(tours: Array<KomootTourSummary>, filters?: KomootGetTourRequestFilters): KomootTourSummary[];
14
16
  protected getBasicAuthCredentials(): {
15
17
  username: string;
16
18
  password: string;
@@ -10,14 +10,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.KomootApi = void 0;
13
+ const gd_eventlog_1 = require("gd-eventlog");
13
14
  const valid_1 = require("../../../../utils/valid");
14
15
  const base_1 = require("../base");
15
16
  const API_BASE_URL = 'https://api.komoot.de/v007';
16
- let IID = 0;
17
17
  class KomootApi extends base_1.AppApiBase {
18
18
  constructor() {
19
19
  super();
20
- this.iid = IID++;
20
+ this.logger = new gd_eventlog_1.EventLogger('KomootAPI');
21
21
  }
22
22
  login(username, password, userid) {
23
23
  return __awaiter(this, void 0, void 0, function* () {
@@ -71,43 +71,73 @@ class KomootApi extends base_1.AppApiBase {
71
71
  getTours() {
72
72
  return __awaiter(this, arguments, void 0, function* (params = {}, filters) {
73
73
  var _a, _b, _c;
74
- const auth = this.getBasicAuthCredentials();
75
- if (!this.userid)
76
- throw new Error('invalid credentials');
77
- const keys = Object.keys(params !== null && params !== void 0 ? params : {});
78
- const args = keys.map(key => `${key}=${params[key]}`).join('&');
79
- const url = keys.length > 0 ? `/users/${this.userid}/tours/?` + args : `/users/${this.userid}/tours/`;
80
- const response = yield this.get(url, { auth });
81
- if (response.status >= 400 && response.status < 500) {
82
- throw new Error('invalid credentials');
74
+ const tsStart = Date.now();
75
+ let args;
76
+ try {
77
+ const auth = this.getBasicAuthCredentials();
78
+ if (!this.userid) {
79
+ this.logger.logEvent({ message: 'getTours failed', reason: 'invalid credentials' });
80
+ throw new Error('invalid credentials');
81
+ }
82
+ const keys = Object.keys(params !== null && params !== void 0 ? params : {});
83
+ args = keys.map(key => `${key}=${params[key]}`).join('&');
84
+ const url = keys.length > 0 ? `/users/${this.userid}/tours/?` + args : `/users/${this.userid}/tours/`;
85
+ const response = yield this.get(url, { auth });
86
+ if (response.status >= 400 && response.status < 500) {
87
+ this.logger.logEvent({ message: 'getTours failed', args, reason: 'invalid credentials', duration: Date.now() - tsStart });
88
+ throw new Error('invalid credentials');
89
+ }
90
+ const tours = (_c = (_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a._embedded) === null || _b === void 0 ? void 0 : _b.tours) !== null && _c !== void 0 ? _c : [];
91
+ const result = this.applyFilter(tours, filters);
92
+ this.logger.logEvent({ message: 'getTours result', args, tours: result.length, duration: Date.now() - tsStart });
93
+ return result;
94
+ }
95
+ catch (err) {
96
+ this.logger.logEvent({ message: 'getTours failed', args, reason: err.message, duration: Date.now() - tsStart });
97
+ throw (err);
83
98
  }
84
- const tours = (_c = (_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a._embedded) === null || _b === void 0 ? void 0 : _b.tours) !== null && _c !== void 0 ? _c : [];
85
- return tours.filter(tour => {
86
- let valid = true;
87
- if (filters === null || filters === void 0 ? void 0 : filters.sport)
88
- valid = valid && (tour.sport === filters.sport);
89
- if (filters === null || filters === void 0 ? void 0 : filters.after)
90
- valid = valid && (new Date(tour.date).valueOf() > filters.after.valueOf());
91
- if (filters === null || filters === void 0 ? void 0 : filters.lastUpdateAfter)
92
- valid = valid && (new Date(tour.changed_at).valueOf() > filters.lastUpdateAfter.valueOf());
93
- return valid;
94
- });
95
99
  });
96
100
  }
97
101
  getTourCoordinates(id) {
98
102
  return __awaiter(this, void 0, void 0, function* () {
99
103
  var _a, _b;
100
- const url = `/tours/${id}/coordinates`;
101
- const auth = this.getBasicAuthCredentials();
102
- const response = yield this.get(url, { auth });
103
- if (response.status === 404) {
104
- throw new Error('not found');
104
+ const tsStart = Date.now();
105
+ try {
106
+ const url = `/tours/${id}/coordinates`;
107
+ const auth = this.getBasicAuthCredentials();
108
+ const response = yield this.get(url, { auth });
109
+ if (response.status === 404) {
110
+ this.logger.logEvent({ message: 'getTourCoordinates failed', id, reason: 'not found', duration: Date.now() - tsStart });
111
+ throw new Error('not found');
112
+ }
113
+ else if (response.status >= 400 && response.status < 500) {
114
+ this.logger.logEvent({ message: 'getTourCoordinates failed', id, reason: 'invalid credentials', duration: Date.now() - tsStart });
115
+ throw new Error('invalid credentials');
116
+ }
117
+ const points = (_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.items) !== null && _b !== void 0 ? _b : [];
118
+ this.logger.logEvent({ message: 'getTourCoordinates result', id, points: points.length, duration: Date.now() - tsStart });
119
+ return points;
105
120
  }
106
- else if (response.status >= 400 && response.status < 500) {
107
- throw new Error('invalid credentials');
121
+ catch (err) {
122
+ this.logger.logEvent({ message: 'getTourCoordinates failed', id, reason: err.message, duration: Date.now() - tsStart });
123
+ throw (err);
108
124
  }
109
- const points = (_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.items) !== null && _b !== void 0 ? _b : [];
110
- return points;
125
+ });
126
+ }
127
+ applyFilter(tours, filters) {
128
+ var _a, _b;
129
+ if (!filters)
130
+ return tours;
131
+ const sportTypeFilter = (_b = (_a = filters.sport) === null || _a === void 0 ? void 0 : _a.split(',')) !== null && _b !== void 0 ? _b : [];
132
+ return tours.filter(tour => {
133
+ let valid = true;
134
+ if (filters === null || filters === void 0 ? void 0 : filters.sport)
135
+ valid = valid && (sportTypeFilter.includes(tour.sport));
136
+ if (filters === null || filters === void 0 ? void 0 : filters.after)
137
+ valid = valid && (new Date(tour.date).valueOf() > filters.after.valueOf());
138
+ if (filters === null || filters === void 0 ? void 0 : filters.lastUpdateAfter)
139
+ valid = valid && (new Date(tour.changed_at).valueOf() > filters.lastUpdateAfter.valueOf());
140
+ return valid;
111
141
  });
112
142
  }
113
143
  getBasicAuthCredentials() {
@@ -90,4 +90,5 @@ export interface KomootCoordinate extends KomootPoint {
90
90
  t: number;
91
91
  }
92
92
  export type KomootTourType = 'tour_planned' | 'tour_recorded';
93
- export type KomootSportType = 'hike' | 'touringbicycle' | 'racebike' | 'mtb' | 'jogging' | 'mtb_easy' | 'mtb_advancde' | 'mountaineering';
93
+ export type KomootSportType = 'hike' | 'touringbicycle' | 'mtb' | 'racebike' | 'jogging' | 'mtb_easy' | 'mtb_advancde' | 'mountaineering';
94
+ export type KomootSportTypeNames = 'Hiking' | 'Cycling' | 'Mountain biking' | 'Road cycling' | 'Running' | 'Gravel riding' | 'Enduro mountain biking' | 'Mountaineering';
@@ -24,6 +24,7 @@ export declare class AppsService extends IncyclistService {
24
24
  getAppOperationSettings(app: string): any[];
25
25
  getConnectedServices(operation: AppsOperation): Array<AppDefinition>;
26
26
  getName(key: string): string;
27
+ getKey(name: string): string;
27
28
  protected getAppDisplayProps(key: string): {
28
29
  name: string;
29
30
  key: string;
@@ -156,6 +156,10 @@ let AppsService = (() => {
156
156
  const found = this.serviceMap.find(r => r.key === key);
157
157
  return found ? found.name : key;
158
158
  }
159
+ getKey(name) {
160
+ const found = this.serviceMap.find(r => r.name === name);
161
+ return found ? found.key : name;
162
+ }
159
163
  getAppDisplayProps(key) {
160
164
  const entry = this.serviceMap.find(e => e.key === key);
161
165
  const { name, iconUrl } = entry;
@@ -91,8 +91,6 @@ class EPMParser extends xml_1.XMLParser {
91
91
  json.min = reader.ReadUint32();
92
92
  json.max = reader.ReadUint32();
93
93
  json.cnt = reader.ReadUint32();
94
- if (json.cnt !== num) {
95
- }
96
94
  json.sampleRate = reader.ReadUint32();
97
95
  json.validFor = reader.ReadUint32();
98
96
  json.elevationStart = reader.ReadFloat();
@@ -112,6 +112,7 @@ export interface RouteInfo extends RouteBase {
112
112
  originalName?: string;
113
113
  version?: number;
114
114
  source?: string;
115
+ isLoopVerified?: boolean;
115
116
  }
116
117
  export type LocalizedText = {
117
118
  [index: string]: string;
@@ -6,15 +6,18 @@ import { Route } from "../../base/model/route";
6
6
  import { BaseCard } from "./base";
7
7
  import { RouteCardType, ActiveImportProps } from "./types";
8
8
  import { AppStatus } from "../../base/types";
9
+ import { EventLogger } from "gd-eventlog";
9
10
  export declare class ActiveImportCard extends BaseCard implements Card<Route> {
10
11
  protected file: FileInfo;
11
12
  protected error: Error;
12
13
  protected cardObserver: any;
13
14
  protected deleteObserver: PromiseObserver<void>;
15
+ protected logger: EventLogger;
14
16
  constructor(file: FileInfo);
15
17
  setVisible(): void;
16
18
  canStart(status: AppStatus): boolean;
17
19
  delete(): PromiseObserver<boolean>;
20
+ setInitialized(init: boolean): void;
18
21
  setError(error: Error): void;
19
22
  setData(): void;
20
23
  emitUpdate(): void;
@@ -25,4 +28,5 @@ export declare class ActiveImportCard extends BaseCard implements Card<Route> {
25
28
  getTitle(): string;
26
29
  getDisplayProperties(): ActiveImportProps;
27
30
  retry(): void;
31
+ protected logError(err: Error, fn: string): void;
28
32
  }
@@ -5,12 +5,14 @@ const observer_1 = require("../../../base/types/observer");
5
5
  const service_1 = require("../service");
6
6
  const base_1 = require("./base");
7
7
  const types_1 = require("./types");
8
+ const gd_eventlog_1 = require("gd-eventlog");
8
9
  class ActiveImportCard extends base_1.BaseCard {
9
10
  constructor(file) {
10
11
  super();
11
12
  this.file = file;
12
13
  this.error = null;
13
14
  this.cardObserver = new observer_1.Observer();
15
+ this.logger = new gd_eventlog_1.EventLogger('ActiveImportCard');
14
16
  }
15
17
  setVisible() {
16
18
  this.visible = true;
@@ -26,17 +28,26 @@ class ActiveImportCard extends base_1.BaseCard {
26
28
  }
27
29
  return observer_1.PromiseObserver.alwaysReturning(true);
28
30
  }
31
+ setInitialized(init) {
32
+ try {
33
+ const prev = this.initialized;
34
+ this.initialized = init;
35
+ if (init && !prev)
36
+ this.emitUpdate();
37
+ }
38
+ catch (err) {
39
+ this.logError(err, 'setInitialized');
40
+ }
41
+ }
29
42
  setError(error) {
30
43
  this.error = error;
31
- process.nextTick(() => {
32
- this.emitUpdate();
33
- });
44
+ process.nextTick(() => { this.emitUpdate(); });
34
45
  }
35
46
  setData() {
36
47
  }
37
48
  emitUpdate() {
38
49
  const props = this.getDisplayProperties();
39
- this.cardObserver.emit('update', props);
50
+ this.cardObserver.emit('update', Object.assign({}, props));
40
51
  }
41
52
  getData() {
42
53
  return undefined;
@@ -65,5 +76,8 @@ class ActiveImportCard extends base_1.BaseCard {
65
76
  this.emitUpdate();
66
77
  (0, service_1.useRouteList)().import(this.file, this);
67
78
  }
79
+ logError(err, fn) {
80
+ this.logger.logEvent({ message: 'error', error: err.message, fn, stack: err.stack });
81
+ }
68
82
  }
69
83
  exports.ActiveImportCard = ActiveImportCard;
@@ -92,8 +92,11 @@ export declare class RouteCard extends BaseCard implements Card<Route> {
92
92
  changeSettings(props: RouteSettings): void;
93
93
  protected adjustStartPosAvi(settings: RouteSettings): void;
94
94
  save(): Promise<void>;
95
- delete(enforced?: boolean): PromiseObserver<boolean>;
96
- protected _delete(enforced?: boolean): Promise<boolean>;
95
+ delete(props?: {
96
+ enforced?: boolean;
97
+ source: 'user' | 'system';
98
+ }): PromiseObserver<boolean>;
99
+ protected _delete(enforced?: boolean, source?: 'user' | 'system'): Promise<boolean>;
97
100
  start(): void;
98
101
  cancel(): void;
99
102
  addWorkout(): void;
@@ -338,22 +338,23 @@ class RouteCard extends base_1.BaseCard {
338
338
  }
339
339
  });
340
340
  }
341
- delete(enforced) {
341
+ delete(props) {
342
+ const { enforced, source = 'user' } = props !== null && props !== void 0 ? props : {};
342
343
  try {
343
344
  this.logger.logEvent({ message: 'delete card', card: this.getTitle() });
344
345
  const service = (0, service_1.getRouteList)();
345
346
  service.unselectCard(this);
346
347
  if (this.deleteObserver)
347
348
  return this.deleteObserver;
348
- this.deleteObserver = new observer_1.PromiseObserver(this._delete(enforced));
349
+ this.deleteObserver = new observer_1.PromiseObserver(this._delete(enforced, source));
349
350
  return this.deleteObserver;
350
351
  }
351
352
  catch (err) {
352
353
  this.logError(err, 'delete');
353
354
  }
354
355
  }
355
- _delete(enforced) {
356
- return __awaiter(this, void 0, void 0, function* () {
356
+ _delete(enforced_1) {
357
+ return __awaiter(this, arguments, void 0, function* (enforced, source = 'user') {
357
358
  yield (0, utils_1.waitNextTick)();
358
359
  let deleted = false;
359
360
  try {
@@ -384,7 +385,7 @@ class RouteCard extends base_1.BaseCard {
384
385
  this.deleteFromUIList();
385
386
  this.deleteRouteUserSettings();
386
387
  this.logger.logEvent({ message: 'card deleted', card: this.getTitle() });
387
- (0, service_1.getRouteList)().emitLists('updated', { log: true });
388
+ (0, service_1.getRouteList)().emitLists('updated', { log: true, source });
388
389
  deleted = true;
389
390
  }
390
391
  catch (err) {
@@ -742,6 +743,8 @@ class RouteCard extends base_1.BaseCard {
742
743
  }
743
744
  verifyRoute(route) {
744
745
  var _a, _b;
746
+ if (route.description.isLoopVerified)
747
+ return;
745
748
  if ((0, route_1.checkIsLoop)(route)) {
746
749
  const points = (_b = (_a = route.details) === null || _a === void 0 ? void 0 : _a.points) !== null && _b !== void 0 ? _b : [];
747
750
  if (!points.length)
@@ -753,6 +756,7 @@ class RouteCard extends base_1.BaseCard {
753
756
  if (Math.abs(slope) > 1) {
754
757
  this.logger.logEvent({ message: 'Loop elevation profile adjusted', route: route.description.title });
755
758
  (0, route_1.updateSlopes)(route.details.points);
759
+ route.description.isLoopVerified = true;
756
760
  }
757
761
  }
758
762
  }
@@ -91,6 +91,8 @@ export declare class RouteListService extends IncyclistService implements IRoute
91
91
  observer?: undefined;
92
92
  cards?: undefined;
93
93
  };
94
+ getVisibleRoutes(): Array<Route>;
95
+ getAllAppRoutes(source: string): Array<RouteInfo>;
94
96
  searchRepo(requestedFilters?: SearchFilter): {
95
97
  routes: SummaryCardDisplayProps[];
96
98
  filters: SearchFilter;
@@ -137,27 +139,29 @@ export declare class RouteListService extends IncyclistService implements IRoute
137
139
  import(info: FileInfo | Array<FileInfo>, retry?: ActiveImportCard): void;
138
140
  emitLists(event: 'loaded' | 'updated', props?: {
139
141
  log?: boolean;
142
+ source?: 'user' | 'system';
140
143
  }): void;
141
144
  getCard(id: string): RouteCard;
142
145
  addCardAgain(card: RouteCard): void;
143
146
  protected addImportCard(file: FileInfo): ActiveImportCard;
144
147
  protected getFilterContentTypes(): Array<string>;
145
148
  protected getFilterRouteTypes(): Array<string>;
149
+ protected getFilterRouteSources(): Array<string>;
146
150
  protected getFilterCountries(): Array<string>;
147
- protected addRoute(route: Route): void;
151
+ protected addRoute(route: Route, source?: 'user' | 'system'): void;
148
152
  protected verifyPoints(card: RouteCard, route: Route): Promise<void>;
149
153
  protected addFromApi(route: Route): Promise<void>;
150
- protected update(route: Route): Promise<void>;
154
+ protected update(route: Route, source?: 'user' | 'system'): Promise<void>;
151
155
  protected loadRoutes(): Promise<void>;
152
156
  protected preloadDetails(): Promise<void>;
153
157
  protected startSync(): void;
154
158
  protected getSyncFrequency(): any;
155
- protected checkKomootFeatureEnabled(): any;
156
159
  protected stopSync(): void;
157
160
  protected performSync(service?: string): Promise<void>;
158
161
  protected loadSyncedRouteDetails(route: Route): Promise<Route>;
159
162
  protected onSyncRouteAdded(source: string, descriptions: Array<RouteInfo>): void;
160
163
  protected onSyncRouteUpdated(source: string, routes: Array<Route>): void;
164
+ protected onSyncRouteDeleted(source: string, routes: Array<string>): void;
161
165
  protected checkUIUpdateWithNoRepoStats(): Promise<void>;
162
166
  protected loadRoutesFromRepo(): Promise<void>;
163
167
  protected loadRoutesFromApi(): Promise<void>;
@@ -103,7 +103,7 @@ let RouteListService = (() => {
103
103
  this.db = new db_1.RoutesDbLoader();
104
104
  this.createPreviewQueue = [];
105
105
  this.routes = [];
106
- this.filters = {};
106
+ this.filters = this.getFilters();
107
107
  this.syncInfo = {};
108
108
  this.getRouteSyncFactory().setRouteList(this);
109
109
  this.handleConfigChanges();
@@ -182,13 +182,16 @@ let RouteListService = (() => {
182
182
  saveFilters(requestedFilters) {
183
183
  this.filters = requestedFilters;
184
184
  const settings = this.getUserSettings();
185
- settings.set('preferencs.search.filter', requestedFilters !== null && requestedFilters !== void 0 ? requestedFilters : null);
185
+ settings.set('preferences.search.filter', requestedFilters !== null && requestedFilters !== void 0 ? requestedFilters : null);
186
186
  }
187
187
  getFilters() {
188
- if (!this.filters) {
189
- const settings = this.getUserSettings();
190
- this.filters = settings.get('preferencs.search.filter', undefined);
188
+ try {
189
+ if (!this.filters) {
190
+ const settings = this.getUserSettings();
191
+ this.filters = settings.get('preferences.search.filter', undefined);
192
+ }
191
193
  }
194
+ catch (_a) { }
192
195
  return this.filters;
193
196
  }
194
197
  searchAgain() {
@@ -199,28 +202,39 @@ let RouteListService = (() => {
199
202
  if (!this.initialized)
200
203
  this.preload();
201
204
  const filters = requestedFilters || this.filters;
202
- this.prevFilters = filters;
203
- return this.searchRepo(requestedFilters);
205
+ if (filters !== this.prevFilters) {
206
+ this.prevFilters = filters;
207
+ this.saveFilters(filters);
208
+ }
209
+ return this.searchRepo(filters);
210
+ }
211
+ getVisibleRoutes() {
212
+ return this.routes.filter(r => r.description && !r.description.isDeleted);
213
+ }
214
+ getAllAppRoutes(source) {
215
+ let routes = this.routes.map(r => r.description);
216
+ routes = this.applySourceFilter({ routeSource: source }, routes);
217
+ return routes;
204
218
  }
205
219
  searchRepo(requestedFilters) {
206
220
  if (!this.observer)
207
221
  this.observer = new RouteListObserver_1.RouteListObserver(this);
208
222
  try {
209
- const filters = requestedFilters || this.filters;
223
+ const filters = requestedFilters !== null && requestedFilters !== void 0 ? requestedFilters : this.filters;
210
224
  let routes = Array.from(this.getAllSearchCards().map(c => c.getDisplayProperties()));
211
225
  routes.sort((a, b) => a.title > b.title ? 1 : -1);
212
226
  if (!filters) {
213
227
  return { routes, filters, observer: this.observer };
214
228
  }
215
- routes = routes.filter(r => filters.includeDeleted || !(r === null || r === void 0 ? void 0 : r.isDeleted));
229
+ if (!filters.includeDeleted)
230
+ routes = routes.filter(r => !(r === null || r === void 0 ? void 0 : r.isDeleted));
216
231
  routes = this.applyTitleFilter(filters, routes);
217
232
  routes = this.applyDistanceFilter(filters, routes);
218
233
  routes = this.applyElevationFilter(filters, routes);
219
234
  routes = this.applyCountryFilter(filters, routes);
220
235
  routes = this.applyContentTypeFilter(filters, routes);
221
236
  routes = this.applyRouteTypeFilter(filters, routes);
222
- if (this.checkKomootFeatureEnabled())
223
- routes = this.applySourceFilter(filters, routes);
237
+ routes = this.applySourceFilter(filters, routes);
224
238
  const cards = routes.map(r => this.getCard(r.id));
225
239
  this.setListTop('list', 0);
226
240
  this.setListTop('tiles', 0);
@@ -260,11 +274,12 @@ let RouteListService = (() => {
260
274
  }
261
275
  applySourceFilter(filters, routes) {
262
276
  routes = routes.filter(r => r.source === undefined || this.getAppsService().isEnabled(r.source, 'RouteDownload'));
263
- if (filters.source) {
264
- const internal = filters.source === 'none';
265
- const sources = filters.source.split('|');
266
- routes = routes.filter(r => (r.source === undefined && internal) ||
267
- (r.source === undefined && sources.includes('incyclist')) ||
277
+ if (filters.routeSource) {
278
+ const local = filters.routeSource === 'Local';
279
+ const internal = filters.routeSource === 'Incyclist';
280
+ const sources = filters.routeSource.split('|').map(s => this.getAppsService().getKey(s));
281
+ routes = routes.filter(r => (r.source === undefined && !r.isLocal && internal) ||
282
+ (r.source === undefined && r.isLocal && local) ||
268
283
  (r.source && !internal && sources.includes(r.source)));
269
284
  }
270
285
  return routes;
@@ -319,15 +334,17 @@ let RouteListService = (() => {
319
334
  let countries = [];
320
335
  let contentTypes = [];
321
336
  let routeTypes = [];
337
+ let routeSources = [];
322
338
  try {
323
339
  countries = this.getFilterCountries();
324
340
  contentTypes = this.getFilterContentTypes();
325
341
  routeTypes = this.getFilterRouteTypes();
342
+ routeSources = this.getFilterRouteSources();
326
343
  }
327
344
  catch (err) {
328
345
  this.logError(err, 'getFilterOption');
329
346
  }
330
- return { countries, contentTypes, routeTypes };
347
+ return { countries, contentTypes, routeTypes, routeSources };
331
348
  }
332
349
  onResize() {
333
350
  try {
@@ -439,7 +456,7 @@ let RouteListService = (() => {
439
456
  getRouteDetails(id_1) {
440
457
  return __awaiter(this, arguments, void 0, function* (id, expectLocal = false) {
441
458
  try {
442
- const route = this.routes.find(r => r.description.id === id);
459
+ const route = this.getVisibleRoutes().find(r => r.description.id === id);
443
460
  if (!route)
444
461
  return;
445
462
  if (expectLocal && route.description.requiresDownload && !route.description.isDownloaded) {
@@ -459,7 +476,7 @@ let RouteListService = (() => {
459
476
  getRouteDescription(id) {
460
477
  var _a;
461
478
  try {
462
- return (_a = this.routes.find(r => r.description.id === id)) === null || _a === void 0 ? void 0 : _a.description;
479
+ return (_a = this.getVisibleRoutes().find(r => r.description.id === id)) === null || _a === void 0 ? void 0 : _a.description;
463
480
  }
464
481
  catch (err) {
465
482
  this.logError(err, 'getRouteDescription', id);
@@ -550,20 +567,19 @@ let RouteListService = (() => {
550
567
  emitLists(event, props) {
551
568
  var _a;
552
569
  try {
570
+ const { log, source = 'user' } = props !== null && props !== void 0 ? props : {};
553
571
  if (this.currentView === 'grid' || this.currentView === 'list') {
554
572
  const d = this.searchAgain();
555
- this.observer.emit(event, d);
573
+ this.observer.emit(event, d, source);
556
574
  return;
557
575
  }
558
- const { log } = props !== null && props !== void 0 ? props : {};
559
576
  if (log) {
560
577
  const logs = (_a = this.createRoutesLogEntry(true)) !== null && _a !== void 0 ? _a : {};
561
578
  this.logEvent(Object.assign({ message: `RoutesList ${event}` }, logs));
562
579
  }
563
580
  const lists = this.getLists();
564
- const hash = lists ? lists.map(l => l.getCards().map(c => c.getId()).join(',')).join(':') : '';
565
581
  if (this.observer)
566
- this.observer.emit(event, lists, hash);
582
+ this.observer.emit(event, lists, source);
567
583
  }
568
584
  catch (err) {
569
585
  this.logError(err, 'emitLists', event);
@@ -593,10 +609,23 @@ let RouteListService = (() => {
593
609
  getFilterRouteTypes() {
594
610
  return ['Loop', 'Point to Point'];
595
611
  }
612
+ getFilterRouteSources() {
613
+ const options = ['Local', 'Incyclist'];
614
+ this.getVisibleRoutes().forEach(r => {
615
+ const source = r.description.source;
616
+ if (!source)
617
+ return;
618
+ const option = this.getAppsService().getName(source);
619
+ if (option && !options.includes(option)) {
620
+ options.push(option);
621
+ }
622
+ });
623
+ return options;
624
+ }
596
625
  getFilterCountries() {
597
626
  try {
598
627
  const countries = [];
599
- this.routes.forEach(r => {
628
+ this.getVisibleRoutes().forEach(r => {
600
629
  const iso = r.description.country;
601
630
  let country = 'Unknown';
602
631
  if (iso)
@@ -618,16 +647,21 @@ let RouteListService = (() => {
618
647
  return [];
619
648
  }
620
649
  }
621
- addRoute(route) {
622
- var _a;
623
- this.logEvent({ message: 'route added', route: route.description.title, source: route.description.source });
650
+ addRoute(route, source = 'system') {
651
+ var _a, _b;
652
+ this.routes.push(route);
624
653
  if ((_a = route.description) === null || _a === void 0 ? void 0 : _a.isDeleted)
625
654
  return;
626
- if (!route.description.country) {
627
- route.updateCountryFromPoints()
628
- .then(() => {
629
- this.db.save(route, false);
630
- });
655
+ if (!((_b = route.description) === null || _b === void 0 ? void 0 : _b.isDeleted)) {
656
+ if (source === 'user') {
657
+ this.logEvent({ message: 'route added', route: route.description.title, source: route.description.source });
658
+ }
659
+ if (!route.description.country) {
660
+ route.updateCountryFromPoints()
661
+ .then(() => {
662
+ this.db.save(route, false);
663
+ });
664
+ }
631
665
  }
632
666
  const list = this.selectList(route);
633
667
  const card = new RouteCard_1.RouteCard(route, { list });
@@ -635,8 +669,7 @@ let RouteListService = (() => {
635
669
  list.add(card);
636
670
  if (list.getId() === 'myRoutes')
637
671
  card.enableDelete(true);
638
- this.routes.push(route);
639
- this.emitLists('updated');
672
+ this.emitLists('updated', { source });
640
673
  }
641
674
  verifyPoints(card, route) {
642
675
  return __awaiter(this, void 0, void 0, function* () {
@@ -651,11 +684,11 @@ let RouteListService = (() => {
651
684
  const existing = this.findCard(route);
652
685
  if (existing)
653
686
  return;
654
- this.addRoute(route);
687
+ this.addRoute(route, 'system');
655
688
  });
656
689
  }
657
- update(route) {
658
- return __awaiter(this, void 0, void 0, function* () {
690
+ update(route_2) {
691
+ return __awaiter(this, arguments, void 0, function* (route, source = 'user') {
659
692
  const existing = this.findCard(route);
660
693
  if (existing)
661
694
  existing.card.updateRoute(route);
@@ -701,8 +734,6 @@ let RouteListService = (() => {
701
734
  });
702
735
  }
703
736
  startSync() {
704
- if (!this.checkKomootFeatureEnabled())
705
- return;
706
737
  try {
707
738
  if (!this.syncInfo.iv) {
708
739
  this.syncInfo.iv = setInterval(() => this.performSync(), this.getSyncFrequency());
@@ -716,10 +747,6 @@ let RouteListService = (() => {
716
747
  getSyncFrequency() {
717
748
  return this.getUserSettings().get('syncFrequency', SYNC_INTERVAL);
718
749
  }
719
- checkKomootFeatureEnabled() {
720
- const settings = (0, settings_1.useUserSettings)();
721
- return settings.get('KOMOOT', false);
722
- }
723
750
  stopSync() {
724
751
  var _a;
725
752
  if (!this.syncInfo.iv)
@@ -739,8 +766,10 @@ let RouteListService = (() => {
739
766
  this.syncInfo.observer = observer;
740
767
  const onAdded = this.onSyncRouteAdded.bind(this);
741
768
  const onUpdated = this.onSyncRouteUpdated.bind(this);
769
+ const onDeleted = this.onSyncRouteDeleted.bind(this);
742
770
  observer.on('added', onAdded);
743
771
  observer.on('updated', onUpdated);
772
+ observer.on('deleted', onDeleted);
744
773
  observer.once('done', () => {
745
774
  delete this.syncInfo.observer;
746
775
  this.observer.emit('sync-done');
@@ -769,7 +798,8 @@ let RouteListService = (() => {
769
798
  descriptions.forEach(descr => {
770
799
  const route = new route_1.Route(descr);
771
800
  this.db.save(route);
772
- this.addRoute(route);
801
+ this.addRoute(route, 'system');
802
+ this.logEvent({ message: 'route added', route: route.description.title, source });
773
803
  });
774
804
  }
775
805
  onSyncRouteUpdated(source, routes) {
@@ -777,7 +807,18 @@ let RouteListService = (() => {
777
807
  descriptions.forEach(descr => {
778
808
  const route = new route_1.Route(descr);
779
809
  this.loadSyncedRouteDetails(route);
780
- this.update(route);
810
+ this.update(route, 'system');
811
+ this.logEvent({ message: 'route updated', route: route.description.title, source });
812
+ });
813
+ }
814
+ onSyncRouteDeleted(source, routes) {
815
+ routes.forEach(id => {
816
+ const card = this.getCard(id);
817
+ if (card) {
818
+ card.delete({ source: 'system' });
819
+ const description = card.getRouteDescription();
820
+ this.logEvent({ message: 'route deleted', route: description === null || description === void 0 ? void 0 : description.title, source });
821
+ }
781
822
  });
782
823
  }
783
824
  checkUIUpdateWithNoRepoStats() {
@@ -826,6 +867,8 @@ let RouteListService = (() => {
826
867
  yield this.loadSyncedRouteDetails(route);
827
868
  }
828
869
  else {
870
+ const target = [{ route, added: false }];
871
+ yield this.api.loadDetails(target);
829
872
  }
830
873
  }
831
874
  if (route.description.hasVideo) {
@@ -843,7 +886,7 @@ let RouteListService = (() => {
843
886
  this.db.save(route, false);
844
887
  })
845
888
  .catch(err => {
846
- console.log('# error', route.description.id, route.description.title, err);
889
+ this.logError(err, 'verifyRouteCountry', { id: route.description.id, title: route.description.title });
847
890
  });
848
891
  }
849
892
  }
@@ -993,7 +1036,7 @@ let RouteListService = (() => {
993
1036
  const legacyCard = this.myRoutes.getCards().find(c => { var _a, _b; return ((_b = (_a = c.getData()) === null || _a === void 0 ? void 0 : _a.description) === null || _b === void 0 ? void 0 : _b.id) === legacyId; });
994
1037
  if (legacyCard) {
995
1038
  this.myRoutes.remove(legacyCard);
996
- const idx = this.routes.findIndex(r => r.description.id === legacyId);
1039
+ const idx = this.routes.findIndex(r => r.description.id === legacyId && !r.description.isDeleted);
997
1040
  if (idx !== -1) {
998
1041
  this.routes.splice(idx, 1);
999
1042
  }
@@ -1,4 +1,5 @@
1
1
  import { LatLng } from "../../utils/geo";
2
+ import { RouteInfo } from "../base/types";
2
3
  import { SummaryCardDisplayProps } from "./cards/RouteCard";
3
4
  import { RouteCardType } from "./cards/types";
4
5
  export interface RouteStartSettings {
@@ -37,13 +38,14 @@ export interface SearchFilter {
37
38
  country?: string;
38
39
  contentType?: string;
39
40
  routeType?: string;
40
- source?: string;
41
+ routeSource?: string;
41
42
  includeDeleted?: boolean;
42
43
  }
43
44
  export interface SearchFilterOptions {
44
45
  countries: Array<string>;
45
46
  contentTypes: Array<string>;
46
47
  routeTypes: Array<string>;
48
+ routeSources: Array<string>;
47
49
  }
48
50
  export interface RoutesRepoUpdates {
49
51
  prev?: number;
@@ -58,4 +60,5 @@ export interface IRouteList {
58
60
  searchRepo(requestedFilters?: SearchFilter): {
59
61
  routes: Array<SummaryCardDisplayProps>;
60
62
  };
63
+ getAllAppRoutes(source: string): Array<RouteInfo>;
61
64
  }
@@ -90,6 +90,7 @@ let RouteSyncFactory = (() => {
90
90
  syncs[service] = so;
91
91
  so.on('added', (info) => observer.emit('added', service, info));
92
92
  so.on('updated', (routes) => observer.emit('updated', service, routes));
93
+ so.on('deleted', (routes) => observer.emit('deleted', service, routes));
93
94
  so.on('details', (route) => observer.emit('details', service, route));
94
95
  so.on('done', () => {
95
96
  delete syncs[service];
@@ -7,6 +7,11 @@ import { RouteInfo, RoutePoint } from "../../base/types";
7
7
  import { IRouteList } from "../../list/types";
8
8
  import { RouteSyncFactory } from "../factory";
9
9
  import { IRouteSyncProvider } from "../types";
10
+ interface SyncModifications {
11
+ added: Array<KomootTourSummary>;
12
+ updated: Array<Route>;
13
+ deleted: Array<string>;
14
+ }
10
15
  export declare class KomootSyncProvider extends IncyclistService implements IRouteSyncProvider {
11
16
  protected lastSyncTS: number;
12
17
  protected observer: Observer;
@@ -18,18 +23,23 @@ export declare class KomootSyncProvider extends IncyclistService implements IRou
18
23
  protected getKomootId(route: Route): number;
19
24
  protected stopSync(): void;
20
25
  protected loadTours(): Promise<Array<KomootTourSummary>>;
21
- protected checkTours(tours: Array<KomootTourSummary>): {
22
- added: Array<KomootTourSummary>;
23
- updated: Array<Route>;
24
- };
26
+ protected emitUpdates(added: KomootTourSummary[], updated: Route[], deleted: string[]): void;
27
+ protected loadToursFromApi(): Promise<{
28
+ tours: Array<KomootTourSummary>;
29
+ done: boolean;
30
+ }>;
31
+ protected checkTours(tours: Array<KomootTourSummary>): SyncModifications;
25
32
  protected buildRouteInfo(tour: KomootTourSummary): RouteInfo;
26
33
  protected adddRouteDetails(route: Route, coordinates: Array<KomootCoordinate>): Promise<void>;
27
34
  protected buildRoutePoints(coordinates: KomootCoordinate[], points: RoutePoint[]): RoutePoint[];
28
35
  protected emitAdded(tours: Array<KomootTourSummary>): void;
29
36
  protected emitUpdated(routes: Array<Route>): void;
37
+ protected emitDeleted(routes: Array<string>): void;
30
38
  protected getLastSyncTime(): Date;
31
39
  protected getSportFilter(): string;
40
+ protected getAvailableSports(): Array<string>;
32
41
  protected getCreatedFilter(days?: number): Date;
42
+ protected updateLastSyncTime(): void;
33
43
  protected saveLastSyncTime(): void;
34
44
  protected getUserSettings(): import("../../../settings").UserSettingsService;
35
45
  protected getRouteListService(): IRouteList;
@@ -38,3 +48,4 @@ export declare class KomootSyncProvider extends IncyclistService implements IRou
38
48
  protected getKomootAppConnection(): KomootAppConnection;
39
49
  protected getAppsService(): import("../../../apps").AppsService;
40
50
  }
51
+ export {};
@@ -56,6 +56,14 @@ const geo_1 = require("../../../utils/geo");
56
56
  const route_1 = require("../../base/model/route");
57
57
  const factory_1 = require("../factory");
58
58
  const PAGE_LIMIT = 50;
59
+ const SPORTS_FILTER = 'racebike';
60
+ const sportsOptions = {
61
+ touringbicycle: 'Cycling',
62
+ mtb: 'Mountain biking',
63
+ racebike: 'Road cycling',
64
+ mtb_easy: 'Gravel riding',
65
+ mtb_advancde: 'Enduro mountain biking'
66
+ };
59
67
  let KomootSyncProvider = (() => {
60
68
  var _a;
61
69
  let _classSuper = service_1.IncyclistService;
@@ -127,32 +135,48 @@ let KomootSyncProvider = (() => {
127
135
  return [];
128
136
  }
129
137
  this.logEvent({ message: 'start komoot route sync' });
138
+ const { tours, done } = yield this.loadToursFromApi();
139
+ if (done) {
140
+ const { added, updated, deleted } = this.checkTours(tours);
141
+ this.updateLastSyncTime();
142
+ this.observer.emit('done');
143
+ this.logEvent({ message: 'komoot route sync finished', added: added === null || added === void 0 ? void 0 : added.length, updated: updated === null || updated === void 0 ? void 0 : updated.length, deleted: deleted.length });
144
+ return added;
145
+ }
146
+ else {
147
+ this.observer.emit('done');
148
+ this.logEvent({ message: 'komoot route sync stopped' });
149
+ return [];
150
+ }
151
+ });
152
+ }
153
+ emitUpdates(added, updated, deleted) {
154
+ if (added.length > 0) {
155
+ this.emitAdded(added);
156
+ }
157
+ if (updated.length > 0) {
158
+ this.emitUpdated(updated);
159
+ }
160
+ if (deleted.length > 0) {
161
+ this.emitDeleted(deleted);
162
+ }
163
+ }
164
+ loadToursFromApi() {
165
+ return __awaiter(this, void 0, void 0, function* () {
130
166
  const api = this.getKomootApi();
131
167
  let page = 0;
132
168
  let done = false;
133
169
  const tours = [];
134
- let cntAdded = 0;
135
- let cntUpdated = 0;
136
170
  while (!done && !this.stopRequested) {
137
171
  try {
138
- const filters = { lastUpdateAfter: this.getLastSyncTime(), sport: this.getSportFilter(), after: this.getCreatedFilter() };
172
+ const filters = { sport: this.getSportFilter(), after: this.getCreatedFilter() };
139
173
  const pageTours = yield api.getTours({ type: 'tour_planned', page, limit: PAGE_LIMIT }, filters);
140
- if ((pageTours === null || pageTours === void 0 ? void 0 : pageTours.length) > 0) {
141
- const { added, updated } = this.checkTours(pageTours);
142
- if (added.length > 0) {
143
- cntAdded += added.length;
144
- this.emitAdded(added);
145
- tours.push(...added);
146
- }
147
- if (updated.length > 0) {
148
- cntUpdated += updated.length;
149
- this.emitUpdated(updated);
150
- }
174
+ if (pageTours.length > 0) {
175
+ tours.push(...pageTours);
151
176
  page++;
152
177
  }
153
- else {
178
+ else
154
179
  done = true;
155
- }
156
180
  yield (0, utils_1.sleep)(100);
157
181
  }
158
182
  catch (err) {
@@ -160,18 +184,13 @@ let KomootSyncProvider = (() => {
160
184
  done = true;
161
185
  }
162
186
  }
163
- if (done) {
164
- this.lastSyncTS = Date.now();
165
- this.saveLastSyncTime();
166
- }
167
- this.logEvent({ message: 'komoot route sync finished', added: cntAdded, updated: cntUpdated });
168
- this.observer.emit('done');
169
- return tours;
187
+ return { tours, done };
170
188
  });
171
189
  }
172
190
  checkTours(tours) {
173
- var _b, _c, _d;
174
- const routes = (_d = (_c = (_b = this.getRouteListService()) === null || _b === void 0 ? void 0 : _b.searchRepo({ source: 'komoot' })) === null || _c === void 0 ? void 0 : _c.routes) !== null && _d !== void 0 ? _d : [];
191
+ var _b, _c;
192
+ const routes = (_c = (_b = this.getRouteListService()) === null || _b === void 0 ? void 0 : _b.getAllAppRoutes('komoot')) !== null && _c !== void 0 ? _c : [];
193
+ const matched = routes.map(r => ({ id: r.id, matched: false }));
175
194
  const added = [];
176
195
  const updated = [];
177
196
  tours.forEach(tour => {
@@ -181,7 +200,15 @@ let KomootSyncProvider = (() => {
181
200
  if (!route) {
182
201
  added.push(tour);
183
202
  }
203
+ else if (route.isDeleted) {
204
+ const matchRecord = matched.find(r => r.id === id);
205
+ if (matchRecord)
206
+ matchRecord.matched = true;
207
+ }
184
208
  else {
209
+ const matchRecord = matched.find(r => r.id === id);
210
+ if (matchRecord)
211
+ matchRecord.matched = true;
185
212
  const routeTS = (_b = route.tsLastChange) !== null && _b !== void 0 ? _b : 0;
186
213
  const tourTS = (new Date(tour.changed_at)).valueOf();
187
214
  if (tourTS > routeTS) {
@@ -190,7 +217,10 @@ let KomootSyncProvider = (() => {
190
217
  }
191
218
  }
192
219
  });
193
- return { added, updated };
220
+ const unmatched = matched.filter(r => !r.matched);
221
+ const deleted = unmatched.map(r => r.id);
222
+ this.emitUpdates(added, updated, deleted);
223
+ return { added, updated, deleted };
194
224
  }
195
225
  buildRouteInfo(tour) {
196
226
  var _b;
@@ -203,7 +233,7 @@ let KomootSyncProvider = (() => {
203
233
  distance: tour.distance,
204
234
  category: 'external',
205
235
  source: 'komoot',
206
- previewUrl: (_b = tour.map_image_preview) === null || _b === void 0 ? void 0 : _b.src,
236
+ previewUrl: (_b = tour.vector_map_image_preview) === null || _b === void 0 ? void 0 : _b.src,
207
237
  tsLastChange: (new Date(tour.changed_at)).valueOf()
208
238
  };
209
239
  return info;
@@ -288,6 +318,9 @@ let KomootSyncProvider = (() => {
288
318
  emitUpdated(routes) {
289
319
  this.observer.emit('updated', routes);
290
320
  }
321
+ emitDeleted(routes) {
322
+ this.observer.emit('deleted', routes);
323
+ }
291
324
  getLastSyncTime() {
292
325
  var _b;
293
326
  const lastSyncTS = (_b = this.lastSyncTS) !== null && _b !== void 0 ? _b : 0;
@@ -295,11 +328,19 @@ let KomootSyncProvider = (() => {
295
328
  return this.lastSyncTS > 0 ? new Date(this.lastSyncTS) : undefined;
296
329
  }
297
330
  getSportFilter() {
298
- return 'racebike';
331
+ return SPORTS_FILTER;
332
+ }
333
+ getAvailableSports() {
334
+ const keys = Object.keys(sportsOptions);
335
+ return keys.map(sport => sportsOptions[sport]);
299
336
  }
300
337
  getCreatedFilter(days = 365) {
301
338
  return new Date(Date.now() - days * 24 * 60 * 60 * 1000);
302
339
  }
340
+ updateLastSyncTime() {
341
+ this.lastSyncTS = Date.now();
342
+ this.saveLastSyncTime();
343
+ }
303
344
  saveLastSyncTime() {
304
345
  if (!this.lastSyncTS)
305
346
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.4.18",
3
+ "version": "1.4.20",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.26"
6
6
  },