]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | import { Injectable } from '@angular/core'; |
2 | ||
f67539c2 | 3 | import _ from 'lodash'; |
494da23a | 4 | import { IndividualConfig, ToastrService } from 'ngx-toastr'; |
9f95a23c | 5 | import { BehaviorSubject, Subject } from 'rxjs'; |
11fdf7f2 TL |
6 | |
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'; | |
11fdf7f2 TL |
11 | import { TaskMessageService } from './task-message.service'; |
12 | ||
13 | @Injectable({ | |
81eedcae | 14 | providedIn: 'root' |
11fdf7f2 TL |
15 | }) |
16 | export class NotificationService { | |
17 | private hideToasties = false; | |
18 | ||
9f95a23c | 19 | // Data observable |
11fdf7f2 | 20 | private dataSource = new BehaviorSubject<CdNotification[]>([]); |
11fdf7f2 TL |
21 | data$ = this.dataSource.asObservable(); |
22 | ||
9f95a23c TL |
23 | // Sidebar observable |
24 | sidebarSubject = new Subject(); | |
25 | ||
81eedcae TL |
26 | private queued: CdNotificationConfig[] = []; |
27 | private queuedTimeoutId: number; | |
11fdf7f2 TL |
28 | KEY = 'cdNotifications'; |
29 | ||
30 | constructor( | |
494da23a | 31 | public toastr: ToastrService, |
11fdf7f2 TL |
32 | private taskMessageService: TaskMessageService, |
33 | private cdDatePipe: CdDatePipe | |
34 | ) { | |
35 | const stringNotifications = localStorage.getItem(this.KEY); | |
36 | let notifications: CdNotification[] = []; | |
37 | ||
38 | if (_.isString(stringNotifications)) { | |
39 | notifications = JSON.parse(stringNotifications, (_key, value) => { | |
40 | if (_.isPlainObject(value)) { | |
41 | return _.assign(new CdNotification(), value); | |
42 | } | |
43 | return value; | |
44 | }); | |
45 | } | |
46 | ||
47 | this.dataSource.next(notifications); | |
48 | } | |
49 | ||
50 | /** | |
51 | * Removes all current saved notifications | |
52 | */ | |
53 | removeAll() { | |
54 | localStorage.removeItem(this.KEY); | |
55 | this.dataSource.next([]); | |
56 | } | |
57 | ||
9f95a23c TL |
58 | /** |
59 | * Removes a single saved notifications | |
60 | */ | |
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)); | |
66 | } | |
67 | ||
11fdf7f2 TL |
68 | /** |
69 | * Method used for saving a shown notification (check show() method). | |
70 | */ | |
71 | save(notification: CdNotification) { | |
72 | const recent = this.dataSource.getValue(); | |
73 | recent.push(notification); | |
9f95a23c | 74 | recent.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1)); |
11fdf7f2 | 75 | while (recent.length > 10) { |
9f95a23c | 76 | recent.pop(); |
11fdf7f2 TL |
77 | } |
78 | this.dataSource.next(recent); | |
79 | localStorage.setItem(this.KEY, JSON.stringify(recent)); | |
80 | } | |
81 | ||
11fdf7f2 TL |
82 | /** |
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. | |
91 | */ | |
92 | show( | |
93 | type: NotificationType, | |
94 | title: string, | |
95 | message?: string, | |
494da23a | 96 | options?: any | IndividualConfig, |
11fdf7f2 TL |
97 | application?: string |
98 | ): number; | |
99 | show(config: CdNotificationConfig | (() => CdNotificationConfig)): number; | |
100 | show( | |
101 | arg: NotificationType | CdNotificationConfig | (() => CdNotificationConfig), | |
102 | title?: string, | |
103 | message?: string, | |
494da23a | 104 | options?: any | IndividualConfig, |
11fdf7f2 TL |
105 | application?: string |
106 | ): number { | |
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; | |
113 | } else { | |
114 | config = new CdNotificationConfig( | |
115 | arg as NotificationType, | |
116 | title, | |
117 | message, | |
118 | options, | |
119 | application | |
120 | ); | |
121 | } | |
81eedcae TL |
122 | this.queueToShow(config); |
123 | }, 10); | |
124 | } | |
125 | ||
126 | private queueToShow(config: CdNotificationConfig) { | |
127 | this.cancel(this.queuedTimeoutId); | |
128 | if (!this.queued.find((c) => _.isEqual(c, config))) { | |
129 | this.queued.push(config); | |
130 | } | |
131 | this.queuedTimeoutId = window.setTimeout(() => { | |
132 | this.showQueued(); | |
133 | }, 500); | |
134 | } | |
135 | ||
136 | private showQueued() { | |
137 | this.getUnifiedTitleQueue().forEach((config) => { | |
11fdf7f2 | 138 | const notification = new CdNotification(config); |
9f95a23c TL |
139 | |
140 | if (!notification.isFinishedTask) { | |
141 | this.save(notification); | |
142 | } | |
11fdf7f2 | 143 | this.showToasty(notification); |
81eedcae TL |
144 | }); |
145 | } | |
146 | ||
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>'; | |
152 | } | |
153 | return config; | |
154 | }); | |
155 | } | |
156 | ||
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] = []; | |
163 | } | |
164 | byTitle[config.title].push(config); | |
165 | } | |
166 | return byTitle; | |
11fdf7f2 TL |
167 | } |
168 | ||
169 | private showToasty(notification: CdNotification) { | |
170 | // Exit immediately if no toasty should be displayed. | |
171 | if (this.hideToasties) { | |
172 | return; | |
173 | } | |
174 | this.toastr[['error', 'info', 'success'][notification.type]]( | |
175 | (notification.message ? notification.message + '<br>' : '') + | |
176 | this.renderTimeAndApplicationHtml(notification), | |
177 | notification.title, | |
178 | notification.options | |
179 | ); | |
180 | } | |
181 | ||
182 | renderTimeAndApplicationHtml(notification: CdNotification): string { | |
183 | return `<small class="date">${this.cdDatePipe.transform( | |
184 | notification.timestamp | |
9f95a23c | 185 | )}</small><i class="float-right custom-icon ${notification.applicationClass}" title="${ |
11fdf7f2 TL |
186 | notification.application |
187 | }"></i>`; | |
188 | } | |
189 | ||
190 | notifyTask(finishedTask: FinishedTask, success: boolean = true): number { | |
9f95a23c TL |
191 | const notification = this.finishedTaskToNotification(finishedTask, success); |
192 | notification.isFinishedTask = true; | |
193 | return this.show(notification); | |
194 | } | |
195 | ||
196 | finishedTaskToNotification( | |
197 | finishedTask: FinishedTask, | |
198 | success: boolean = true | |
199 | ): CdNotificationConfig { | |
11fdf7f2 TL |
200 | let notification: CdNotificationConfig; |
201 | if (finishedTask.success && success) { | |
202 | notification = new CdNotificationConfig( | |
203 | NotificationType.success, | |
204 | this.taskMessageService.getSuccessTitle(finishedTask) | |
205 | ); | |
206 | } else { | |
207 | notification = new CdNotificationConfig( | |
208 | NotificationType.error, | |
209 | this.taskMessageService.getErrorTitle(finishedTask), | |
210 | this.taskMessageService.getErrorMessage(finishedTask) | |
211 | ); | |
212 | } | |
9f95a23c TL |
213 | notification.isFinishedTask = true; |
214 | ||
215 | return notification; | |
11fdf7f2 TL |
216 | } |
217 | ||
218 | /** | |
219 | * Prevent the notification from being shown. | |
220 | * @param {number} timeoutId A number representing the ID of the timeout to be canceled. | |
221 | */ | |
9f95a23c | 222 | cancel(timeoutId: number) { |
11fdf7f2 TL |
223 | window.clearTimeout(timeoutId); |
224 | } | |
225 | ||
226 | /** | |
227 | * Suspend showing the notification toasties. | |
228 | * @param {boolean} suspend Set to ``true`` to disable/hide toasties. | |
229 | */ | |
230 | suspendToasties(suspend: boolean) { | |
231 | this.hideToasties = suspend; | |
232 | } | |
9f95a23c TL |
233 | |
234 | toggleSidebar(forceClose = false) { | |
235 | this.sidebarSubject.next(forceClose); | |
236 | } | |
11fdf7f2 | 237 | } |