Path: blob/main/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, IStatusMessageChangeEvent, StatusMessageChangeType, IStatusMessageViewItem } from '../../../common/notifications.js';6import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from '../../../services/statusbar/browser/statusbar.js';7import { Disposable, IDisposable, dispose } from '../../../../base/common/lifecycle.js';8import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from './notificationsCommands.js';9import { localize } from '../../../../nls.js';10import { INotificationService, NotificationsFilter } from '../../../../platform/notification/common/notification.js';1112export class NotificationsStatus extends Disposable {1314private notificationsCenterStatusItem: IStatusbarEntryAccessor | undefined;15private newNotificationsCount = 0;1617private currentStatusMessage: [IStatusMessageViewItem, IDisposable] | undefined;1819private isNotificationsCenterVisible: boolean = false;20private isNotificationsToastsVisible: boolean = false;2122constructor(23private readonly model: INotificationsModel,24@IStatusbarService private readonly statusbarService: IStatusbarService,25@INotificationService private readonly notificationService: INotificationService26) {27super();2829this.updateNotificationsCenterStatusItem();3031if (model.statusMessage) {32this.doSetStatusMessage(model.statusMessage);33}3435this.registerListeners();36}3738private registerListeners(): void {39this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e)));40this._register(this.model.onDidChangeStatusMessage(e => this.onDidChangeStatusMessage(e)));41this._register(this.notificationService.onDidChangeFilter(() => this.updateNotificationsCenterStatusItem()));42}4344private onDidChangeNotification(e: INotificationChangeEvent): void {4546// Consider a notification as unread as long as it only47// appeared as toast and not in the notification center48if (!this.isNotificationsCenterVisible) {49if (e.kind === NotificationChangeType.ADD) {50this.newNotificationsCount++;51} else if (e.kind === NotificationChangeType.REMOVE && this.newNotificationsCount > 0) {52this.newNotificationsCount--;53}54}5556// Update in status bar57this.updateNotificationsCenterStatusItem();58}5960private updateNotificationsCenterStatusItem(): void {6162// Figure out how many notifications have progress only if neither63// toasts are visible nor center is visible. In that case we still64// want to give a hint to the user that something is running.65let notificationsInProgress = 0;66if (!this.isNotificationsCenterVisible && !this.isNotificationsToastsVisible) {67for (const notification of this.model.notifications) {68if (notification.hasProgress) {69notificationsInProgress++;70}71}72}7374// Show the status bar entry depending on do not disturb setting7576let statusProperties: IStatusbarEntry = {77name: localize('status.notifications', "Notifications"),78text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-dot)' : '$(bell)'}`,79ariaLabel: localize('status.notifications', "Notifications"),80command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER,81tooltip: this.getTooltip(notificationsInProgress),82showBeak: this.isNotificationsCenterVisible83};8485if (this.notificationService.getFilter() === NotificationsFilter.ERROR) {86statusProperties = {87...statusProperties,88text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-slash-dot)' : '$(bell-slash)'}`,89ariaLabel: localize('status.doNotDisturb', "Do Not Disturb"),90tooltip: localize('status.doNotDisturbTooltip', "Do Not Disturb Mode is Enabled")91};92}9394if (!this.notificationsCenterStatusItem) {95this.notificationsCenterStatusItem = this.statusbarService.addEntry(96statusProperties,97'status.notifications',98StatusbarAlignment.RIGHT,99Number.NEGATIVE_INFINITY /* last entry */100);101} else {102this.notificationsCenterStatusItem.update(statusProperties);103}104}105106private getTooltip(notificationsInProgress: number): string {107if (this.isNotificationsCenterVisible) {108return localize('hideNotifications', "Hide Notifications");109}110111if (this.model.notifications.length === 0) {112return localize('zeroNotifications', "No Notifications");113}114115if (notificationsInProgress === 0) {116if (this.newNotificationsCount === 0) {117return localize('noNotifications', "No New Notifications");118}119120if (this.newNotificationsCount === 1) {121return localize('oneNotification', "1 New Notification");122}123124return localize({ key: 'notifications', comment: ['{0} will be replaced by a number'] }, "{0} New Notifications", this.newNotificationsCount);125}126127if (this.newNotificationsCount === 0) {128return localize({ key: 'noNotificationsWithProgress', comment: ['{0} will be replaced by a number'] }, "No New Notifications ({0} in progress)", notificationsInProgress);129}130131if (this.newNotificationsCount === 1) {132return localize({ key: 'oneNotificationWithProgress', comment: ['{0} will be replaced by a number'] }, "1 New Notification ({0} in progress)", notificationsInProgress);133}134135return localize({ key: 'notificationsWithProgress', comment: ['{0} and {1} will be replaced by a number'] }, "{0} New Notifications ({1} in progress)", this.newNotificationsCount, notificationsInProgress);136}137138update(isCenterVisible: boolean, isToastsVisible: boolean): void {139let updateNotificationsCenterStatusItem = false;140141if (this.isNotificationsCenterVisible !== isCenterVisible) {142this.isNotificationsCenterVisible = isCenterVisible;143this.newNotificationsCount = 0; // Showing the notification center resets the unread counter to 0144updateNotificationsCenterStatusItem = true;145}146147if (this.isNotificationsToastsVisible !== isToastsVisible) {148this.isNotificationsToastsVisible = isToastsVisible;149updateNotificationsCenterStatusItem = true;150}151152// Update in status bar as needed153if (updateNotificationsCenterStatusItem) {154this.updateNotificationsCenterStatusItem();155}156}157158private onDidChangeStatusMessage(e: IStatusMessageChangeEvent): void {159const statusItem = e.item;160161switch (e.kind) {162163// Show status notification164case StatusMessageChangeType.ADD:165this.doSetStatusMessage(statusItem);166167break;168169// Hide status notification (if its still the current one)170case StatusMessageChangeType.REMOVE:171if (this.currentStatusMessage && this.currentStatusMessage[0] === statusItem) {172dispose(this.currentStatusMessage[1]);173this.currentStatusMessage = undefined;174}175176break;177}178}179180private doSetStatusMessage(item: IStatusMessageViewItem): void {181const message = item.message;182183const showAfter = item.options && typeof item.options.showAfter === 'number' ? item.options.showAfter : 0;184const hideAfter = item.options && typeof item.options.hideAfter === 'number' ? item.options.hideAfter : -1;185186// Dismiss any previous187if (this.currentStatusMessage) {188dispose(this.currentStatusMessage[1]);189}190191// Create new192let statusMessageEntry: IStatusbarEntryAccessor;193let showHandle: Timeout | undefined = setTimeout(() => {194statusMessageEntry = this.statusbarService.addEntry(195{196name: localize('status.message', "Status Message"),197text: message,198ariaLabel: message199},200'status.message',201StatusbarAlignment.LEFT,202Number.NEGATIVE_INFINITY /* last entry */203);204showHandle = undefined;205}, showAfter);206207// Dispose function takes care of timeouts and actual entry208let hideHandle: Timeout | undefined;209const statusMessageDispose = {210dispose: () => {211if (showHandle) {212clearTimeout(showHandle);213}214215if (hideHandle) {216clearTimeout(hideHandle);217}218219statusMessageEntry?.dispose();220}221};222223if (hideAfter > 0) {224hideHandle = setTimeout(() => statusMessageDispose.dispose(), hideAfter);225}226227// Remember as current status message228this.currentStatusMessage = [item, statusMessageDispose];229}230}231232233