mk-magic-alerts
Version:
Flying in alerts for Angular applications
256 lines (243 loc) • 17.3 kB
JavaScript
import * as i0 from '@angular/core';
import { Pipe, Injectable, Input, Component, createComponent } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { BehaviorSubject, auditTime, Subject, fromEvent, timer, race } from 'rxjs';
import { takeUntil, repeat, first, tap } from 'rxjs/operators';
import { trigger, state, transition, style, sequence, query, animate, group } from '@angular/animations';
const AlertState = {
DISPLAY: 'display',
DISMISS: 'dismiss',
DISMISSED: 'dismissed',
};
class NewlineAndTabsPipe {
transform(value) {
return value?.toString().replace(/(?:\r\n|\r|\n)/g, '<br />').replace(/(?:\t)/g, ' ') ?? '';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: NewlineAndTabsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.0.3", ngImport: i0, type: NewlineAndTabsPipe, isStandalone: true, name: "newlineAndTabs" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: NewlineAndTabsPipe, decorators: [{
type: Pipe,
args: [{
name: 'newlineAndTabs',
standalone: true
}]
}] });
const alertAnimations = [
trigger('state', [
state('void', style({
height: '0',
width: '0',
'padding-top': '0',
'padding-bottom': '0',
'margin-top': '5px',
'margin-bottom': '0',
opacity: '0',
})),
state(AlertState.DISPLAY, style({
height: '*',
'padding-top': '*',
'padding-bottom': '*',
'margin-top': '5px',
'margin-bottom': '0',
})),
state(AlertState.DISMISS, style({
height: '0',
width: '0',
'padding-top': '0',
'padding-bottom': '0',
'margin-top': '5px',
'margin-bottom': '0',
'border-width': '0',
opacity: '0',
})),
state(AlertState.DISMISSED, style({
height: '0',
width: '0',
'padding-top': '0',
'padding-bottom': '0',
'margin-top': '0',
'margin-bottom': '0',
'border-width': '0',
opacity: '0',
})),
transition('* => display',
// sequence = sequential actions:
sequence([
query('.alert-text', [style({ opacity: 0 })]),
animate('0.3s ease'),
query('.alert-text', [animate('0.1s', style({ opacity: 1 }))])
])),
transition('display => dismiss',
// group = parallel actions:
group([
query('.alert-text', [animate('0.05s', style({ opacity: 0 }))]),
animate('0.5s ease')
])),
transition('dismiss => dismissed',
// group = parallel actions:
group([
query('.alert-text', [animate('0.05s', style({ opacity: 0 }))]),
animate('0.3s ease')
]))
])
];
class Alert {
constructor(text, type, dismissTime) {
this.text = text;
this.type = type;
this.dismissTimeInMillis = dismissTime;
this.state = AlertState.DISPLAY;
}
}
class AlertsStore {
constructor() {
this.alertsSubject = new BehaviorSubject([]);
// Added 'auditTime(100)' to prevent error 'NG0100: Expression has changed after it was checked'
this.alerts$ = this.alertsSubject.asObservable().pipe(auditTime(100));
this.dismissAllSubject = new Subject();
this.dismissAll$ = this.dismissAllSubject.asObservable();
}
dismissAll() {
this.dismissAllSubject.next();
}
addAlert(text, type, dismissTime) {
// Remove already dismissed alerts
// filter() makes a shallow copy of the array (a new array, but pointing to the same objects)
const activeAlerts = this.alertsSubject.getValue().filter((m) => m.state !== AlertState.DISMISSED);
// Create and add new message:
activeAlerts.push(new Alert(text, type, dismissTime));
// Trigger Observable to display alerts:
this.alertsSubject.next(activeAlerts);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertsStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertsStore, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertsStore, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
class AlertComponent {
constructor(elementRef, alertsStore, cdRef) {
this.elementRef = elementRef;
this.alertsStore = alertsStore;
this.cdRef = cdRef;
this.dismissTimeInMillis = 2_147_483_647;
this.destroy$ = new Subject();
}
ngOnInit() {
const mouseenter$ = fromEvent(this.elementRef.nativeElement, 'mouseenter');
const mouseleave$ = fromEvent(this.elementRef.nativeElement, 'mouseleave');
// Observable that allows closing of the alert by user-click:
const dismissalByUser$ = fromEvent(this.elementRef.nativeElement, 'mouseup');
// If dismissal is requested programmatically from alerts-service:
const dismissalByService$ = this.alertsStore.dismissAll$;
// Observable that closes the alert after a given time,
// unless user hovers over the alert with a cursor => 'takeUntil()' and 'repeat()'
const dismissalAfterTimeout$ = timer(this.dismissTimeInMillis).pipe(takeUntil(mouseenter$), repeat({ delay: () => mouseleave$ })); // on mouseleave: Resubscribe to source (here: 'timer()')
race([dismissalByUser$, dismissalByService$, dismissalAfterTimeout$])
.pipe(first(), // after first emission, unsubscribe also from the winning observable
tap(() => this.setDismissalStart()), takeUntil(this.destroy$))
.subscribe();
}
/* Triggers the animated disappearing of the alert */
setDismissalStart() {
this.alertParams.state = AlertState.DISMISS;
this.cdRef.detectChanges();
}
/* Is evoked at the end of the animated disappearing of the alert */
onDismissalCompleted(event) {
if (event.fromState === AlertState.DISPLAY && event.toState === AlertState.DISMISS) {
this.alertParams.state = AlertState.DISMISSED;
this.cdRef.detectChanges();
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.unsubscribe();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertComponent, deps: [{ token: i0.ElementRef }, { token: AlertsStore }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: AlertComponent, isStandalone: true, selector: "app-alert", inputs: { alertParams: "alertParams", dismissTimeInMillis: "dismissTimeInMillis" }, ngImport: i0, template: "@if (alertParams) {\n\t<div class=\"clickable-area\">\n\t\t<div class=\"alert-container alert-{{ alertParams.type }}\" [@state]=\"alertParams.state\" (@state.done)=\"onDismissalCompleted($event)\">\n\t\t\t\n\t\t\t<div class=\"alert-text\" [innerHtml]=\"alertParams.text | newlineAndTabs\"></div>\n\t\n\t\t\t<div class=\"btn-dismiss\">\n\t\t\t\t<i class=\"fa fa-times\"></i>\n\t\t\t</div>\n\t\n\t\t</div>\n\t</div>\n}\n", styles: [".clickable-area{cursor:pointer}.clickable-area .alert-container{position:relative;padding:5px 15px;margin:0;border-radius:4px;border-width:1px;border-style:solid;overflow:hidden;box-shadow:0 4px 8px #00000014,0 6px 20px #00000014;z-index:1020!important;display:flex;justify-content:space-between;align-items:center}.clickable-area .alert-container .alert-text{font:400 16px/20px Roboto}.clickable-area .alert-container .btn-dismiss{align-self:flex-start;margin-top:1px}.clickable-area .alert-info{color:#004085;border-color:#004085;background-color:#b8daff}.clickable-area .alert-info .btn-dismiss:hover{text-shadow:0 0 10px #3366ff,0 0 20px #3366ff}.clickable-area .alert-success{color:#155724;border-color:#155724;background-color:#c3e6cb}.clickable-area .alert-success .btn-dismiss:hover{text-shadow:0 0 10px #30a24d,0 0 20px #30a24d}.clickable-area .alert-warning{color:#856404;border-color:#856404;background-color:#ffeeba}.clickable-area .alert-warning .btn-dismiss:hover{text-shadow:0 0 10px #b38600,0 0 20px #b38600}.clickable-area .alert-error{color:#721c24;border-color:#721c24;background-color:#f8d7da}.clickable-area .alert-error .btn-dismiss:hover{text-shadow:0 0 10px #ff4c59,0 0 20px #ff4c59}\n"], dependencies: [{ kind: "pipe", type: NewlineAndTabsPipe, name: "newlineAndTabs" }], animations: alertAnimations }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertComponent, decorators: [{
type: Component,
args: [{ selector: 'app-alert', animations: alertAnimations, imports: [NewlineAndTabsPipe], template: "@if (alertParams) {\n\t<div class=\"clickable-area\">\n\t\t<div class=\"alert-container alert-{{ alertParams.type }}\" [@state]=\"alertParams.state\" (@state.done)=\"onDismissalCompleted($event)\">\n\t\t\t\n\t\t\t<div class=\"alert-text\" [innerHtml]=\"alertParams.text | newlineAndTabs\"></div>\n\t\n\t\t\t<div class=\"btn-dismiss\">\n\t\t\t\t<i class=\"fa fa-times\"></i>\n\t\t\t</div>\n\t\n\t\t</div>\n\t</div>\n}\n", styles: [".clickable-area{cursor:pointer}.clickable-area .alert-container{position:relative;padding:5px 15px;margin:0;border-radius:4px;border-width:1px;border-style:solid;overflow:hidden;box-shadow:0 4px 8px #00000014,0 6px 20px #00000014;z-index:1020!important;display:flex;justify-content:space-between;align-items:center}.clickable-area .alert-container .alert-text{font:400 16px/20px Roboto}.clickable-area .alert-container .btn-dismiss{align-self:flex-start;margin-top:1px}.clickable-area .alert-info{color:#004085;border-color:#004085;background-color:#b8daff}.clickable-area .alert-info .btn-dismiss:hover{text-shadow:0 0 10px #3366ff,0 0 20px #3366ff}.clickable-area .alert-success{color:#155724;border-color:#155724;background-color:#c3e6cb}.clickable-area .alert-success .btn-dismiss:hover{text-shadow:0 0 10px #30a24d,0 0 20px #30a24d}.clickable-area .alert-warning{color:#856404;border-color:#856404;background-color:#ffeeba}.clickable-area .alert-warning .btn-dismiss:hover{text-shadow:0 0 10px #b38600,0 0 20px #b38600}.clickable-area .alert-error{color:#721c24;border-color:#721c24;background-color:#f8d7da}.clickable-area .alert-error .btn-dismiss:hover{text-shadow:0 0 10px #ff4c59,0 0 20px #ff4c59}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: AlertsStore }, { type: i0.ChangeDetectorRef }], propDecorators: { alertParams: [{
type: Input,
args: [{ required: true }]
}], dismissTimeInMillis: [{
type: Input
}] } });
class AlertsComponent {
constructor(alertsStore) {
this.alertsStore = alertsStore;
this.alerts$ = this.alertsStore.alerts$;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertsComponent, deps: [{ token: AlertsStore }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: AlertsComponent, isStandalone: true, selector: "magic-alerts", ngImport: i0, template: "\n\n<link href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\" rel=\"stylesheet\">\n<link href=\"https://use.fontawesome.com/releases/v5.3.1/css/all.css\" rel=\"stylesheet\" integrity=\"sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU\" crossorigin=\"anonymous\">\n\n<!-- Alerts Container -->\n<div id=\"mk-magic-messages\" class=\"alerts-container\">\n\t@for(alertParams of alerts$ | async; track alertParams) {\n\t\t<app-alert\n\t\t\t[alertParams]=\"alertParams\"\n\t\t\t[dismissTimeInMillis]=\"alertParams.dismissTimeInMillis\">\n\t\t</app-alert>\n\t}\n</div>", styles: ["#mk-magic-messages.alerts-container{position:fixed;top:0;left:15px;right:15px;z-index:20000}@media (min-width: 768px){#mk-magic-messages.alerts-container{left:10%;right:10%}}@media (min-width: 992px){#mk-magic-messages.alerts-container{left:20%;right:20%}}@media (min-width: 1200px){#mk-magic-messages.alerts-container{left:25%;right:25%}}@media (min-width: 1500px){#mk-magic-messages.alerts-container{left:30%;right:30%}}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "app-alert", inputs: ["alertParams", "dismissTimeInMillis"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertsComponent, decorators: [{
type: Component,
args: [{ selector: 'magic-alerts', imports: [AlertComponent, AsyncPipe], template: "\n\n<link href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\" rel=\"stylesheet\">\n<link href=\"https://use.fontawesome.com/releases/v5.3.1/css/all.css\" rel=\"stylesheet\" integrity=\"sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU\" crossorigin=\"anonymous\">\n\n<!-- Alerts Container -->\n<div id=\"mk-magic-messages\" class=\"alerts-container\">\n\t@for(alertParams of alerts$ | async; track alertParams) {\n\t\t<app-alert\n\t\t\t[alertParams]=\"alertParams\"\n\t\t\t[dismissTimeInMillis]=\"alertParams.dismissTimeInMillis\">\n\t\t</app-alert>\n\t}\n</div>", styles: ["#mk-magic-messages.alerts-container{position:fixed;top:0;left:15px;right:15px;z-index:20000}@media (min-width: 768px){#mk-magic-messages.alerts-container{left:10%;right:10%}}@media (min-width: 992px){#mk-magic-messages.alerts-container{left:20%;right:20%}}@media (min-width: 1200px){#mk-magic-messages.alerts-container{left:25%;right:25%}}@media (min-width: 1500px){#mk-magic-messages.alerts-container{left:30%;right:30%}}\n"] }]
}], ctorParameters: () => [{ type: AlertsStore }] });
class AlertsService {
constructor(alertsStore, applicationRef, injector) {
this.alertsStore = alertsStore;
this.applicationRef = applicationRef;
this.injector = injector;
this.initializeAlertsComponent();
}
initializeAlertsComponent() {
// Create a div element and append it to the document body
const hostElement = document.createElement('div');
document.body.appendChild(hostElement);
// Create AlertsComponent and attach it to the DOM
this.alertsComponentRef = createComponent(AlertsComponent, {
hostElement,
environmentInjector: this.applicationRef.injector,
elementInjector: this.injector
});
// Attach the AlertsComponent to the application
this.applicationRef.attachView(this.alertsComponentRef.hostView);
}
showInfo(text, dismissTimeInMillis = 10_000) {
this.alertsStore.addAlert(text, 'info', dismissTimeInMillis);
}
showSuccess(text, dismissTimeInMillis = 4_000) {
this.alertsStore.addAlert(text, 'success', dismissTimeInMillis);
}
showWarning(text, dismissTimeInMillis = 10_000) {
this.alertsStore.addAlert(text, 'warning', dismissTimeInMillis);
}
showError(text, dismissTimeInMillis = 2_147_483_647) {
this.alertsStore.addAlert(text, 'error', dismissTimeInMillis);
}
clear() {
this.alertsStore.dismissAll();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertsService, deps: [{ token: AlertsStore }, { token: i0.ApplicationRef }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertsService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: AlertsService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: AlertsStore }, { type: i0.ApplicationRef }, { type: i0.Injector }] });
class MockAlertsService {
showInfo(text, dismissTimeInMillis = 10_000) { }
showSuccess(text, dismissTimeInMillis = 4_000) { }
showWarning(text, dismissTimeInMillis = 10_000) { }
showError(text, dismissTimeInMillis = 2_147_483_647) { }
clear() { }
}
/*
* Public API Surface of mk-magic-alerts
*/
/**
* Generated bundle index. Do not edit.
*/
export { AlertsService, MockAlertsService };
//# sourceMappingURL=mk-magic-alerts.mjs.map