1 import { Injectable } from '@angular/core';
3 import _ from 'lodash';
4 import { IndividualConfig, ToastrService } from 'ngx-toastr';
5 import { BehaviorSubject, Subject } from 'rxjs';
7 import { NotificationType } from '../enum/notification-type.enum';
8 import { CdNotification, CdNotificationConfig } from '../models/cd-notification';
9 import { FinishedTask } from '../models/finished-task';
10 import { CdDatePipe } from '../pipes/cd-date.pipe';
11 import { TaskMessageService } from './task-message.service';
16 export class NotificationService {
17 private hideToasties = false;
20 private dataSource = new BehaviorSubject<CdNotification[]>([]);
21 data$ = this.dataSource.asObservable();
24 sidebarSubject = new Subject();
26 private queued: CdNotificationConfig[] = [];
27 private queuedTimeoutId: number;
28 KEY = 'cdNotifications';
31 public toastr: ToastrService,
32 private taskMessageService: TaskMessageService,
33 private cdDatePipe: CdDatePipe
35 const stringNotifications = localStorage.getItem(this.KEY);
36 let notifications: CdNotification[] = [];
38 if (_.isString(stringNotifications)) {
39 notifications = JSON.parse(stringNotifications, (_key, value) => {
40 if (_.isPlainObject(value)) {
41 return _.assign(new CdNotification(), value);
47 this.dataSource.next(notifications);
51 * Removes all current saved notifications
54 localStorage.removeItem(this.KEY);
55 this.dataSource.next([]);
59 * Removes a single saved notifications
61 remove(index: number) {
62 const recent = this.dataSource.getValue();
63 recent.splice(index, 1);
64 this.dataSource.next(recent);
65 localStorage.setItem(this.KEY, JSON.stringify(recent));
69 * Method used for saving a shown notification (check show() method).
71 save(notification: CdNotification) {
72 const recent = this.dataSource.getValue();
73 recent.push(notification);
74 recent.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1));
75 while (recent.length > 10) {
78 this.dataSource.next(recent);
79 localStorage.setItem(this.KEY, JSON.stringify(recent));
83 * Method for showing a notification.
84 * @param {NotificationType} type toastr type
85 * @param {string} title
86 * @param {string} [message] The message to be displayed. Note, use this field
87 * for error notifications only.
88 * @param {*} [options] toastr compatible options, used when creating a toastr
89 * @param {string} [application] Only needed if notification comes from an external application
90 * @returns The timeout ID that is set to be able to cancel the notification.
93 type: NotificationType,
96 options?: any | IndividualConfig,
99 show(config: CdNotificationConfig | (() => CdNotificationConfig)): number;
101 arg: NotificationType | CdNotificationConfig | (() => CdNotificationConfig),
104 options?: any | IndividualConfig,
107 return window.setTimeout(() => {
108 let config: CdNotificationConfig;
109 if (_.isFunction(arg)) {
110 config = arg() as CdNotificationConfig;
111 } else if (_.isObject(arg)) {
112 config = arg as CdNotificationConfig;
114 config = new CdNotificationConfig(
115 arg as NotificationType,
122 this.queueToShow(config);
126 private queueToShow(config: CdNotificationConfig) {
127 this.cancel(this.queuedTimeoutId);
128 if (!this.queued.find((c) => _.isEqual(c, config))) {
129 this.queued.push(config);
131 this.queuedTimeoutId = window.setTimeout(() => {
136 private showQueued() {
137 this.getUnifiedTitleQueue().forEach((config) => {
138 const notification = new CdNotification(config);
140 if (!notification.isFinishedTask) {
141 this.save(notification);
143 this.showToasty(notification);
147 private getUnifiedTitleQueue(): CdNotificationConfig[] {
148 return Object.values(this.queueShiftByTitle()).map((configs) => {
149 const config = configs[0];
150 if (configs.length > 1) {
151 config.message = '<ul>' + configs.map((c) => `<li>${c.message}</li>`).join('') + '</ul>';
157 private queueShiftByTitle(): { [key: string]: CdNotificationConfig[] } {
158 const byTitle: { [key: string]: CdNotificationConfig[] } = {};
159 let config: CdNotificationConfig;
160 while ((config = this.queued.shift())) {
161 if (!byTitle[config.title]) {
162 byTitle[config.title] = [];
164 byTitle[config.title].push(config);
169 private showToasty(notification: CdNotification) {
170 // Exit immediately if no toasty should be displayed.
171 if (this.hideToasties) {
174 this.toastr[['error', 'info', 'success'][notification.type]](
175 (notification.message ? notification.message + '<br>' : '') +
176 this.renderTimeAndApplicationHtml(notification),
182 renderTimeAndApplicationHtml(notification: CdNotification): string {
183 return `<small class="date">${this.cdDatePipe.transform(
184 notification.timestamp
185 )}</small><i class="float-right custom-icon ${notification.applicationClass}" title="${
186 notification.application
190 notifyTask(finishedTask: FinishedTask, success: boolean = true): number {
191 const notification = this.finishedTaskToNotification(finishedTask, success);
192 notification.isFinishedTask = true;
193 return this.show(notification);
196 finishedTaskToNotification(
197 finishedTask: FinishedTask,
198 success: boolean = true
199 ): CdNotificationConfig {
200 let notification: CdNotificationConfig;
201 if (finishedTask.success && success) {
202 notification = new CdNotificationConfig(
203 NotificationType.success,
204 this.taskMessageService.getSuccessTitle(finishedTask)
207 notification = new CdNotificationConfig(
208 NotificationType.error,
209 this.taskMessageService.getErrorTitle(finishedTask),
210 this.taskMessageService.getErrorMessage(finishedTask)
213 notification.isFinishedTask = true;
219 * Prevent the notification from being shown.
220 * @param {number} timeoutId A number representing the ID of the timeout to be canceled.
222 cancel(timeoutId: number) {
223 window.clearTimeout(timeoutId);
227 * Suspend showing the notification toasties.
228 * @param {boolean} suspend Set to ``true`` to disable/hide toasties.
230 suspendToasties(suspend: boolean) {
231 this.hideToasties = suspend;
234 toggleSidebar(forceClose = false) {
235 this.sidebarSubject.next(forceClose);