|
|
|
@ -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<any> = null; |
|
|
|
private overlayRef: OverlayRef; |
|
|
|
private toastComponentRef: ComponentRef<TbSnackBarComponent>; |
|
|
|
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<any, any>([ |
|
|
|
[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<TbSnackBarComponent>, |
|
|
|
@Optional() |
|
|
|
private overlayRef: OverlayRef) { |
|
|
|
private snackBarRef: MatSnackBarRef<TbSnackBarComponent>) { |
|
|
|
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(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|