incyclist-services 1.8.0 → 1.8.1

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.
@@ -101,6 +101,7 @@ let ActivityRideService = (() => {
101
101
  isSaveDone;
102
102
  isDonateShown;
103
103
  isSummaryShown;
104
+ stopPromise;
104
105
  statsCalculator;
105
106
  durationCalculator;
106
107
  deviceDataHandler = this.onDeviceData.bind(this);
@@ -183,6 +184,17 @@ let ActivityRideService = (() => {
183
184
  }
184
185
  }
185
186
  async stop() {
187
+ if (this.stopPromise !== undefined)
188
+ return this.stopPromise;
189
+ this.stopPromise = this.doStop();
190
+ try {
191
+ await this.stopPromise;
192
+ }
193
+ finally {
194
+ delete this.stopPromise;
195
+ }
196
+ }
197
+ async doStop() {
186
198
  this.stopWorker();
187
199
  if (!this.observer || !this.activity)
188
200
  return;
@@ -206,7 +218,9 @@ let ActivityRideService = (() => {
206
218
  delete this.observer;
207
219
  }
208
220
  async cleanup() {
209
- if (this.state !== 'idle' && this.state !== 'completed')
221
+ if (this.stopPromise !== undefined)
222
+ await this.stopPromise;
223
+ else if (this.state !== 'idle' && this.state !== 'completed')
210
224
  await this.stop();
211
225
  delete this.activity;
212
226
  }
@@ -1161,7 +1175,7 @@ let ActivityRideService = (() => {
1161
1175
  getTotalDistance() {
1162
1176
  try {
1163
1177
  const route = this.current.route;
1164
- if (!route)
1178
+ if (!route || !this.activity)
1165
1179
  return 0;
1166
1180
  const isLoop = (0, route_1.checkIsLoop)(route);
1167
1181
  const totalRouteDistance = this.current?.endPos ?? route.distance ?? route.points?.at(-1)?.routeDistance;
@@ -1181,7 +1195,7 @@ let ActivityRideService = (() => {
1181
1195
  getTotalElevation() {
1182
1196
  try {
1183
1197
  const route = this.current.route;
1184
- if (!route)
1198
+ if (!route || !this.activity)
1185
1199
  return 0;
1186
1200
  const isLoop = (0, route_1.checkIsLoop)(route);
1187
1201
  const totalRouteDistance = this.current?.endPos ?? route.distance ?? route.points?.at(-1)?.routeDistance;
@@ -87,6 +87,8 @@ let RouteDownloadService = (() => {
87
87
  const observer = new types_1.DownloadObserver(this._download(route));
88
88
  this.downloads.push({ route, observer });
89
89
  this.emit('download-started', route, observer);
90
+ observer.once('done', () => { this.removeDownload(route); });
91
+ observer.once('error', () => { this.removeDownload(route); });
90
92
  return observer;
91
93
  }
92
94
  stopDownload(route) {
@@ -97,6 +99,12 @@ let RouteDownloadService = (() => {
97
99
  info.observer.stop();
98
100
  this.downloads.splice(idx, 1);
99
101
  }
102
+ removeDownload(route) {
103
+ const idx = this.downloads.findIndex(d => d.route.description.id === route.description.id);
104
+ if (idx === -1)
105
+ return;
106
+ this.downloads.splice(idx, 1);
107
+ }
100
108
  async deleteIncompleteFile(route, videoDir) {
101
109
  const { file } = await this.getDownloadFileName(route, videoDir);
102
110
  if (file) {
@@ -739,6 +739,7 @@ let RouteCard = (() => {
739
739
  try {
740
740
  (0, service_1.getRouteList)().logEvent({ message: 'download stopped', route: this.route?.description?.title, });
741
741
  this.getRouteDownload().stopDownload(this.route);
742
+ this.downloadObserver?.reset();
742
743
  if (immediate) {
743
744
  delete this.downloadObserver;
744
745
  }
@@ -784,7 +785,7 @@ let RouteCard = (() => {
784
785
  this.route.details.video.url = url;
785
786
  this.updateRoute(this.route);
786
787
  (0, sleep_1.sleep)(5).then(() => {
787
- this.downloadObserver.reset();
788
+ this.downloadObserver?.reset();
788
789
  delete this.downloadObserver;
789
790
  });
790
791
  if (this.list.getId() !== 'myRoutes') {
@@ -812,9 +813,9 @@ let RouteCard = (() => {
812
813
  async onDownloadError(err) {
813
814
  try {
814
815
  (0, service_1.getRouteList)().logEvent({ message: 'download failed', reason: err.message, route: this.route?.description?.title });
815
- this.downloadObserver.stop();
816
+ this.downloadObserver?.stop();
816
817
  (0, sleep_1.sleep)(5).then(() => {
817
- this.downloadObserver.reset();
818
+ this.downloadObserver?.reset();
818
819
  delete this.downloadObserver;
819
820
  });
820
821
  }
@@ -472,22 +472,37 @@ let RouteListService = (() => {
472
472
  this.logError(err, 'stopSearch');
473
473
  }
474
474
  }
475
- getFilterOptions() {
475
+ getFilterOptions(filters = {}) {
476
476
  let countries = [];
477
477
  let contentTypes = [];
478
478
  let routeTypes = [];
479
479
  let routeSources = [];
480
480
  try {
481
- countries = this.getFilterCountries();
482
- contentTypes = this.getFilterContentTypes();
483
- routeTypes = this.getFilterRouteTypes();
484
- routeSources = this.getFilterRouteSources();
481
+ countries = this.getFilterCountries(filters);
482
+ contentTypes = this.getFilterContentTypes(filters);
483
+ routeTypes = this.getFilterRouteTypes(filters);
484
+ routeSources = this.getFilterRouteSources(filters);
485
485
  }
486
486
  catch (err) {
487
487
  this.logError(err, 'getFilterOption');
488
488
  }
489
489
  return { countries, contentTypes, routeTypes, routeSources };
490
490
  }
491
+ getRoutesForFilterOptions(filters, excludeKey) {
492
+ const activeFilters = { ...filters, [excludeKey]: undefined };
493
+ let routes = this.getAllSearchCards().map(c => c.getDisplayProperties());
494
+ if (!activeFilters.includeDeleted) {
495
+ routes = routes.filter(r => !r?.isDeleted);
496
+ }
497
+ routes = this.applyTitleFilter(activeFilters, routes);
498
+ routes = this.applyDistanceFilter(activeFilters, routes);
499
+ routes = this.applyElevationFilter(activeFilters, routes);
500
+ routes = this.applyCountryFilter(activeFilters, routes);
501
+ routes = this.applyContentTypeFilter(activeFilters, routes);
502
+ routes = this.applyRouteTypeFilter(activeFilters, routes);
503
+ routes = this.applySourceFilter(activeFilters, routes);
504
+ return routes;
505
+ }
491
506
  onResize() {
492
507
  try {
493
508
  this.resetCards();
@@ -861,16 +876,28 @@ let RouteListService = (() => {
861
876
  this.emitLists('updated');
862
877
  return card;
863
878
  }
864
- getFilterContentTypes() {
865
- return ['GPX', 'Video'];
879
+ getFilterContentTypes(filters = {}) {
880
+ const options = [];
881
+ const routes = this.getRoutesForFilterOptions(filters, 'contentType');
882
+ if (routes.some(r => r.hasVideo))
883
+ options.push('Video');
884
+ if (routes.some(r => !r.hasVideo))
885
+ options.push('GPX');
886
+ return options;
866
887
  }
867
- getFilterRouteTypes() {
868
- return ['Loop', 'Point to Point'];
888
+ getFilterRouteTypes(filters = {}) {
889
+ const options = [];
890
+ const routes = this.getRoutesForFilterOptions(filters, 'routeType');
891
+ if (routes.some(r => r.isLoop))
892
+ options.push('Loop');
893
+ if (routes.some(r => !r.isLoop))
894
+ options.push('Point to Point');
895
+ return options;
869
896
  }
870
- getFilterRouteSources() {
897
+ getFilterRouteSources(filters = {}) {
871
898
  const options = ['Local', 'Incyclist'];
872
- this.getVisibleRoutes().forEach(r => {
873
- const source = r.description.source;
899
+ this.getRoutesForFilterOptions(filters, 'routeSource').forEach(r => {
900
+ const source = r.source;
874
901
  if (!source)
875
902
  return;
876
903
  const option = this.getAppsService().getName(source);
@@ -880,11 +907,11 @@ let RouteListService = (() => {
880
907
  });
881
908
  return options;
882
909
  }
883
- getFilterCountries() {
910
+ getFilterCountries(filters = {}) {
884
911
  try {
885
912
  const countries = [];
886
- this.getVisibleRoutes().forEach(r => {
887
- const iso = r.description.country;
913
+ this.getRoutesForFilterOptions(filters, 'country').forEach(r => {
914
+ const iso = r.country;
888
915
  let country = 'Unknown';
889
916
  if (iso)
890
917
  country = (0, countries_1.getCountries)().getCountryFromIso(iso);
@@ -95,6 +95,7 @@ let ActivityRideService = (() => {
95
95
  isSaveDone;
96
96
  isDonateShown;
97
97
  isSummaryShown;
98
+ stopPromise;
98
99
  statsCalculator;
99
100
  durationCalculator;
100
101
  deviceDataHandler = this.onDeviceData.bind(this);
@@ -177,6 +178,17 @@ let ActivityRideService = (() => {
177
178
  }
178
179
  }
179
180
  async stop() {
181
+ if (this.stopPromise !== undefined)
182
+ return this.stopPromise;
183
+ this.stopPromise = this.doStop();
184
+ try {
185
+ await this.stopPromise;
186
+ }
187
+ finally {
188
+ delete this.stopPromise;
189
+ }
190
+ }
191
+ async doStop() {
180
192
  this.stopWorker();
181
193
  if (!this.observer || !this.activity)
182
194
  return;
@@ -200,7 +212,9 @@ let ActivityRideService = (() => {
200
212
  delete this.observer;
201
213
  }
202
214
  async cleanup() {
203
- if (this.state !== 'idle' && this.state !== 'completed')
215
+ if (this.stopPromise !== undefined)
216
+ await this.stopPromise;
217
+ else if (this.state !== 'idle' && this.state !== 'completed')
204
218
  await this.stop();
205
219
  delete this.activity;
206
220
  }
@@ -1155,7 +1169,7 @@ let ActivityRideService = (() => {
1155
1169
  getTotalDistance() {
1156
1170
  try {
1157
1171
  const route = this.current.route;
1158
- if (!route)
1172
+ if (!route || !this.activity)
1159
1173
  return 0;
1160
1174
  const isLoop = checkIsLoop(route);
1161
1175
  const totalRouteDistance = this.current?.endPos ?? route.distance ?? route.points?.at(-1)?.routeDistance;
@@ -1175,7 +1189,7 @@ let ActivityRideService = (() => {
1175
1189
  getTotalElevation() {
1176
1190
  try {
1177
1191
  const route = this.current.route;
1178
- if (!route)
1192
+ if (!route || !this.activity)
1179
1193
  return 0;
1180
1194
  const isLoop = checkIsLoop(route);
1181
1195
  const totalRouteDistance = this.current?.endPos ?? route.distance ?? route.points?.at(-1)?.routeDistance;
@@ -84,6 +84,8 @@ let RouteDownloadService = (() => {
84
84
  const observer = new DownloadObserver(this._download(route));
85
85
  this.downloads.push({ route, observer });
86
86
  this.emit('download-started', route, observer);
87
+ observer.once('done', () => { this.removeDownload(route); });
88
+ observer.once('error', () => { this.removeDownload(route); });
87
89
  return observer;
88
90
  }
89
91
  stopDownload(route) {
@@ -94,6 +96,12 @@ let RouteDownloadService = (() => {
94
96
  info.observer.stop();
95
97
  this.downloads.splice(idx, 1);
96
98
  }
99
+ removeDownload(route) {
100
+ const idx = this.downloads.findIndex(d => d.route.description.id === route.description.id);
101
+ if (idx === -1)
102
+ return;
103
+ this.downloads.splice(idx, 1);
104
+ }
97
105
  async deleteIncompleteFile(route, videoDir) {
98
106
  const { file } = await this.getDownloadFileName(route, videoDir);
99
107
  if (file) {
@@ -736,6 +736,7 @@ let RouteCard = (() => {
736
736
  try {
737
737
  getRouteList().logEvent({ message: 'download stopped', route: this.route?.description?.title, });
738
738
  this.getRouteDownload().stopDownload(this.route);
739
+ this.downloadObserver?.reset();
739
740
  if (immediate) {
740
741
  delete this.downloadObserver;
741
742
  }
@@ -781,7 +782,7 @@ let RouteCard = (() => {
781
782
  this.route.details.video.url = url;
782
783
  this.updateRoute(this.route);
783
784
  sleep(5).then(() => {
784
- this.downloadObserver.reset();
785
+ this.downloadObserver?.reset();
785
786
  delete this.downloadObserver;
786
787
  });
787
788
  if (this.list.getId() !== 'myRoutes') {
@@ -809,9 +810,9 @@ let RouteCard = (() => {
809
810
  async onDownloadError(err) {
810
811
  try {
811
812
  getRouteList().logEvent({ message: 'download failed', reason: err.message, route: this.route?.description?.title });
812
- this.downloadObserver.stop();
813
+ this.downloadObserver?.stop();
813
814
  sleep(5).then(() => {
814
- this.downloadObserver.reset();
815
+ this.downloadObserver?.reset();
815
816
  delete this.downloadObserver;
816
817
  });
817
818
  }
@@ -466,22 +466,37 @@ let RouteListService = (() => {
466
466
  this.logError(err, 'stopSearch');
467
467
  }
468
468
  }
469
- getFilterOptions() {
469
+ getFilterOptions(filters = {}) {
470
470
  let countries = [];
471
471
  let contentTypes = [];
472
472
  let routeTypes = [];
473
473
  let routeSources = [];
474
474
  try {
475
- countries = this.getFilterCountries();
476
- contentTypes = this.getFilterContentTypes();
477
- routeTypes = this.getFilterRouteTypes();
478
- routeSources = this.getFilterRouteSources();
475
+ countries = this.getFilterCountries(filters);
476
+ contentTypes = this.getFilterContentTypes(filters);
477
+ routeTypes = this.getFilterRouteTypes(filters);
478
+ routeSources = this.getFilterRouteSources(filters);
479
479
  }
480
480
  catch (err) {
481
481
  this.logError(err, 'getFilterOption');
482
482
  }
483
483
  return { countries, contentTypes, routeTypes, routeSources };
484
484
  }
485
+ getRoutesForFilterOptions(filters, excludeKey) {
486
+ const activeFilters = { ...filters, [excludeKey]: undefined };
487
+ let routes = this.getAllSearchCards().map(c => c.getDisplayProperties());
488
+ if (!activeFilters.includeDeleted) {
489
+ routes = routes.filter(r => !r?.isDeleted);
490
+ }
491
+ routes = this.applyTitleFilter(activeFilters, routes);
492
+ routes = this.applyDistanceFilter(activeFilters, routes);
493
+ routes = this.applyElevationFilter(activeFilters, routes);
494
+ routes = this.applyCountryFilter(activeFilters, routes);
495
+ routes = this.applyContentTypeFilter(activeFilters, routes);
496
+ routes = this.applyRouteTypeFilter(activeFilters, routes);
497
+ routes = this.applySourceFilter(activeFilters, routes);
498
+ return routes;
499
+ }
485
500
  onResize() {
486
501
  try {
487
502
  this.resetCards();
@@ -855,16 +870,28 @@ let RouteListService = (() => {
855
870
  this.emitLists('updated');
856
871
  return card;
857
872
  }
858
- getFilterContentTypes() {
859
- return ['GPX', 'Video'];
873
+ getFilterContentTypes(filters = {}) {
874
+ const options = [];
875
+ const routes = this.getRoutesForFilterOptions(filters, 'contentType');
876
+ if (routes.some(r => r.hasVideo))
877
+ options.push('Video');
878
+ if (routes.some(r => !r.hasVideo))
879
+ options.push('GPX');
880
+ return options;
860
881
  }
861
- getFilterRouteTypes() {
862
- return ['Loop', 'Point to Point'];
882
+ getFilterRouteTypes(filters = {}) {
883
+ const options = [];
884
+ const routes = this.getRoutesForFilterOptions(filters, 'routeType');
885
+ if (routes.some(r => r.isLoop))
886
+ options.push('Loop');
887
+ if (routes.some(r => !r.isLoop))
888
+ options.push('Point to Point');
889
+ return options;
863
890
  }
864
- getFilterRouteSources() {
891
+ getFilterRouteSources(filters = {}) {
865
892
  const options = ['Local', 'Incyclist'];
866
- this.getVisibleRoutes().forEach(r => {
867
- const source = r.description.source;
893
+ this.getRoutesForFilterOptions(filters, 'routeSource').forEach(r => {
894
+ const source = r.source;
868
895
  if (!source)
869
896
  return;
870
897
  const option = this.getAppsService().getName(source);
@@ -874,11 +901,11 @@ let RouteListService = (() => {
874
901
  });
875
902
  return options;
876
903
  }
877
- getFilterCountries() {
904
+ getFilterCountries(filters = {}) {
878
905
  try {
879
906
  const countries = [];
880
- this.getVisibleRoutes().forEach(r => {
881
- const iso = r.description.country;
907
+ this.getRoutesForFilterOptions(filters, 'country').forEach(r => {
908
+ const iso = r.country;
882
909
  let country = 'Unknown';
883
910
  if (iso)
884
911
  country = getCountries().getCountryFromIso(iso);
@@ -32,6 +32,7 @@ export declare class ActivityRideService extends IncyclistService {
32
32
  protected isSaveDone: boolean;
33
33
  protected isDonateShown: boolean;
34
34
  protected isSummaryShown: boolean;
35
+ protected stopPromise: Promise<void>;
35
36
  protected statsCalculator: ActivityStatsCalculator;
36
37
  protected durationCalculator: ActivityDuration;
37
38
  protected deviceDataHandler: any;
@@ -63,6 +64,7 @@ export declare class ActivityRideService extends IncyclistService {
63
64
  init(id?: string): Observer;
64
65
  start(): void;
65
66
  stop(): Promise<void>;
67
+ protected doStop(): Promise<void>;
66
68
  cleanup(): Promise<void>;
67
69
  pause(autoResume?: boolean): void;
68
70
  resume(requester?: 'user' | 'system'): void;
@@ -18,6 +18,7 @@ export declare class RouteDownloadService extends IncyclistService {
18
18
  }>;
19
19
  download(route: Route): DownloadObserver;
20
20
  stopDownload(route: Route): void;
21
+ protected removeDownload(route: Route): void;
21
22
  protected deleteIncompleteFile(route: Route, videoDir: string): Promise<void>;
22
23
  protected getObserver(route: Route): DownloadObserver;
23
24
  protected _download(route: Route): Promise<void>;
@@ -93,7 +93,8 @@ export declare class RouteListService extends IncyclistService implements IRoute
93
93
  private applyDistanceFilter;
94
94
  private applyTitleFilter;
95
95
  stopSearch(): void;
96
- getFilterOptions(): SearchFilterOptions;
96
+ getFilterOptions(filters?: SearchFilter): SearchFilterOptions;
97
+ private getRoutesForFilterOptions;
97
98
  onResize(): void;
98
99
  onCarouselInitialized(list: CardList<Route>, item: any, itemsInSlide: any): void;
99
100
  onCarouselUpdated(list: any): void;
@@ -133,10 +134,10 @@ export declare class RouteListService extends IncyclistService implements IRoute
133
134
  getCard(id: string): RouteCard;
134
135
  addCardAgain(card: RouteCard): void;
135
136
  protected addImportCard(file: FileInfo): ActiveImportCard;
136
- protected getFilterContentTypes(): Array<string>;
137
- protected getFilterRouteTypes(): Array<string>;
138
- protected getFilterRouteSources(): Array<string>;
139
- protected getFilterCountries(): Array<string>;
137
+ protected getFilterContentTypes(filters?: SearchFilter): Array<string>;
138
+ protected getFilterRouteTypes(filters?: SearchFilter): Array<string>;
139
+ protected getFilterRouteSources(filters?: SearchFilter): Array<string>;
140
+ protected getFilterCountries(filters?: SearchFilter): Array<string>;
140
141
  existsBySourceUri(uri: string): boolean;
141
142
  getBySourceUri(uri: string): Route;
142
143
  addRoute(route: Route, source?: 'user' | 'system' | 'import'): void;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.27"
6
6
  },
7
7
  "dependencies": {
8
8
  "@garmin/fitsdk": "^21.213.0",
9
9
  "axios": "^1.19.0",
10
- "incyclist-devices": "^3.0.37",
10
+ "incyclist-devices": "^3.0.38",
11
11
  "promise.any": "^2.0.6",
12
12
  "semver": "^7.7.4",
13
13
  "tcx-builder": "^1.1.1",
@@ -21,14 +21,14 @@
21
21
  "@types/jest": "^30.0.0",
22
22
  "@types/node": "^26.2.0",
23
23
  "@typescript-eslint/eslint-plugin": "^8.65.0",
24
- "@typescript-eslint/parser": "^8.67.0",
24
+ "@typescript-eslint/parser": "^8.69.0",
25
25
  "dotenv": "^17.4.2",
26
26
  "eslint": "^10.9.1",
27
27
  "formdata-node": "^6.0.3",
28
- "globals": "^17.9.0",
28
+ "globals": "^17.12.0",
29
29
  "jest": "^30.5.0",
30
30
  "jsdoc": "^4.0.5",
31
- "ts-jest": "^29.4.9",
31
+ "ts-jest": "^29.4.12",
32
32
  "typedoc": "^0.28.20",
33
33
  "typedoc-plugin-markdown": "^4.12.0",
34
34
  "typedoc-plugin-no-inherit": "^1.6.1",