Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, IStatusMessageChangeEvent, StatusMessageChangeType, IStatusMessageViewItem } from '../../../common/notifications.js';
7
import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from '../../../services/statusbar/browser/statusbar.js';
8
import { Disposable, IDisposable, dispose } from '../../../../base/common/lifecycle.js';
9
import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from './notificationsCommands.js';
10
import { localize } from '../../../../nls.js';
11
import { INotificationService, NotificationsFilter } from '../../../../platform/notification/common/notification.js';
12
13
export class NotificationsStatus extends Disposable {
14
15
private notificationsCenterStatusItem: IStatusbarEntryAccessor | undefined;
16
private newNotificationsCount = 0;
17
18
private currentStatusMessage: [IStatusMessageViewItem, IDisposable] | undefined;
19
20
private isNotificationsCenterVisible: boolean = false;
21
private isNotificationsToastsVisible: boolean = false;
22
23
constructor(
24
private readonly model: INotificationsModel,
25
@IStatusbarService private readonly statusbarService: IStatusbarService,
26
@INotificationService private readonly notificationService: INotificationService
27
) {
28
super();
29
30
this.updateNotificationsCenterStatusItem();
31
32
if (model.statusMessage) {
33
this.doSetStatusMessage(model.statusMessage);
34
}
35
36
this.registerListeners();
37
}
38
39
private registerListeners(): void {
40
this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e)));
41
this._register(this.model.onDidChangeStatusMessage(e => this.onDidChangeStatusMessage(e)));
42
this._register(this.notificationService.onDidChangeFilter(() => this.updateNotificationsCenterStatusItem()));
43
}
44
45
private onDidChangeNotification(e: INotificationChangeEvent): void {
46
47
// Consider a notification as unread as long as it only
48
// appeared as toast and not in the notification center
49
if (!this.isNotificationsCenterVisible) {
50
if (e.kind === NotificationChangeType.ADD) {
51
this.newNotificationsCount++;
52
} else if (e.kind === NotificationChangeType.REMOVE && this.newNotificationsCount > 0) {
53
this.newNotificationsCount--;
54
}
55
}
56
57
// Update in status bar
58
this.updateNotificationsCenterStatusItem();
59
}
60
61
private updateNotificationsCenterStatusItem(): void {
62
63
// Figure out how many notifications have progress only if neither
64
// toasts are visible nor center is visible. In that case we still
65
// want to give a hint to the user that something is running.
66
let notificationsInProgress = 0;
67
if (!this.isNotificationsCenterVisible && !this.isNotificationsToastsVisible) {
68
for (const notification of this.model.notifications) {
69
if (notification.hasProgress) {
70
notificationsInProgress++;
71
}
72
}
73
}
74
75
// Show the status bar entry depending on do not disturb setting
76
77
let statusProperties: IStatusbarEntry = {
78
name: localize('status.notifications', "Notifications"),
79
text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-dot)' : '$(bell)'}`,
80
ariaLabel: localize('status.notifications', "Notifications"),
81
command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER,
82
tooltip: this.getTooltip(notificationsInProgress),
83
showBeak: this.isNotificationsCenterVisible
84
};
85
86
if (this.notificationService.getFilter() === NotificationsFilter.ERROR) {
87
statusProperties = {
88
...statusProperties,
89
text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-slash-dot)' : '$(bell-slash)'}`,
90
ariaLabel: localize('status.doNotDisturb', "Do Not Disturb"),
91
tooltip: localize('status.doNotDisturbTooltip', "Do Not Disturb Mode is Enabled")
92
};
93
}
94
95
if (!this.notificationsCenterStatusItem) {
96
this.notificationsCenterStatusItem = this.statusbarService.addEntry(
97
statusProperties,
98
'status.notifications',
99
StatusbarAlignment.RIGHT,
100
Number.NEGATIVE_INFINITY /* last entry */
101
);
102
} else {
103
this.notificationsCenterStatusItem.update(statusProperties);
104
}
105
}
106
107
private getTooltip(notificationsInProgress: number): string {
108
if (this.isNotificationsCenterVisible) {
109
return localize('hideNotifications', "Hide Notifications");
110
}
111
112
if (this.model.notifications.length === 0) {
113
return localize('zeroNotifications', "No Notifications");
114
}
115
116
if (notificationsInProgress === 0) {
117
if (this.newNotificationsCount === 0) {
118
return localize('noNotifications', "No New Notifications");
119
}
120
121
if (this.newNotificationsCount === 1) {
122
return localize('oneNotification', "1 New Notification");
123
}
124
125
return localize({ key: 'notifications', comment: ['{0} will be replaced by a number'] }, "{0} New Notifications", this.newNotificationsCount);
126
}
127
128
if (this.newNotificationsCount === 0) {
129
return localize({ key: 'noNotificationsWithProgress', comment: ['{0} will be replaced by a number'] }, "No New Notifications ({0} in progress)", notificationsInProgress);
130
}
131
132
if (this.newNotificationsCount === 1) {
133
return localize({ key: 'oneNotificationWithProgress', comment: ['{0} will be replaced by a number'] }, "1 New Notification ({0} in progress)", notificationsInProgress);
134
}
135
136
return localize({ key: 'notificationsWithProgress', comment: ['{0} and {1} will be replaced by a number'] }, "{0} New Notifications ({1} in progress)", this.newNotificationsCount, notificationsInProgress);
137
}
138
139
update(isCenterVisible: boolean, isToastsVisible: boolean): void {
140
let updateNotificationsCenterStatusItem = false;
141
142
if (this.isNotificationsCenterVisible !== isCenterVisible) {
143
this.isNotificationsCenterVisible = isCenterVisible;
144
this.newNotificationsCount = 0; // Showing the notification center resets the unread counter to 0
145
updateNotificationsCenterStatusItem = true;
146
}
147
148
if (this.isNotificationsToastsVisible !== isToastsVisible) {
149
this.isNotificationsToastsVisible = isToastsVisible;
150
updateNotificationsCenterStatusItem = true;
151
}
152
153
// Update in status bar as needed
154
if (updateNotificationsCenterStatusItem) {
155
this.updateNotificationsCenterStatusItem();
156
}
157
}
158
159
private onDidChangeStatusMessage(e: IStatusMessageChangeEvent): void {
160
const statusItem = e.item;
161
162
switch (e.kind) {
163
164
// Show status notification
165
case StatusMessageChangeType.ADD:
166
this.doSetStatusMessage(statusItem);
167
168
break;
169
170
// Hide status notification (if its still the current one)
171
case StatusMessageChangeType.REMOVE:
172
if (this.currentStatusMessage && this.currentStatusMessage[0] === statusItem) {
173
dispose(this.currentStatusMessage[1]);
174
this.currentStatusMessage = undefined;
175
}
176
177
break;
178
}
179
}
180
181
private doSetStatusMessage(item: IStatusMessageViewItem): void {
182
const message = item.message;
183
184
const showAfter = item.options && typeof item.options.showAfter === 'number' ? item.options.showAfter : 0;
185
const hideAfter = item.options && typeof item.options.hideAfter === 'number' ? item.options.hideAfter : -1;
186
187
// Dismiss any previous
188
if (this.currentStatusMessage) {
189
dispose(this.currentStatusMessage[1]);
190
}
191
192
// Create new
193
let statusMessageEntry: IStatusbarEntryAccessor;
194
let showHandle: Timeout | undefined = setTimeout(() => {
195
statusMessageEntry = this.statusbarService.addEntry(
196
{
197
name: localize('status.message', "Status Message"),
198
text: message,
199
ariaLabel: message
200
},
201
'status.message',
202
StatusbarAlignment.LEFT,
203
Number.NEGATIVE_INFINITY /* last entry */
204
);
205
showHandle = undefined;
206
}, showAfter);
207
208
// Dispose function takes care of timeouts and actual entry
209
let hideHandle: Timeout | undefined;
210
const statusMessageDispose = {
211
dispose: () => {
212
if (showHandle) {
213
clearTimeout(showHandle);
214
}
215
216
if (hideHandle) {
217
clearTimeout(hideHandle);
218
}
219
220
statusMessageEntry?.dispose();
221
}
222
};
223
224
if (hideAfter > 0) {
225
hideHandle = setTimeout(() => statusMessageDispose.dispose(), hideAfter);
226
}
227
228
// Remember as current status message
229
this.currentStatusMessage = [item, statusMessageDispose];
230
}
231
}
232
233