diff --git a/src/assets/icons/offline.svg b/src/assets/icons/offline.svg
new file mode 100644
index 0000000..d5c6610
--- /dev/null
+++ b/src/assets/icons/offline.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/styles/_full-pages.scss b/src/assets/styles/_full-pages.scss
index 9c9c6a4..90a85ae 100644
--- a/src/assets/styles/_full-pages.scss
+++ b/src/assets/styles/_full-pages.scss
@@ -1,17 +1,14 @@
-@import 'variables';
+@use 'variables';
.full-page-section,
.full-page-content {
position: fixed;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
+ inset: 0;
}
.full-page-section {
opacity: 0.7;
- background: $white;
+ background: variables.$white;
z-index: 900;
}
diff --git a/src/lib/error/full-page-error/full-page-error.component.html b/src/lib/error/full-page-error/full-page-error.component.html
index e55ee4d..1ffdcf7 100644
--- a/src/lib/error/full-page-error/full-page-error.component.html
+++ b/src/lib/error/full-page-error/full-page-error.component.html
@@ -1,15 +1,36 @@
-
-
-
-
- {{ error.message }}
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ error.message }}
+
+
+
+
diff --git a/src/lib/error/full-page-error/full-page-error.component.scss b/src/lib/error/full-page-error/full-page-error.component.scss
index 8791f01..6638559 100644
--- a/src/lib/error/full-page-error/full-page-error.component.scss
+++ b/src/lib/error/full-page-error/full-page-error.component.scss
@@ -1,13 +1,32 @@
-@import '../../../assets/styles/variables';
+@use '../../../assets/styles/variables';
+
+.offline-box {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ height: 40px;
+ width: 300px;
+ background: variables.$white;
+ border: 1px solid variables.$separator;
+ border-radius: 10px;
+ padding: 14px;
+
+ > mat-icon {
+ opacity: 0.3;
+ }
+
+ > iqser-circle-button {
+ flex-grow: 1;
+ justify-content: flex-end;
+ }
+}
.full-page-section {
opacity: 0.95;
}
.full-page-content {
- display: flex;
flex-direction: column;
- align-items: center;
text-align: center;
> mat-icon {
@@ -16,12 +35,12 @@
opacity: 0.1;
}
- .heading-l {
- color: $grey-7;
- font-weight: initial;
- }
-
.error {
- color: $red-1;
+ color: variables.$primary;
}
}
+
+:is(.offline-box, .full-page-content) .heading-l {
+ color: #9398a0;
+ font-weight: initial;
+}
diff --git a/src/lib/error/full-page-error/full-page-error.component.ts b/src/lib/error/full-page-error/full-page-error.component.ts
index dcc4fce..43862b9 100644
--- a/src/lib/error/full-page-error/full-page-error.component.ts
+++ b/src/lib/error/full-page-error/full-page-error.component.ts
@@ -1,11 +1,19 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IconButtonTypes } from '../../buttons';
import { ErrorService } from '../error.service';
+import { animate, state, style, transition, trigger } from '@angular/animations';
@Component({
selector: 'iqser-full-page-error',
templateUrl: './full-page-error.component.html',
styleUrls: ['./full-page-error.component.scss'],
+ animations: [
+ trigger('animateOpenClose', [
+ state('open', style({ bottom: '20px' })),
+ state('void', style({ bottom: '-70px' })),
+ transition('* => open, open => void', animate('1s ease-in-out'))
+ ])
+ ],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FullPageErrorComponent {
diff --git a/src/lib/error/index.ts b/src/lib/error/index.ts
index 9a01d8a..42fbcd1 100644
--- a/src/lib/error/index.ts
+++ b/src/lib/error/index.ts
@@ -1,2 +1,4 @@
export * from './error.service';
+export * from './max-retries.token';
+export * from './server-error-interceptor';
export * from './full-page-error/full-page-error.component';
diff --git a/src/lib/error/max-retries.token.ts b/src/lib/error/max-retries.token.ts
new file mode 100644
index 0000000..b2ad58a
--- /dev/null
+++ b/src/lib/error/max-retries.token.ts
@@ -0,0 +1,3 @@
+import { InjectionToken } from '@angular/core';
+
+export const MAX_RETRIES_ON_SERVER_ERROR = new InjectionToken('Number of retries before giving up');
diff --git a/src/lib/error/server-error-interceptor.ts b/src/lib/error/server-error-interceptor.ts
new file mode 100644
index 0000000..913805d
--- /dev/null
+++ b/src/lib/error/server-error-interceptor.ts
@@ -0,0 +1,52 @@
+import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
+import { Inject, Injectable, Optional } from '@angular/core';
+import { ErrorService } from '@iqser/common-ui';
+import { MonoTypeOperatorFunction, Observable, throwError, timer } from 'rxjs';
+import { catchError, mergeMap, retryWhen, tap } from 'rxjs/operators';
+import { MAX_RETRIES_ON_SERVER_ERROR } from './max-retries.token';
+
+function updateSeconds(seconds: number) {
+ if (seconds === 0 || seconds === 1) {
+ return seconds + 1;
+ } else {
+ return seconds * seconds;
+ }
+}
+
+function backoffOnServerError(maxRetries = 3): MonoTypeOperatorFunction> {
+ let seconds = 0;
+ return retryWhen(attempts =>
+ attempts.pipe(
+ tap(() => (seconds = updateSeconds(seconds))),
+ mergeMap((error: HttpErrorResponse, index) => {
+ if ((error.status <= 500 && error.status !== 0) || index === maxRetries) {
+ return throwError(error);
+ } else {
+ console.error('An error occurred: ', error);
+ console.error(`Retrying in ${seconds} seconds...`);
+ return timer(seconds * 1000);
+ }
+ })
+ )
+ );
+}
+
+@Injectable()
+export class ServerErrorInterceptor implements HttpInterceptor {
+ constructor(
+ private readonly _errorService: ErrorService,
+ @Optional() @Inject(MAX_RETRIES_ON_SERVER_ERROR) private readonly _maxRetries: number
+ ) {}
+
+ intercept(req: HttpRequest, next: HttpHandler): Observable> {
+ return next.handle(req).pipe(
+ catchError((error: HttpErrorResponse) => {
+ if (error.status >= 500 || error.status === 0) {
+ this._errorService.set(error);
+ }
+ return throwError(error);
+ }),
+ backoffOnServerError(this._maxRetries || 3)
+ );
+ }
+}
diff --git a/src/lib/icons/icons.module.ts b/src/lib/icons/icons.module.ts
index 8c39496..85445a7 100644
--- a/src/lib/icons/icons.module.ts
+++ b/src/lib/icons/icons.module.ts
@@ -18,6 +18,7 @@ export class IqserIconsModule {
'edit',
'failure',
'help-outline',
+ 'offline',
'refresh',
'search',
'sort-asc',
diff --git a/src/lib/listing/sync-width.directive.ts b/src/lib/listing/sync-width.directive.ts
index c7cde8c..d2f4693 100644
--- a/src/lib/listing/sync-width.directive.ts
+++ b/src/lib/listing/sync-width.directive.ts
@@ -8,7 +8,7 @@ export class SyncWidthDirective implements OnDestroy {
private readonly _interval: number;
constructor(private readonly _elementRef: ElementRef) {
- this._interval = setInterval(() => {
+ this._interval = window.setInterval(() => {
this._matchWidth();
}, 1000);
}
diff --git a/src/lib/utils/decorators/debounce.decorator.ts b/src/lib/utils/decorators/debounce.decorator.ts
index 0da4c4d..8be812b 100644
--- a/src/lib/utils/decorators/debounce.decorator.ts
+++ b/src/lib/utils/decorators/debounce.decorator.ts
@@ -6,7 +6,7 @@ export function Debounce(delay = 300): MethodDecorator {
descriptorCopy.value = function _new(...args: unknown[]) {
clearTimeout(timeout);
- timeout = setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay);
+ timeout = window.setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay);
};
return descriptorCopy;