2019-08-09 19:13:18 +03:00
|
|
|
///
|
2020-02-20 10:26:43 +02:00
|
|
|
/// Copyright © 2016-2020 The Thingsboard Authors
|
2019-08-09 19:13:18 +03:00
|
|
|
///
|
|
|
|
|
/// 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-04-27 09:27:14 +03:00
|
|
|
AfterViewInit,
|
|
|
|
|
ChangeDetectorRef,
|
2019-08-09 19:13:18 +03:00
|
|
|
Component,
|
|
|
|
|
Directive,
|
|
|
|
|
ElementRef,
|
2020-04-27 09:27:14 +03:00
|
|
|
Inject,
|
|
|
|
|
Input,
|
|
|
|
|
NgZone,
|
|
|
|
|
OnDestroy,
|
|
|
|
|
ViewChild,
|
2019-08-09 19:13:18 +03:00
|
|
|
ViewContainerRef
|
|
|
|
|
} from '@angular/core';
|
2020-02-10 13:15:29 +02:00
|
|
|
import { MAT_SNACK_BAR_DATA, MatSnackBar, MatSnackBarConfig, MatSnackBarRef } from '@angular/material/snack-bar';
|
2019-08-09 19:13:18 +03:00
|
|
|
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 { MatButton } from '@angular/material/button';
|
2020-04-27 09:27:14 +03:00
|
|
|
import Timeout = NodeJS.Timeout;
|
2019-08-09 19:13:18 +03:00
|
|
|
|
|
|
|
|
@Directive({
|
|
|
|
|
selector: '[tb-toast]'
|
|
|
|
|
})
|
|
|
|
|
export class ToastDirective implements AfterViewInit, OnDestroy {
|
|
|
|
|
|
|
|
|
|
@Input()
|
|
|
|
|
toastTarget = 'root';
|
|
|
|
|
|
|
|
|
|
private notificationSubscription: Subscription = null;
|
2019-08-28 16:02:27 +03:00
|
|
|
private hideNotificationSubscription: Subscription = null;
|
2019-08-09 19:13:18 +03:00
|
|
|
|
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;
|
|
|
|
|
|
2019-08-09 19:13:18 +03:00
|
|
|
constructor(public elementRef: ElementRef,
|
|
|
|
|
public viewContainerRef: ViewContainerRef,
|
|
|
|
|
private notificationService: NotificationService,
|
|
|
|
|
public snackBar: MatSnackBar,
|
2020-01-24 19:35:10 +02:00
|
|
|
private ngZone: NgZone,
|
2019-08-09 19:13:18 +03:00
|
|
|
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
|
|
|
});
|
2019-08-09 19:13:18 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
2019-08-28 16:02:27 +03: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();
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-08-28 16:02:27 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
2019-08-09 19:13:18 +03:00
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-09 19:13:18 +03:00
|
|
|
ngOnDestroy(): void {
|
|
|
|
|
if (this.notificationSubscription) {
|
|
|
|
|
this.notificationSubscription.unsubscribe();
|
|
|
|
|
}
|
2019-08-28 16:02:27 +03:00
|
|
|
if (this.hideNotificationSubscription) {
|
|
|
|
|
this.hideNotificationSubscription.unsubscribe();
|
|
|
|
|
}
|
2019-08-09 19:13:18 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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;
|
|
|
|
|
|
2019-08-09 19:13:18 +03:00
|
|
|
private parentEl: HTMLElement;
|
2020-02-10 13:04:56 +02:00
|
|
|
public snackBarContainerEl: HTMLElement;
|
2019-08-09 19:13:18 +03:00
|
|
|
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,
|
2019-08-09 19:13:18 +03:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|