diff --git a/ui-ngx/src/app/shared/components/snack-bar-component.html b/ui-ngx/src/app/shared/components/snack-bar-component.html index 53f23fcf8a..573166fd1e 100644 --- a/ui-ngx/src/app/shared/components/snack-bar-component.html +++ b/ui-ngx/src/app/shared/components/snack-bar-component.html @@ -25,5 +25,5 @@ 'info-toast': notification.type === 'info' }">
- + diff --git a/ui-ngx/src/app/shared/components/snack-bar-component.scss b/ui-ngx/src/app/shared/components/snack-bar-component.scss index 6ef4191efb..8b211bffce 100644 --- a/ui-ngx/src/app/shared/components/snack-bar-component.scss +++ b/ui-ngx/src/app/shared/components/snack-bar-component.scss @@ -16,6 +16,33 @@ :host { display: inline-block; pointer-events: all; + &.toast-panel { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1000; + display: flex; + &.left { + justify-content: flex-start; + } + &.right { + justify-content: flex-end; + } + &.top { + align-items: flex-start; + } + &.bottom { + align-items: flex-end; + } + &.h-center { + justify-content: center; + } + &.v-center { + align-items: center; + } + } .tb-toast { box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12); color: #fff; diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index 1d59b86a54..fb704c74db 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -16,9 +16,9 @@ import { AfterViewInit, ChangeDetectorRef, - Component, ComponentRef, + Component, ComponentFactoryResolver, ComponentRef, Directive, - ElementRef, + ElementRef, HostBinding, Inject, Input, NgZone, @@ -34,8 +34,7 @@ import { BreakpointObserver } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { MatButton } from '@angular/material/button'; import Timeout = NodeJS.Timeout; -import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { PortalInjector } from '@angular/cdk/portal'; @Directive({ selector: '[tb-toast]' @@ -49,7 +48,6 @@ export class ToastDirective implements AfterViewInit, OnDestroy { private hideNotificationSubscription: Subscription = null; private snackBarRef: MatSnackBarRef = null; - private overlayRef: OverlayRef; private toastComponentRef: ComponentRef; private currentMessage: NotificationMessage = null; @@ -58,7 +56,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy { constructor(private elementRef: ElementRef, private viewContainerRef: ViewContainerRef, private notificationService: NotificationService, - private overlay: Overlay, + private componentFactoryResolver: ComponentFactoryResolver, private snackBar: MatSnackBar, private ngZone: NgZone, private breakpointObserver: BreakpointObserver, @@ -104,9 +102,11 @@ export class ToastDirective implements AfterViewInit, OnDestroy { if (this.snackBarRef) { this.snackBarRef.dismiss(); } - - const position = this.overlay.position(); - let panelClass = ['tb-toast-panel']; + if (this.toastComponentRef) { + this.viewContainerRef.detach(0); + this.toastComponentRef.destroy(); + } + let panelClass = ['tb-toast-panel', 'toast-panel']; if (notificationMessage.panelClass) { if (typeof notificationMessage.panelClass === 'string') { panelClass.push(notificationMessage.panelClass); @@ -114,46 +114,35 @@ export class ToastDirective implements AfterViewInit, OnDestroy { panelClass = panelClass.concat(notificationMessage.panelClass); } } - const overlayConfig = new OverlayConfig({ - panelClass, - backdropClass: 'cdk-overlay-transparent-backdrop', - hasBackdrop: false, - disposeOnNavigation: true - }); - let originX; - let originY; const horizontalPosition = notificationMessage.horizontalPosition || 'left'; const verticalPosition = notificationMessage.verticalPosition || 'top'; if (horizontalPosition === 'start' || horizontalPosition === 'left') { - originX = 'start'; + panelClass.push('left'); } else if (horizontalPosition === 'end' || horizontalPosition === 'right') { - originX = 'end'; + panelClass.push('right'); } else { - originX = 'center'; + panelClass.push('h-center'); } if (verticalPosition === 'top') { - originY = 'top'; + panelClass.push('top'); } else { - originY = 'bottom'; + panelClass.push('bottom'); } - const connectedPosition: ConnectedPosition = { - originX, - originY, - overlayX: originX, - overlayY: originY - }; - overlayConfig.positionStrategy = position.flexibleConnectedTo(this.elementRef) - .withPositions([connectedPosition]); - this.overlayRef = this.overlay.create(overlayConfig); + + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(TbSnackBarComponent); const data: ToastPanelData = { - notification: notificationMessage + notification: notificationMessage, + panelClass, + destroyToastComponent: () => { + this.viewContainerRef.detach(0); + this.toastComponentRef.destroy(); + } }; const injectionTokens = new WeakMap([ - [MAT_SNACK_BAR_DATA, data], - [OverlayRef, this.overlayRef] + [MAT_SNACK_BAR_DATA, data] ]); const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); - this.toastComponentRef = this.overlayRef.attach(new ComponentPortal(TbSnackBarComponent, this.viewContainerRef, injector)); + this.toastComponentRef = this.viewContainerRef.createComponent(componentFactory, 0, injector); this.cd.detectChanges(); if (notificationMessage.duration && notificationMessage.duration > 0) { @@ -173,7 +162,6 @@ export class ToastDirective implements AfterViewInit, OnDestroy { clearTimeout(this.dismissTimeout); this.dismissTimeout = null; } - this.overlayRef = null; this.toastComponentRef = null; this.currentMessage = null; }); @@ -181,43 +169,45 @@ export class ToastDirective implements AfterViewInit, OnDestroy { } private showSnackBar(notificationMessage: NotificationMessage, isGtSm: boolean) { - const data: ToastPanelData = { - notification: notificationMessage, - parent: this.elementRef - }; - const config: MatSnackBarConfig = { - horizontalPosition: notificationMessage.horizontalPosition || 'left', - verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'), - viewContainerRef: this.viewContainerRef, - duration: notificationMessage.duration, - panelClass: notificationMessage.panelClass, - data - }; this.ngZone.run(() => { if (this.snackBarRef) { this.snackBarRef.dismiss(); } + const data: ToastPanelData = { + notification: notificationMessage, + parent: this.elementRef, + panelClass: [], + destroyToastComponent: () => {} + }; + const config: MatSnackBarConfig = { + horizontalPosition: notificationMessage.horizontalPosition || 'left', + verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'), + viewContainerRef: this.viewContainerRef, + duration: notificationMessage.duration, + panelClass: notificationMessage.panelClass, + data + }; this.snackBarRef = this.snackBar.openFromComponent(TbSnackBarComponent, config); - }); - 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(); + if (notificationMessage.duration && notificationMessage.duration > 0 && notificationMessage.forceDismiss) { + if (this.dismissTimeout !== null) { + clearTimeout(this.dismissTimeout); + this.dismissTimeout = null; } - this.dismissTimeout = null; - }, notificationMessage.duration); - } - this.snackBarRef.afterDismissed().subscribe(() => { - 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); } - this.snackBarRef = null; - this.currentMessage = null; + this.snackBarRef.afterDismissed().subscribe(() => { + if (this.dismissTimeout !== null) { + clearTimeout(this.dismissTimeout); + this.dismissTimeout = null; + } + this.snackBarRef = null; + this.currentMessage = null; + }); }); } @@ -235,8 +225,9 @@ export class ToastDirective implements AfterViewInit, OnDestroy { } ngOnDestroy(): void { - if (this.overlayRef) { - this.overlayRef.dispose(); + if (this.toastComponentRef) { + this.viewContainerRef.detach(0); + this.toastComponentRef.destroy(); } if (this.notificationSubscription) { this.notificationSubscription.unsubscribe(); @@ -250,6 +241,8 @@ export class ToastDirective implements AfterViewInit, OnDestroy { interface ToastPanelData { notification: NotificationMessage; parent?: ElementRef; + panelClass: string[]; + destroyToastComponent: () => void; } import { @@ -288,6 +281,11 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy { @ViewChild('actionButton', {static: true}) actionButton: MatButton; + @HostBinding('class') + get panelClass(): string[] { + return this.data.panelClass; + } + private parentEl: HTMLElement; private snackBarContainerEl: HTMLElement; private parentScrollSubscription: Subscription = null; @@ -305,9 +303,7 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy { private data: ToastPanelData, private elementRef: ElementRef, @Optional() - private snackBarRef: MatSnackBarRef, - @Optional() - private overlayRef: OverlayRef) { + private snackBarRef: MatSnackBarRef) { this.animationState = !!this.snackBarRef ? 'default' : 'opened'; this.notification = data.notification; } @@ -319,9 +315,8 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy { this.snackBarContainerEl.style.position = 'absolute'; this.updateContainerRect(); this.updatePosition(this.snackBarRef.containerInstance.snackBarConfig); - const snackBarComponent = this; this.parentScrollSubscription = onParentScrollOrWindowResize(this.parentEl).subscribe(() => { - snackBarComponent.updateContainerRect(); + this.updateContainerRect(); }); } } @@ -373,7 +368,7 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy { const isFadeOut = (toState as ToastAnimationState) === 'closing'; const itFinished = this.animationState === 'closing'; if (isFadeOut && itFinished) { - this.overlayRef.dispose(); + this.data.destroyToastComponent(); } } }