diff --git a/package-lock.json b/package-lock.json index bf9cf289..56803a9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,23 @@ "requires": { "@angular-devkit/core": "13.3.9", "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular-devkit/build-angular": { @@ -240,6 +257,23 @@ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "dev": true }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -292,6 +326,23 @@ "requires": { "@angular-devkit/architect": "0.1303.9", "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular-devkit/core": { @@ -319,6 +370,21 @@ "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, @@ -340,6 +406,21 @@ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", "dev": true + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, @@ -2594,6 +2675,21 @@ "requires": { "ajv": "^8.0.0" } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, @@ -2970,6 +3066,23 @@ "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } } } @@ -3774,6 +3887,23 @@ "yallist": "^4.0.0" } }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -3841,6 +3971,23 @@ "yallist": "^4.0.0" } }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -15378,18 +15525,11 @@ } }, "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } + "tslib": "^2.1.0" } }, "rxjs-for-await": { @@ -16651,6 +16791,15 @@ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -16741,6 +16890,12 @@ "requires": { "has-flag": "^3.0.0" } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, diff --git a/package.json b/package.json index 24598700..bbcc5dc1 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "ngx-markdown": "13.1.0", "ngx-moment": "6.0.2", "opening_hours": "3.8.0", - "rxjs": "6.6.7", + "rxjs": "7.8.0", "swiper": "8.4.5", "tslib": "2.4.1", "zone.js": "0.12.0" diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e3635b8c..a0e23c9a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. diff --git a/src/app/modules/auth/default-auth.service.spec.ts b/src/app/modules/auth/default-auth.service.spec.ts index 9579ebe9..693c78da 100644 --- a/src/app/modules/auth/default-auth.service.spec.ts +++ b/src/app/modules/auth/default-auth.service.spec.ts @@ -19,10 +19,11 @@ import {DefaultAuthService} from './default-auth.service'; import {Browser} from 'ionic-appauth'; import {nowInSeconds, Requestor, StorageBackend} from '@openid/appauth'; import {TranslateService} from '@ngx-translate/core'; -import {LoggerConfig, LoggerModule, NGXLogger} from 'ngx-logger'; +import {LoggerConfig, LoggerModule, NGXLogger, NgxLoggerLevel} from 'ngx-logger'; import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider'; import {HttpClientModule} from '@angular/common/http'; import {IonicStorage} from 'ionic-appauth/lib'; +import {RouterModule} from '@angular/router'; describe('AuthService', () => { let defaultAuthService: DefaultAuthService; @@ -34,7 +35,11 @@ describe('AuthService', () => { storageBackendSpy = jasmine.createSpyObj('StorageBackend', ['getItem']); TestBed.configureTestingModule({ - imports: [HttpClientModule, LoggerModule], + imports: [ + HttpClientModule, + LoggerModule.forRoot({level: NgxLoggerLevel.TRACE}), + RouterModule.forRoot([]), + ], providers: [ NGXLogger, StAppsWebHttpClient, diff --git a/src/app/modules/auth/ng-http.service.ts b/src/app/modules/auth/ng-http.service.ts index 73032f69..42dceb5c 100644 --- a/src/app/modules/auth/ng-http.service.ts +++ b/src/app/modules/auth/ng-http.service.ts @@ -1,8 +1,23 @@ +/* + * Copyright (C) 2023 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + import {Injectable} from '@angular/core'; import {Requestor} from '@openid/appauth'; import {HttpClient, HttpHeaders} from '@angular/common/http'; import {XhrSettings} from 'ionic-appauth/lib/cordova'; -import {Observable} from 'rxjs'; +import {firstValueFrom, Observable} from 'rxjs'; @Injectable({ providedIn: 'root', @@ -40,7 +55,7 @@ export class NgHttpService implements Requestor { break; } - return observable.toPromise(); + return firstValueFrom(observable); } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/app/modules/calendar/schedule.provider.ts b/src/app/modules/calendar/schedule.provider.ts index 5ad1ff38..46e18ea7 100644 --- a/src/app/modules/calendar/schedule.provider.ts +++ b/src/app/modules/calendar/schedule.provider.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -215,14 +215,12 @@ export class ScheduleProvider implements OnDestroy { if (from || to) { const bounds: Bounds = {}; if (from) { - console.log(from); bounds.lowerBound = { limit: from, mode: 'inclusive', }; } if (to) { - console.log(to); bounds.upperBound = { limit: to, mode: 'inclusive', diff --git a/src/app/modules/dashboard/sections/favorites-section/favorites-section.component.ts b/src/app/modules/dashboard/sections/favorites-section/favorites-section.component.ts index 6ab7784d..7e885c73 100644 --- a/src/app/modules/dashboard/sections/favorites-section/favorites-section.component.ts +++ b/src/app/modules/dashboard/sections/favorites-section/favorites-section.component.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. diff --git a/src/app/modules/dashboard/sections/mensa-section/mensa-section.component.ts b/src/app/modules/dashboard/sections/mensa-section/mensa-section.component.ts index e0f1e88d..f223e978 100644 --- a/src/app/modules/dashboard/sections/mensa-section/mensa-section.component.ts +++ b/src/app/modules/dashboard/sections/mensa-section/mensa-section.component.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. diff --git a/src/app/modules/data/data.provider.spec.ts b/src/app/modules/data/data.provider.spec.ts index 2ada5648..d7cfadfb 100644 --- a/src/app/modules/data/data.provider.spec.ts +++ b/src/app/modules/data/data.provider.spec.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -31,6 +31,8 @@ import {StorageProvider} from '../storage/storage.provider'; import {DataModule} from './data.module'; import {DataProvider, DataScope} from './data.provider'; import {StAppsWebHttpClient} from './stapps-web-http-client.provider'; +import {LoggerModule, NgxLoggerLevel} from 'ngx-logger'; +import {RouterModule} from '@angular/router'; describe('DataProvider', () => { let dataProvider: DataProvider; @@ -82,7 +84,7 @@ describe('DataProvider', () => { beforeEach(async () => { TestBed.configureTestingModule({ - imports: [DataModule], + imports: [DataModule, LoggerModule.forRoot({level: NgxLoggerLevel.TRACE}), RouterModule.forRoot([])], providers: [DataProvider, StAppsWebHttpClient], }); storageProvider = TestBed.inject(StorageProvider); diff --git a/src/app/modules/data/detail/data-detail.component.spec.ts b/src/app/modules/data/detail/data-detail.component.spec.ts index d6435257..c471b7a3 100644 --- a/src/app/modules/data/detail/data-detail.component.spec.ts +++ b/src/app/modules/data/detail/data-detail.component.spec.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -27,6 +27,7 @@ import {DataDetailComponent} from './data-detail.component'; import {By} from '@angular/platform-browser'; import {Observable, of} from 'rxjs'; import {StorageProvider} from '../../storage/storage.provider'; +import {LoggerModule, NgxLoggerLevel} from 'ngx-logger'; const translations: any = {data: {detail: {TITLE: 'Foo'}}}; @@ -70,6 +71,7 @@ describe('DataDetailComponent', () => { TranslateModule.forRoot({ loader: {provide: TranslateLoader, useClass: TranslateFakeLoader}, }), + LoggerModule.forRoot({level: NgxLoggerLevel.TRACE}), ], providers: [ { diff --git a/src/app/modules/data/list/search-page.component.ts b/src/app/modules/data/list/search-page.component.ts index 614a5868..138557ca 100644 --- a/src/app/modules/data/list/search-page.component.ts +++ b/src/app/modules/data/list/search-page.component.ts @@ -229,13 +229,7 @@ export class SearchPageComponent implements OnInit, OnDestroy { })(); } } catch (error) { - const alert: HTMLIonAlertElement = await this.alertController.create({ - buttons: ['Dismiss'], - header: 'Error', - subHeader: (error as Error).message, - }); - - await alert.present(); + this.logger.error(error); } finally { this.loading = false; } diff --git a/src/app/modules/data/stapps-web-http-client.provider.ts b/src/app/modules/data/stapps-web-http-client.provider.ts index b34f9eb4..17240f21 100644 --- a/src/app/modules/data/stapps-web-http-client.provider.ts +++ b/src/app/modules/data/stapps-web-http-client.provider.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -15,6 +15,17 @@ import {HttpClient, HttpResponse} from '@angular/common/http'; import {Injectable} from '@angular/core'; import {HttpClientInterface, HttpClientRequest} from '@openstapps/api/lib/http-client-interface'; +import {map, retry} from 'rxjs/operators'; +import {lastValueFrom, Observable} from 'rxjs'; +import {InternetConnectionService} from '../../util/internet-connection.service'; + +type HttpRequestFunctions = InstanceType['request']; +type HttpRequestFunction> = Extract< + HttpRequestFunctions, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (...parameters: any[]) => T +>; +type HttpRequestParameters> = Parameters>; /** * HttpClient that is based on the Angular HttpClient (@TODO: move it to provider or independent package) @@ -23,9 +34,11 @@ import {HttpClientInterface, HttpClientRequest} from '@openstapps/api/lib/http-c export class StAppsWebHttpClient implements HttpClientInterface { /** * - * @param http TODO */ - constructor(private readonly http: HttpClient) {} + constructor( + private readonly http: HttpClient, + private readonly connectionService: InternetConnectionService, + ) {} /** * Make a request @@ -33,42 +46,30 @@ export class StAppsWebHttpClient implements HttpClientInterface { * @param requestConfig Configuration of the request */ async request(requestConfig: HttpClientRequest): Promise> { - const options: { - /** - * TODO - */ - [key: string]: unknown; - /** - * TODO - */ - observe: 'response'; - } = { - body: {}, - observe: 'response', - responseType: 'json', - }; + const request: HttpRequestParameters>> = [ + requestConfig.method || 'GET', + requestConfig.url.toString(), + { + body: (requestConfig.body || {}) as TYPE_OF_BODY, + headers: requestConfig.headers, + observe: 'response', + responseType: 'json', + }, + ]; + // TODO: cache requests by hashing the parameters. - if (typeof requestConfig.body !== 'undefined') { - options.body = requestConfig.body; - } + const response: Observable> = this.http.request(...request).pipe( + retry(this.connectionService.retryConfig), + map( + response => + Object.assign(response, { + statusCode: response.status, + body: response.body || {}, + }) as Response, + ), + ); - if (typeof requestConfig.headers !== 'undefined') { - options.headers = requestConfig.headers; - } - - try { - const response: HttpResponse = await this.http - .request(requestConfig.method || 'GET', requestConfig.url.toString(), options) - .toPromise(); - - // eslint-disable-next-line prefer-object-spread - return Object.assign(response, { - statusCode: response.status, - body: response.body || {}, - }); - } catch (error) { - throw new Error(error as string); - } + return lastValueFrom(response); } } diff --git a/src/app/modules/favorites/favorites-page.component.ts b/src/app/modules/favorites/favorites-page.component.ts index 58dbe49a..b2141bb9 100644 --- a/src/app/modules/favorites/favorites-page.component.ts +++ b/src/app/modules/favorites/favorites-page.component.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. diff --git a/src/app/modules/hebis/hebis-detail/hebis-detail.component.spec.ts b/src/app/modules/hebis/hebis-detail/hebis-detail.component.spec.ts index b5d096df..55a69a31 100644 --- a/src/app/modules/hebis/hebis-detail/hebis-detail.component.spec.ts +++ b/src/app/modules/hebis/hebis-detail/hebis-detail.component.spec.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -28,6 +28,7 @@ import {Observable, of} from 'rxjs'; import {StorageProvider} from '../../storage/storage.provider'; import {IonicModule} from '@ionic/angular'; import {IonIconModule} from '../../../util/ion-icon/ion-icon.module'; +import {LoggerModule, NgxLoggerLevel} from 'ngx-logger'; const translations: any = {data: {detail: {TITLE: 'Foo'}}}; @@ -72,6 +73,7 @@ describe('HebisDetailComponent', () => { TranslateModule.forRoot({ loader: {provide: TranslateLoader, useClass: TranslateFakeLoader}, }), + LoggerModule.forRoot({level: NgxLoggerLevel.TRACE}), ], providers: [ { diff --git a/src/app/modules/hebis/list/hebis-search-page.component.ts b/src/app/modules/hebis/list/hebis-search-page.component.ts index e81a3e20..82259bcf 100644 --- a/src/app/modules/hebis/list/hebis-search-page.component.ts +++ b/src/app/modules/hebis/list/hebis-search-page.component.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. diff --git a/src/app/modules/map/map.provider.spec.ts b/src/app/modules/map/map.provider.spec.ts index c2455473..566eeaa5 100644 --- a/src/app/modules/map/map.provider.spec.ts +++ b/src/app/modules/map/map.provider.spec.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -20,9 +20,10 @@ import {HttpClientModule} from '@angular/common/http'; import {StorageProvider} from '../storage/storage.provider'; import {MapModule} from './map.module'; import {StorageModule} from '../storage/storage.module'; -import {LoggerConfig, LoggerModule, NGXLogger} from 'ngx-logger'; +import {LoggerModule, NGXLogger, NgxLoggerLevel} from 'ngx-logger'; import {ConfigProvider} from '../config/config.provider'; import {sampleDefaultPolygon} from '../../_helpers/data/sample-configuration'; +import {RouterModule} from '@angular/router'; describe('MapProvider', () => { let provider: MapProvider; @@ -31,7 +32,13 @@ describe('MapProvider', () => { beforeEach(() => { configProvider = jasmine.createSpyObj('ConfigProvider', ['getValue']); TestBed.configureTestingModule({ - imports: [MapModule, HttpClientModule, StorageModule, LoggerModule], + imports: [ + MapModule, + HttpClientModule, + StorageModule, + LoggerModule.forRoot({level: NgxLoggerLevel.TRACE}), + RouterModule.forRoot([]), + ], providers: [ { provide: ConfigProvider, @@ -40,7 +47,6 @@ describe('MapProvider', () => { StAppsWebHttpClient, StorageProvider, NGXLogger, - LoggerConfig, ], }); diff --git a/src/app/modules/menu/context/context-menu.service.ts b/src/app/modules/menu/context/context-menu.service.ts index 300803b2..be0cb799 100644 --- a/src/app/modules/menu/context/context-menu.service.ts +++ b/src/app/modules/menu/context/context-menu.service.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -40,7 +40,7 @@ export class ContextMenuService { /** * Container for the filter query (SCSearchFilter) */ - filterQuery = new Subject(); + filterQuery = new Subject(); /** * Observable filterContext streams @@ -65,7 +65,7 @@ export class ContextMenuService { /** * Container for the sort query */ - sortQuery = new Subject(); + sortQuery = new Subject(); /** * Observable SortContext streams diff --git a/src/app/modules/menu/navigation/navigation.component.ts b/src/app/modules/menu/navigation/navigation.component.ts index 8d6370c3..e10a7c83 100644 --- a/src/app/modules/menu/navigation/navigation.component.ts +++ b/src/app/modules/menu/navigation/navigation.component.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2019 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. diff --git a/src/app/modules/menu/navigation/navigation.html b/src/app/modules/menu/navigation/navigation.html index 4dd103fb..59c503b6 100644 --- a/src/app/modules/menu/navigation/navigation.html +++ b/src/app/modules/menu/navigation/navigation.html @@ -13,6 +13,7 @@ ~ this program. If not, see . --> + diff --git a/src/app/modules/menu/navigation/navigation.module.ts b/src/app/modules/menu/navigation/navigation.module.ts index fe77d28a..53879654 100644 --- a/src/app/modules/menu/navigation/navigation.module.ts +++ b/src/app/modules/menu/navigation/navigation.module.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -21,9 +21,10 @@ import {IonicModule} from '@ionic/angular'; import {IonIconModule} from '../../../util/ion-icon/ion-icon.module'; import {TranslateModule} from '@ngx-translate/core'; import {RouterModule} from '@angular/router'; +import {OfflineNoticeComponent} from './offline-notice.component'; @NgModule({ - declarations: [RootLinkDirective, NavigationComponent, TabsComponent], + declarations: [RootLinkDirective, NavigationComponent, TabsComponent, OfflineNoticeComponent], imports: [CommonModule, IonicModule, IonIconModule, TranslateModule, RouterModule], exports: [TabsComponent, RootLinkDirective, NavigationComponent], }) diff --git a/src/app/modules/menu/navigation/navigation.scss b/src/app/modules/menu/navigation/navigation.scss index 51583863..830ebd71 100644 --- a/src/app/modules/menu/navigation/navigation.scss +++ b/src/app/modules/menu/navigation/navigation.scss @@ -1,5 +1,5 @@ /*! - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -21,8 +21,14 @@ stapps-navigation-tabs { } } +stapps-offline-notice.has-error ~ ion-split-pane, +stapps-offline-notice.is-offline ~ ion-split-pane { + margin-top: calc(var(--font-size-md) + 2 * var(--spacing-sm)); +} + :host { ion-split-pane { + transition: margin-top 150ms ease; --side-max-width: 256px; margin-bottom: calc(var(--ion-tabbar-height) + env(safe-area-inset-bottom)); diff --git a/src/app/modules/menu/navigation/offline-notice.component.ts b/src/app/modules/menu/navigation/offline-notice.component.ts new file mode 100644 index 00000000..758fc8b3 --- /dev/null +++ b/src/app/modules/menu/navigation/offline-notice.component.ts @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {Component, ElementRef, HostBinding, OnDestroy, ViewChild} from '@angular/core'; +import {InternetConnectionService} from '../../../util/internet-connection.service'; +import {Subscription} from 'rxjs'; +import {Router} from '@angular/router'; +import {NGXLogger} from 'ngx-logger'; + +@Component({ + selector: 'stapps-offline-notice', + templateUrl: 'offline-notice.html', + styleUrls: ['offline-notice.scss'], +}) +export class OfflineNoticeComponent implements OnDestroy { + @HostBinding('class.is-offline') isOffline = false; + + @HostBinding('class.has-error') hasError = false; + + @ViewChild('spinIcon', {read: ElementRef}) spinIcon: ElementRef; + + readonly subscriptions: Subscription[]; + + constructor( + readonly offlineProvider: InternetConnectionService, + readonly router: Router, + readonly logger: NGXLogger, + ) { + this.subscriptions = [ + this.offlineProvider.offline$.subscribe(isOffline => { + this.isOffline = isOffline; + }), + this.offlineProvider.error$.subscribe(hasError => { + this.hasError = hasError; + }), + ]; + } + + retry() { + this.spinIcon.nativeElement.classList.remove('spin'); + this.spinIcon.nativeElement.offsetWidth; + this.spinIcon.nativeElement.classList.add('spin'); + this.offlineProvider.retry(); + } + + ngOnDestroy() { + for (const subscription of this.subscriptions) { + subscription.unsubscribe(); + } + } +} diff --git a/src/app/modules/menu/navigation/offline-notice.html b/src/app/modules/menu/navigation/offline-notice.html new file mode 100644 index 00000000..ebd5e89a --- /dev/null +++ b/src/app/modules/menu/navigation/offline-notice.html @@ -0,0 +1,25 @@ + + + + {{ 'app.errors.OFFLINE' | translate }} + + + + {{ 'app.errors.CONNECTION_ERROR' | translate }} + + diff --git a/src/app/modules/menu/navigation/offline-notice.scss b/src/app/modules/menu/navigation/offline-notice.scss new file mode 100644 index 00000000..952651bd --- /dev/null +++ b/src/app/modules/menu/navigation/offline-notice.scss @@ -0,0 +1,77 @@ +/*! + * Copyright (C) 2023 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +:host { + display: grid; + $height: calc(var(--font-size-md) + 2 * var(--spacing-sm)); + + height: $height; + width: 100%; + + line-height: var(--font-size-md); + font-size: var(--font-size-md); + font-weight: bold; + + transform: translateY(calc(-1 * $height)); + + transition: all 150ms ease; + + &.is-offline, + &.has-error { + transform: translateY(0px); + } + + > ion-button { + grid-row: 1; + grid-column: 1; + margin: 0; + --border-radius: 0; + opacity: 0; + --padding-top: 0; + --padding-bottom: 0; + transition: all 150ms ease; + z-index: 0; + + &.close { + height: 100%; + margin: 0; + position: absolute; + right: 0; + top: 50%; + bottom: 0; + transform: translateY(-50%); + z-index: 1; + color: var(--ion-color-danger-contrast); + } + } + + &.is-offline > .offline-button, + &.has-error > .close, + &.has-error > .error-button { + opacity: 1; + } +} + +.spin { + animation: loading 1s ease running 3; +} + +@keyframes loading { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/src/app/modules/schedule/page/schedule.service.ts b/src/app/modules/schedule/page/schedule.service.ts deleted file mode 100644 index 3e428058..00000000 --- a/src/app/modules/schedule/page/schedule.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2020 StApps - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ -import {Injectable} from '@angular/core'; - -/** - * MenuService provides bidirectional communication of context menu options and search queries - */ -@Injectable() -export class ScheduleService {} diff --git a/src/app/util/internet-connection.service.ts b/src/app/util/internet-connection.service.ts new file mode 100644 index 00000000..b07f08c6 --- /dev/null +++ b/src/app/util/internet-connection.service.ts @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {fromEvent, merge, ObservableInput, of, race, RetryConfig, share, Subject, takeUntil} from 'rxjs'; +import {Injectable} from '@angular/core'; +import {filter, map, startWith, take, tap} from 'rxjs/operators'; +import {NGXLogger} from 'ngx-logger'; +import {Router} from '@angular/router'; + +@Injectable({ + providedIn: 'root', +}) +export class InternetConnectionService { + private readonly manualRetry$ = new Subject(); + + private readonly abortRetry$ = new Subject(); + + /** + * Emits whenever the browser goes online or offline. + */ + readonly offline$ = window + ? merge( + fromEvent(window, 'online').pipe(map(() => false)), + fromEvent(window, 'offline').pipe(map(() => true)), + ).pipe(startWith(!window.navigator.onLine), share()) + : of(true); + + /** + * Emits whenever http requests should be retried + * + * Also keeps track of when a retry is needed, automatically + * registering itself. + */ + readonly retryConfig: RetryConfig = { + delay: this.doRetry.bind(this), + }; + + private doRetry(error: unknown, retryCount: number): ObservableInput { + return race( + this.offline$.pipe( + tap(it => console.log(it)), + filter(it => !it), + take(1), + ), + this.manualRetry$, + ).pipe( + tap({ + subscribe: () => { + this.errors.add(error); + if (this.errors.size > 0) { + this.error$.next(true); + } + }, + next: () => { + this.logger.error(`${retryCount}x`, error); + }, + unsubscribe: () => { + this.errors.delete(error); + if (this.errors.size === 0) { + this.error$.next(false); + } + }, + }), + takeUntil( + merge( + this.abortRetry$.pipe(tap(() => this.logger.warn('HTTP Request retry aborted manually'))), + this.router.events.pipe(tap(() => this.logger.warn('HTTP Request retry aborted by routing'))), + ), + ), + ); + } + + /** + * Emits when there are errors + */ + readonly error$ = new Subject(); + + private readonly errors = new Set(); + + constructor(private readonly logger: NGXLogger, private readonly router: Router) {} + + /** + * Retry all failed http requests + */ + retry() { + this.manualRetry$.next(); + } + + /** + * Abandon all failed http requests + */ + dismissError() { + this.abortRetry$.next(); + } +} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index ae903634..724741af 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -27,7 +27,9 @@ }, "errors": { "SERVICE": "Fehler bei Dienstausführung.", - "UNKNOWN": "Unbekannter Fehler." + "UNKNOWN": "Unbekannter Fehler.", + "OFFLINE": "Keine Internetverbindung", + "CONNECTION_ERROR": "Verbindungsfehler" } }, "assessments": { diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index def7fa4f..292e6953 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -27,7 +27,9 @@ }, "errors": { "SERVICE": "Service error.", - "UNKNOWN": "Unknown problem." + "UNKNOWN": "Unknown problem.", + "OFFLINE": "No internet connection", + "CONNECTION_ERROR": "Connection error" } }, "assessments": { diff --git a/src/assets/icons.min.woff2 b/src/assets/icons.min.woff2 index 72a5e5d9..81dd2c19 100644 Binary files a/src/assets/icons.min.woff2 and b/src/assets/icons.min.woff2 differ