mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-19 08:02:55 +00:00
feat: offline notice
This commit is contained in:
committed by
Rainer Killinger
parent
11d1ac3f7c
commit
9b4caf526f
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
@@ -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<string> = {};
|
||||
if (from) {
|
||||
console.log(from);
|
||||
bounds.lowerBound = {
|
||||
limit: from,
|
||||
mode: 'inclusive',
|
||||
};
|
||||
}
|
||||
if (to) {
|
||||
console.log(to);
|
||||
bounds.upperBound = {
|
||||
limit: to,
|
||||
mode: 'inclusive',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<typeof HttpClient>['request'];
|
||||
type HttpRequestFunction<T extends ReturnType<HttpRequestFunctions>> = Extract<
|
||||
HttpRequestFunctions,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(...parameters: any[]) => T
|
||||
>;
|
||||
type HttpRequestParameters<T extends ReturnType<HttpRequestFunctions>> = Parameters<HttpRequestFunction<T>>;
|
||||
|
||||
/**
|
||||
* 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<TYPE_OF_BODY>(requestConfig: HttpClientRequest): Promise<Response<TYPE_OF_BODY>> {
|
||||
const options: {
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
[key: string]: unknown;
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
observe: 'response';
|
||||
} = {
|
||||
body: {},
|
||||
observe: 'response',
|
||||
responseType: 'json',
|
||||
};
|
||||
const request: HttpRequestParameters<Observable<HttpResponse<TYPE_OF_BODY>>> = [
|
||||
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<Response<TYPE_OF_BODY>> = this.http.request(...request).pipe(
|
||||
retry(this.connectionService.retryConfig),
|
||||
map(
|
||||
response =>
|
||||
Object.assign(response, {
|
||||
statusCode: response.status,
|
||||
body: response.body || {},
|
||||
}) as Response<TYPE_OF_BODY>,
|
||||
),
|
||||
);
|
||||
|
||||
if (typeof requestConfig.headers !== 'undefined') {
|
||||
options.headers = requestConfig.headers;
|
||||
}
|
||||
|
||||
try {
|
||||
const response: HttpResponse<TYPE_OF_BODY> = await this.http
|
||||
.request<TYPE_OF_BODY>(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -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<SCSearchFilter>();
|
||||
filterQuery = new Subject<SCSearchFilter | undefined>();
|
||||
|
||||
/**
|
||||
* Observable filterContext streams
|
||||
@@ -65,7 +65,7 @@ export class ContextMenuService {
|
||||
/**
|
||||
* Container for the sort query
|
||||
*/
|
||||
sortQuery = new Subject<SCSearchSort[]>();
|
||||
sortQuery = new Subject<SCSearchSort[] | undefined>();
|
||||
|
||||
/**
|
||||
* Observable SortContext streams
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<stapps-offline-notice></stapps-offline-notice>
|
||||
<ion-split-pane contentId="main" when="lg">
|
||||
<ion-menu menuId="main" contentId="main" type="overlay" side="start" swipe-gesture="false">
|
||||
<ion-header>
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
63
src/app/modules/menu/navigation/offline-notice.component.ts
Normal file
63
src/app/modules/menu/navigation/offline-notice.component.ts
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/app/modules/menu/navigation/offline-notice.html
Normal file
25
src/app/modules/menu/navigation/offline-notice.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<ion-button class="offline-button" color="warning">
|
||||
<ion-icon slot="start" size="16" weight="800" name="cloud_off"></ion-icon>
|
||||
<ion-label>{{ 'app.errors.OFFLINE' | translate }}</ion-label>
|
||||
</ion-button>
|
||||
<ion-button class="error-button" color="danger" (click)="retry()">
|
||||
<ion-icon #spinIcon slot="start" size="16" weight="800" name="refresh"></ion-icon>
|
||||
<ion-label>{{ 'app.errors.CONNECTION_ERROR' | translate }}</ion-label>
|
||||
</ion-button>
|
||||
<ion-button class="close" fill="clear" color="light" (click)="offlineProvider.dismissError()"
|
||||
><ion-icon size="16" weight="800" name="close" slot="icon-only"></ion-icon
|
||||
></ion-button>
|
||||
77
src/app/modules/menu/navigation/offline-notice.scss
Normal file
77
src/app/modules/menu/navigation/offline-notice.scss
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
: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);
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
/**
|
||||
* MenuService provides bidirectional communication of context menu options and search queries
|
||||
*/
|
||||
@Injectable()
|
||||
export class ScheduleService {}
|
||||
106
src/app/util/internet-connection.service.ts
Normal file
106
src/app/util/internet-connection.service.ts
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<void>();
|
||||
|
||||
private readonly abortRetry$ = new Subject<void>();
|
||||
|
||||
/**
|
||||
* 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<unknown> {
|
||||
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<boolean>();
|
||||
|
||||
private readonly errors = new Set<unknown>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,9 @@
|
||||
},
|
||||
"errors": {
|
||||
"SERVICE": "Fehler bei Dienstausführung.",
|
||||
"UNKNOWN": "Unbekannter Fehler."
|
||||
"UNKNOWN": "Unbekannter Fehler.",
|
||||
"OFFLINE": "Keine Internetverbindung",
|
||||
"CONNECTION_ERROR": "Verbindungsfehler"
|
||||
}
|
||||
},
|
||||
"assessments": {
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
},
|
||||
"errors": {
|
||||
"SERVICE": "Service error.",
|
||||
"UNKNOWN": "Unknown problem."
|
||||
"UNKNOWN": "Unknown problem.",
|
||||
"OFFLINE": "No internet connection",
|
||||
"CONNECTION_ERROR": "Connection error"
|
||||
}
|
||||
},
|
||||
"assessments": {
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user