diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts
index 5b01813..19d0947 100644
--- a/src/lib/common-ui.module.ts
+++ b/src/lib/common-ui.module.ts
@@ -14,7 +14,7 @@ import {
ToastComponent,
} from './misc';
import { FullPageLoadingIndicatorComponent } from './loading';
-import { FullPageErrorComponent } from './error';
+import { ConnectionStatusComponent, FullPageErrorComponent } from './error';
import { IqserListingModule } from './listing';
import { IqserFiltersModule } from './filtering';
import { IqserInputsModule } from './inputs';
@@ -45,6 +45,7 @@ const modules = [
const components = [
StatusBarComponent,
FullPageLoadingIndicatorComponent,
+ ConnectionStatusComponent,
FullPageErrorComponent,
LogoComponent,
HiddenActionComponent,
diff --git a/src/lib/error/connection-status/connection-status.component.html b/src/lib/error/connection-status/connection-status.component.html
new file mode 100644
index 0000000..45b1490
--- /dev/null
+++ b/src/lib/error/connection-status/connection-status.component.html
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/lib/error/connection-status/connection-status.component.scss b/src/lib/error/connection-status/connection-status.component.scss
new file mode 100644
index 0000000..f7f2fb1
--- /dev/null
+++ b/src/lib/error/connection-status/connection-status.component.scss
@@ -0,0 +1,32 @@
+@use 'sass:math';
+
+.indicator {
+ $width: 160px;
+
+ position: fixed;
+ top: 0;
+ right: 50%;
+ left: 50%;
+ height: 40px;
+ margin-left: -(math.div($width, 2));
+ width: $width;
+ border-radius: 8px;
+ z-index: 5;
+
+ span {
+ color: var(--iqser-grey-1);
+ text-align: center;
+ line-height: 18px;
+ font-weight: 600;
+ height: 18px;
+ width: 100%;
+ }
+}
+
+.offline {
+ background: var(--iqser-yellow-2);
+}
+
+.online {
+ background: var(--iqser-green-2);
+}
diff --git a/src/lib/error/connection-status/connection-status.component.ts b/src/lib/error/connection-status/connection-status.component.ts
new file mode 100644
index 0000000..fab4848
--- /dev/null
+++ b/src/lib/error/connection-status/connection-status.component.ts
@@ -0,0 +1,24 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { connectionStatusTranslations } from '../../translations/connection-status-translations';
+import { animate, state, style, transition, trigger } from '@angular/animations';
+import { ErrorService } from '../error.service';
+
+@Component({
+ selector: 'iqser-connection-status',
+ templateUrl: './connection-status.component.html',
+ styleUrls: ['./connection-status.component.scss'],
+ animations: [
+ trigger('animateOpenClose', [
+ state('offline', style({ top: '100px' })),
+ state('online', style({ top: '-100px' })),
+ transition('* => offline', animate('1s ease-out')),
+ transition('* => online', animate('3s ease-in')),
+ ]),
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ConnectionStatusComponent {
+ connectionStatusTranslations = connectionStatusTranslations;
+
+ constructor(readonly errorService: ErrorService) {}
+}
diff --git a/src/lib/error/error.service.ts b/src/lib/error/error.service.ts
index 237ac1e..ee07896 100644
--- a/src/lib/error/error.service.ts
+++ b/src/lib/error/error.service.ts
@@ -6,6 +6,24 @@ import { delay, filter, map, mapTo } from 'rxjs/operators';
import { NavigationStart, Router } from '@angular/router';
import { shareLast } from '../utils';
+export class CustomError {
+ readonly label: string;
+ readonly actionLabel: string;
+ readonly actionIcon: string;
+ readonly message?: string;
+ readonly action?: () => void;
+
+ constructor(label: string, actionLabel: string, actionIcon: string, message?: string, action?: () => void) {
+ this.label = label;
+ this.actionLabel = actionLabel;
+ this.actionIcon = actionIcon;
+ this.message = message;
+ this.action = action;
+ }
+}
+
+export type ErrorType = HttpErrorResponse | CustomError | undefined;
+
const BANDWIDTH_LIMIT_EXCEEDED = 509 as const;
const OFFLINE_STATUSES = [
HttpStatusCode.BadGateway,
@@ -14,15 +32,15 @@ const OFFLINE_STATUSES = [
BANDWIDTH_LIMIT_EXCEEDED,
] as const;
-const isOffline = (error?: HttpErrorResponse) => !!error && OFFLINE_STATUSES.includes(error.status);
+const isOffline = (error?: ErrorType) => error instanceof HttpErrorResponse && OFFLINE_STATUSES.includes(error.status);
@Injectable({ providedIn: 'root' })
export class ErrorService {
- readonly error$ = new Subject();
readonly offline$: Observable;
readonly online$: Observable;
- readonly serverError$ = this.error$.pipe(filter(error => !error || !isOffline(error)));
readonly connectionStatus$: Observable;
+ private readonly _error$ = new Subject();
+ readonly error$ = this._error$.pipe(filter(error => !error || !isOffline(error)));
private readonly _online$ = new Subject();
constructor(private readonly _loadingService: LoadingService, private readonly _router: Router) {
@@ -36,9 +54,9 @@ export class ErrorService {
this.connectionStatus$ = merge(this.online$, this.offline$, removeIndicator$).pipe(map(event => event?.type));
}
- set(error: HttpErrorResponse): void {
+ set(error: ErrorType): void {
this._loadingService.stop();
- this.error$.next(error);
+ this._error$.next(error);
}
setOnline(): void {
@@ -46,11 +64,11 @@ export class ErrorService {
}
clear(): void {
- this.error$.next();
+ this._error$.next();
}
private _offline() {
- return merge(fromEvent(window, 'offline'), this.error$.pipe(filter(isOffline), mapTo(new Event('offline')), shareLast()));
+ return merge(fromEvent(window, 'offline'), this._error$.pipe(filter(isOffline), mapTo(new Event('offline')), shareLast()));
}
private _online() {
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 864ed88..eb9b099 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,28 +1,19 @@
-
-
-
-
-
+
-
+
- {{ 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 cc2b1f9..6bab451 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,36 +1,3 @@
-@use 'sass:math';
-
-.indicator {
- $width: 160px;
-
- position: fixed;
- top: 0;
- right: 50%;
- left: 50%;
- height: 40px;
- margin-left: -(math.div($width, 2));
- width: $width;
- border-radius: 8px;
- z-index: 5;
-
- span {
- color: var(--iqser-grey-1);
- text-align: center;
- line-height: 18px;
- font-weight: 600;
- height: 18px;
- width: 100%;
- }
-}
-
-.offline {
- background: var(--iqser-yellow-2);
-}
-
-.online {
- background: var(--iqser-green-2);
-}
-
.full-page-section {
opacity: 0.95;
}
@@ -50,7 +17,7 @@
}
}
-:is(.offline-box, .full-page-content) .heading-l {
- color: #9398a0;
+.heading-l {
+ color: var(--iqser-grey-7);
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 1c29a90..0a2f7a6 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,30 +1,36 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IconButtonTypes } from '../../buttons';
-import { ErrorService } from '../error.service';
-import { animate, state, style, transition, trigger } from '@angular/animations';
-import { connectionStatusTranslations } from '../../translations/connection-status-translations';
+import { CustomError, ErrorService, ErrorType } from '../error.service';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@Component({
selector: 'iqser-full-page-error',
templateUrl: './full-page-error.component.html',
styleUrls: ['./full-page-error.component.scss'],
- animations: [
- trigger('animateOpenClose', [
- state('offline', style({ top: '100px' })),
- state('online', style({ top: '-100px' })),
- transition('* => offline', animate('1s ease-out')),
- transition('* => online', animate('3s ease-in')),
- ]),
- ],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FullPageErrorComponent {
- translations = connectionStatusTranslations;
readonly iconButtonTypes = IconButtonTypes;
constructor(readonly errorService: ErrorService) {}
- reload(): void {
- window.location.reload();
+ errorTitle(error: ErrorType): string {
+ return error instanceof CustomError ? error.label : _('error.title');
+ }
+
+ actionLabel(error: ErrorType): string {
+ return error instanceof CustomError ? error.actionLabel : _('error.reload');
+ }
+
+ actionIcon(error: ErrorType): string {
+ return error instanceof CustomError ? error.actionIcon : 'iqser:refresh';
+ }
+
+ action(error: ErrorType): void {
+ if (error instanceof CustomError && error.action) {
+ error.action();
+ } else {
+ window.location.reload();
+ }
}
}
diff --git a/src/lib/error/index.ts b/src/lib/error/index.ts
index 42fbcd1..bf107e8 100644
--- a/src/lib/error/index.ts
+++ b/src/lib/error/index.ts
@@ -2,3 +2,4 @@ export * from './error.service';
export * from './max-retries.token';
export * from './server-error-interceptor';
export * from './full-page-error/full-page-error.component';
+export * from './connection-status/connection-status.component';
diff --git a/src/lib/listing/services/entities.service.ts b/src/lib/listing/services/entities.service.ts
index b326182..bb7f627 100644
--- a/src/lib/listing/services/entities.service.ts
+++ b/src/lib/listing/services/entities.service.ts
@@ -22,6 +22,7 @@ export class EntitiesService extends GenericService<
readonly all$: Observable;
readonly allLength$: Observable;
protected readonly _entityChanged$ = new Subject();
+ protected readonly _entityDeleted$ = new Subject();
private readonly _all$ = new BehaviorSubject([]);
constructor(
@@ -68,8 +69,13 @@ export class EntitiesService extends GenericService<
);
}
+ getEntityDeleted$(entityId: string): Observable {
+ return this._entityDeleted$.pipe(filter(entity => entity.id === entityId));
+ }
+
setEntities(entities: E[]): void {
const changedEntities: E[] = [];
+ const deletedEntities = this.all.filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id));
// Keep old object references for unchanged entities
const newEntities = entities.map(entity => {
@@ -90,6 +96,10 @@ export class EntitiesService extends GenericService<
for (const entity of changedEntities) {
this._entityChanged$.next(entity);
}
+
+ for (const entity of deletedEntities) {
+ this._entityDeleted$.next(entity);
+ }
}
find(id: string): E | undefined {
diff --git a/src/lib/utils/auto-unsubscribe.directive.ts b/src/lib/utils/auto-unsubscribe.directive.ts
index f912ca1..1431dc3 100644
--- a/src/lib/utils/auto-unsubscribe.directive.ts
+++ b/src/lib/utils/auto-unsubscribe.directive.ts
@@ -1,12 +1,14 @@
import { Directive, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
+import { OnDetach } from './custom-route-reuse.strategy';
/**
* Inherit this class when you need to subscribe to observables in your components
*/
@Directive()
-export abstract class AutoUnsubscribe implements OnDestroy {
+export abstract class AutoUnsubscribe implements OnDestroy, OnDetach {
private readonly _subscriptions = new Subscription();
+ private readonly _activeScreenSubscriptions = new Subscription();
/**
* Call this method when you want to subscribe to an observable
@@ -17,12 +19,29 @@ export abstract class AutoUnsubscribe implements OnDestroy {
this._subscriptions.add(subscription);
}
+ /**
+ * Call this method when you want to subscribe to an observable while the screen is active
+ * @param subscription - the new subscription to add to subscriptions array
+ */
+ set addActiveScreenSubscription(subscription: Subscription) {
+ this._activeScreenSubscriptions.closed = false;
+ this._activeScreenSubscriptions.add(subscription);
+ }
+
/**
* This method unsubscribes active subscriptions
* If you implement OnDestroy in a component that inherits AutoUnsubscribeComponent,
* then you must explicitly call super.ngOnDestroy()
*/
ngOnDestroy(): void {
+ this.ngOnDetach();
this._subscriptions.unsubscribe();
}
+
+ /**
+ * This method unsubscribes active subscriptions for active screens
+ */
+ ngOnDetach(): void {
+ this._activeScreenSubscriptions.unsubscribe();
+ }
}