thingsboard/ui-ngx/src/app/shared/components/toast.directive.ts

221 lines
7.8 KiB
TypeScript
Raw Normal View History

///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
2020-02-10 13:04:56 +02:00
AfterViewInit, ApplicationRef, ChangeDetectorRef,
Component,
Directive,
ElementRef,
2020-01-24 19:35:10 +02:00
Inject, Input, NgZone,
2020-02-10 13:04:56 +02:00
OnDestroy, ViewChild,
ViewContainerRef
} from '@angular/core';
import {
MAT_SNACK_BAR_DATA,
MatSnackBar,
MatSnackBarConfig,
MatSnackBarRef
} from '@angular/material';
import { NotificationMessage } from '@app/core/notification/notification.models';
import { onParentScrollOrWindowResize } from '@app/core/utils';
import { Subscription } from 'rxjs';
import { NotificationService } from '@app/core/services/notification.service';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
2020-02-10 13:04:56 +02:00
import Timeout = NodeJS.Timeout;
import { MatButton } from '@angular/material/button';
@Directive({
selector: '[tb-toast]'
})
export class ToastDirective implements AfterViewInit, OnDestroy {
@Input()
toastTarget = 'root';
private notificationSubscription: Subscription = null;
private hideNotificationSubscription: Subscription = null;
2020-01-24 19:14:40 +02:00
private snackBarRef: MatSnackBarRef<TbSnackBarComponent> = null;
2020-01-24 19:35:10 +02:00
private currentMessage: NotificationMessage = null;
2020-01-24 19:14:40 +02:00
2020-02-10 13:04:56 +02:00
private dismissTimeout: Timeout = null;
constructor(public elementRef: ElementRef,
public viewContainerRef: ViewContainerRef,
private notificationService: NotificationService,
public snackBar: MatSnackBar,
2020-01-24 19:35:10 +02:00
private ngZone: NgZone,
private breakpointObserver: BreakpointObserver) {
}
ngAfterViewInit(): void {
this.notificationSubscription = this.notificationService.getNotification().subscribe(
(notificationMessage) => {
2020-01-24 19:35:10 +02:00
if (this.shouldDisplayMessage(notificationMessage)) {
this.currentMessage = notificationMessage;
const data = {
parent: this.elementRef,
notification: notificationMessage
};
const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']);
const config: MatSnackBarConfig = {
horizontalPosition: notificationMessage.horizontalPosition || 'left',
verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'),
viewContainerRef: this.viewContainerRef,
duration: notificationMessage.duration,
2020-02-10 13:04:56 +02:00
panelClass: notificationMessage.panelClass,
2020-01-24 19:35:10 +02:00
data
};
this.ngZone.run(() => {
2020-01-24 19:14:40 +02:00
if (this.snackBarRef) {
this.snackBarRef.dismiss();
}
this.snackBarRef = this.snackBar.openFromComponent(TbSnackBarComponent, config);
2020-02-10 13:04:56 +02:00
if (notificationMessage.duration && notificationMessage.duration > 0 && notificationMessage.forceDismiss) {
if (this.dismissTimeout !== null) {
clearTimeout(this.dismissTimeout);
this.dismissTimeout = null;
}
this.dismissTimeout = setTimeout(() => {
if (this.snackBarRef) {
this.snackBarRef.instance.actionButton._elementRef.nativeElement.click();
}
this.dismissTimeout = null;
}, notificationMessage.duration);
}
2020-01-24 19:14:40 +02:00
this.snackBarRef.afterDismissed().subscribe(() => {
2020-02-10 13:04:56 +02:00
if (this.dismissTimeout !== null) {
clearTimeout(this.dismissTimeout);
this.dismissTimeout = null;
}
2020-01-24 19:14:40 +02:00
this.snackBarRef = null;
2020-01-24 19:35:10 +02:00
this.currentMessage = null;
2020-01-24 19:14:40 +02:00
});
2020-01-24 19:35:10 +02:00
});
}
}
);
this.hideNotificationSubscription = this.notificationService.getHideNotification().subscribe(
(hideNotification) => {
if (hideNotification) {
const target = hideNotification.target || 'root';
if (this.toastTarget === target) {
2020-01-24 19:35:10 +02:00
this.ngZone.run(() => {
if (this.snackBarRef) {
this.snackBarRef.dismiss();
}
});
}
}
}
);
}
2020-01-24 19:35:10 +02:00
private shouldDisplayMessage(notificationMessage: NotificationMessage): boolean {
if (notificationMessage && notificationMessage.message) {
const target = notificationMessage.target || 'root';
if (this.toastTarget === target) {
if (!this.currentMessage || this.currentMessage.message !== notificationMessage.message
|| this.currentMessage.type !== notificationMessage.type) {
return true;
}
}
}
return false;
}
ngOnDestroy(): void {
if (this.notificationSubscription) {
this.notificationSubscription.unsubscribe();
}
if (this.hideNotificationSubscription) {
this.hideNotificationSubscription.unsubscribe();
}
}
}
@Component({
selector: 'tb-snack-bar-component',
templateUrl: 'snack-bar-component.html',
styleUrls: ['snack-bar-component.scss']
})
export class TbSnackBarComponent implements AfterViewInit, OnDestroy {
2020-02-10 13:04:56 +02:00
@ViewChild('actionButton', {static: true}) actionButton: MatButton;
private parentEl: HTMLElement;
2020-02-10 13:04:56 +02:00
public snackBarContainerEl: HTMLElement;
private parentScrollSubscription: Subscription = null;
public notification: NotificationMessage;
constructor(@Inject(MAT_SNACK_BAR_DATA) public data: any, private elementRef: ElementRef,
2020-02-10 13:04:56 +02:00
public cd: ChangeDetectorRef,
public snackBarRef: MatSnackBarRef<TbSnackBarComponent>) {
this.notification = data.notification;
}
ngAfterViewInit() {
this.parentEl = this.data.parent.nativeElement;
this.snackBarContainerEl = this.elementRef.nativeElement.parentNode;
this.snackBarContainerEl.style.position = 'absolute';
this.updateContainerRect();
this.updatePosition(this.snackBarRef.containerInstance.snackBarConfig);
const snackBarComponent = this;
this.parentScrollSubscription = onParentScrollOrWindowResize(this.parentEl).subscribe(() => {
snackBarComponent.updateContainerRect();
});
}
updatePosition(config: MatSnackBarConfig) {
const isRtl = config.direction === 'rtl';
const isLeft = (config.horizontalPosition === 'left' ||
(config.horizontalPosition === 'start' && !isRtl) ||
(config.horizontalPosition === 'end' && isRtl));
const isRight = !isLeft && config.horizontalPosition !== 'center';
if (isLeft) {
this.snackBarContainerEl.style.justifyContent = 'flex-start';
} else if (isRight) {
this.snackBarContainerEl.style.justifyContent = 'flex-end';
} else {
this.snackBarContainerEl.style.justifyContent = 'center';
}
if (config.verticalPosition === 'top') {
this.snackBarContainerEl.style.alignItems = 'flex-start';
} else {
this.snackBarContainerEl.style.alignItems = 'flex-end';
}
}
ngOnDestroy() {
if (this.parentScrollSubscription) {
this.parentScrollSubscription.unsubscribe();
}
}
updateContainerRect() {
const viewportOffset = this.parentEl.getBoundingClientRect();
this.snackBarContainerEl.style.top = viewportOffset.top + 'px';
this.snackBarContainerEl.style.left = viewportOffset.left + 'px';
this.snackBarContainerEl.style.width = viewportOffset.width + 'px';
this.snackBarContainerEl.style.height = viewportOffset.height + 'px';
}
action(): void {
this.snackBarRef.dismissWithAction();
}
}