Browse Source

Improve toast panel

pull/3378/head
Igor Kulikov 6 years ago
parent
commit
e5e103be98
  1. 2
      ui-ngx/src/app/shared/components/snack-bar-component.html
  2. 27
      ui-ngx/src/app/shared/components/snack-bar-component.scss
  3. 143
      ui-ngx/src/app/shared/components/toast.directive.ts

2
ui-ngx/src/app/shared/components/snack-bar-component.html

@ -25,5 +25,5 @@
'info-toast': notification.type === 'info' 'info-toast': notification.type === 'info'
}"> }">
<div class="toast-text" [innerHTML]="notification.message"></div> <div class="toast-text" [innerHTML]="notification.message"></div>
<button #actionButton mat-button (click)="action()">{{ 'action.close' | translate }}</button> <button #actionButton type="button" mat-button (click)="action()">{{ 'action.close' | translate }}</button>
</div> </div>

27
ui-ngx/src/app/shared/components/snack-bar-component.scss

@ -16,6 +16,33 @@
:host { :host {
display: inline-block; display: inline-block;
pointer-events: all; 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 { .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); 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; color: #fff;

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

@ -16,9 +16,9 @@
import { import {
AfterViewInit, ChangeDetectorRef, AfterViewInit, ChangeDetectorRef,
Component, ComponentRef, Component, ComponentFactoryResolver, ComponentRef,
Directive, Directive,
ElementRef, ElementRef, HostBinding,
Inject, Inject,
Input, Input,
NgZone, NgZone,
@ -34,8 +34,7 @@ import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { PortalInjector } from '@angular/cdk/portal';
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
@Directive({ @Directive({
selector: '[tb-toast]' selector: '[tb-toast]'
@ -49,7 +48,6 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
private hideNotificationSubscription: Subscription = null; private hideNotificationSubscription: Subscription = null;
private snackBarRef: MatSnackBarRef<any> = null; private snackBarRef: MatSnackBarRef<any> = null;
private overlayRef: OverlayRef;
private toastComponentRef: ComponentRef<TbSnackBarComponent>; private toastComponentRef: ComponentRef<TbSnackBarComponent>;
private currentMessage: NotificationMessage = null; private currentMessage: NotificationMessage = null;
@ -58,7 +56,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
constructor(private elementRef: ElementRef, constructor(private elementRef: ElementRef,
private viewContainerRef: ViewContainerRef, private viewContainerRef: ViewContainerRef,
private notificationService: NotificationService, private notificationService: NotificationService,
private overlay: Overlay, private componentFactoryResolver: ComponentFactoryResolver,
private snackBar: MatSnackBar, private snackBar: MatSnackBar,
private ngZone: NgZone, private ngZone: NgZone,
private breakpointObserver: BreakpointObserver, private breakpointObserver: BreakpointObserver,
@ -104,9 +102,11 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
if (this.snackBarRef) { if (this.snackBarRef) {
this.snackBarRef.dismiss(); this.snackBarRef.dismiss();
} }
if (this.toastComponentRef) {
const position = this.overlay.position(); this.viewContainerRef.detach(0);
let panelClass = ['tb-toast-panel']; this.toastComponentRef.destroy();
}
let panelClass = ['tb-toast-panel', 'toast-panel'];
if (notificationMessage.panelClass) { if (notificationMessage.panelClass) {
if (typeof notificationMessage.panelClass === 'string') { if (typeof notificationMessage.panelClass === 'string') {
panelClass.push(notificationMessage.panelClass); panelClass.push(notificationMessage.panelClass);
@ -114,46 +114,35 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
panelClass = panelClass.concat(notificationMessage.panelClass); 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 horizontalPosition = notificationMessage.horizontalPosition || 'left';
const verticalPosition = notificationMessage.verticalPosition || 'top'; const verticalPosition = notificationMessage.verticalPosition || 'top';
if (horizontalPosition === 'start' || horizontalPosition === 'left') { if (horizontalPosition === 'start' || horizontalPosition === 'left') {
originX = 'start'; panelClass.push('left');
} else if (horizontalPosition === 'end' || horizontalPosition === 'right') { } else if (horizontalPosition === 'end' || horizontalPosition === 'right') {
originX = 'end'; panelClass.push('right');
} else { } else {
originX = 'center'; panelClass.push('h-center');
} }
if (verticalPosition === 'top') { if (verticalPosition === 'top') {
originY = 'top'; panelClass.push('top');
} else { } else {
originY = 'bottom'; panelClass.push('bottom');
} }
const connectedPosition: ConnectedPosition = {
originX, const componentFactory = this.componentFactoryResolver.resolveComponentFactory(TbSnackBarComponent);
originY,
overlayX: originX,
overlayY: originY
};
overlayConfig.positionStrategy = position.flexibleConnectedTo(this.elementRef)
.withPositions([connectedPosition]);
this.overlayRef = this.overlay.create(overlayConfig);
const data: ToastPanelData = { const data: ToastPanelData = {
notification: notificationMessage notification: notificationMessage,
panelClass,
destroyToastComponent: () => {
this.viewContainerRef.detach(0);
this.toastComponentRef.destroy();
}
}; };
const injectionTokens = new WeakMap<any, any>([ const injectionTokens = new WeakMap<any, any>([
[MAT_SNACK_BAR_DATA, data], [MAT_SNACK_BAR_DATA, data]
[OverlayRef, this.overlayRef]
]); ]);
const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); 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(); this.cd.detectChanges();
if (notificationMessage.duration && notificationMessage.duration > 0) { if (notificationMessage.duration && notificationMessage.duration > 0) {
@ -173,7 +162,6 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
clearTimeout(this.dismissTimeout); clearTimeout(this.dismissTimeout);
this.dismissTimeout = null; this.dismissTimeout = null;
} }
this.overlayRef = null;
this.toastComponentRef = null; this.toastComponentRef = null;
this.currentMessage = null; this.currentMessage = null;
}); });
@ -181,43 +169,45 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
} }
private showSnackBar(notificationMessage: NotificationMessage, isGtSm: boolean) { 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(() => { this.ngZone.run(() => {
if (this.snackBarRef) { if (this.snackBarRef) {
this.snackBarRef.dismiss(); 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); this.snackBarRef = this.snackBar.openFromComponent(TbSnackBarComponent, config);
}); if (notificationMessage.duration && notificationMessage.duration > 0 && notificationMessage.forceDismiss) {
if (notificationMessage.duration && notificationMessage.duration > 0 && notificationMessage.forceDismiss) { if (this.dismissTimeout !== null) {
if (this.dismissTimeout !== null) { clearTimeout(this.dismissTimeout);
clearTimeout(this.dismissTimeout); this.dismissTimeout = null;
this.dismissTimeout = null;
}
this.dismissTimeout = setTimeout(() => {
if (this.snackBarRef) {
this.snackBarRef.instance.actionButton._elementRef.nativeElement.click();
} }
this.dismissTimeout = null; this.dismissTimeout = setTimeout(() => {
}, notificationMessage.duration); if (this.snackBarRef) {
} this.snackBarRef.instance.actionButton._elementRef.nativeElement.click();
this.snackBarRef.afterDismissed().subscribe(() => { }
if (this.dismissTimeout !== null) { this.dismissTimeout = null;
clearTimeout(this.dismissTimeout); }, notificationMessage.duration);
this.dismissTimeout = null;
} }
this.snackBarRef = null; this.snackBarRef.afterDismissed().subscribe(() => {
this.currentMessage = null; 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 { ngOnDestroy(): void {
if (this.overlayRef) { if (this.toastComponentRef) {
this.overlayRef.dispose(); this.viewContainerRef.detach(0);
this.toastComponentRef.destroy();
} }
if (this.notificationSubscription) { if (this.notificationSubscription) {
this.notificationSubscription.unsubscribe(); this.notificationSubscription.unsubscribe();
@ -250,6 +241,8 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
interface ToastPanelData { interface ToastPanelData {
notification: NotificationMessage; notification: NotificationMessage;
parent?: ElementRef; parent?: ElementRef;
panelClass: string[];
destroyToastComponent: () => void;
} }
import { import {
@ -288,6 +281,11 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy {
@ViewChild('actionButton', {static: true}) actionButton: MatButton; @ViewChild('actionButton', {static: true}) actionButton: MatButton;
@HostBinding('class')
get panelClass(): string[] {
return this.data.panelClass;
}
private parentEl: HTMLElement; private parentEl: HTMLElement;
private snackBarContainerEl: HTMLElement; private snackBarContainerEl: HTMLElement;
private parentScrollSubscription: Subscription = null; private parentScrollSubscription: Subscription = null;
@ -305,9 +303,7 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy {
private data: ToastPanelData, private data: ToastPanelData,
private elementRef: ElementRef, private elementRef: ElementRef,
@Optional() @Optional()
private snackBarRef: MatSnackBarRef<TbSnackBarComponent>, private snackBarRef: MatSnackBarRef<TbSnackBarComponent>) {
@Optional()
private overlayRef: OverlayRef) {
this.animationState = !!this.snackBarRef ? 'default' : 'opened'; this.animationState = !!this.snackBarRef ? 'default' : 'opened';
this.notification = data.notification; this.notification = data.notification;
} }
@ -319,9 +315,8 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy {
this.snackBarContainerEl.style.position = 'absolute'; this.snackBarContainerEl.style.position = 'absolute';
this.updateContainerRect(); this.updateContainerRect();
this.updatePosition(this.snackBarRef.containerInstance.snackBarConfig); this.updatePosition(this.snackBarRef.containerInstance.snackBarConfig);
const snackBarComponent = this;
this.parentScrollSubscription = onParentScrollOrWindowResize(this.parentEl).subscribe(() => { 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 isFadeOut = (toState as ToastAnimationState) === 'closing';
const itFinished = this.animationState === 'closing'; const itFinished = this.animationState === 'closing';
if (isFadeOut && itFinished) { if (isFadeOut && itFinished) {
this.overlayRef.dispose(); this.data.destroyToastComponent();
} }
} }
} }

Loading…
Cancel
Save