diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 5e96676ba40..8356bd94844 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -40,7 +40,7 @@ import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/com import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { ILabelService, ResourceLabelFormatter, IFormatterChangeEvent } from 'vs/platform/label/common/label'; -import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; import { IProgressRunner, IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, IWorkspaceFoldersWillChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -232,8 +232,12 @@ export class StandaloneNotificationService implements INotificationService { readonly onDidRemoveNotification: Event = Event.None; + readonly onDidChangeDoNotDisturbMode: Event = Event.None; + public _serviceBrand: undefined; + public doNotDisturbMode: boolean = false; + private static readonly NO_OP: INotificationHandle = new NoOpNotification(); public info(message: string): INotificationHandle { @@ -271,8 +275,6 @@ export class StandaloneNotificationService implements INotificationService { public status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - - public setFilter(filter: NotificationsFilter): void { } } export class StandaloneCommandService implements ICommandService { diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index a0d963aa542..cada490a6a0 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -141,8 +141,10 @@ suite('AbstractKeybindingService', () => { const notificationService: INotificationService = { _serviceBrand: undefined, + doNotDisturbMode: false, onDidAddNotification: undefined!, onDidRemoveNotification: undefined!, + onDidChangeDoNotDisturbMode: undefined!, notify: (notification: INotification) => { showMessageCalls.push({ sev: notification.severity, message: notification.message }); return new NoOpNotification(); @@ -169,8 +171,7 @@ suite('AbstractKeybindingService', () => { statusMessageCallsDisposed!.push(message); } }; - }, - setFilter() { } + } }; const resolver = new KeybindingResolver(items, [], () => { }); diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index e662f5a0016..3c82e108ea0 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -319,6 +319,13 @@ export interface INotificationService { readonly _serviceBrand: undefined; + /** + * The DND mode can be enabled or disabled + * and will result in all info and warning + * notifications to be silent. + */ + doNotDisturbMode: boolean; + /** * Emitted when a new notification is added. */ @@ -329,6 +336,11 @@ export interface INotificationService { */ readonly onDidRemoveNotification: Event; + /** + * Emitted when a do not disturb mode has changed. + */ + readonly onDidChangeDoNotDisturbMode: Event; + /** * Show the provided notification to the user. The returned `INotificationHandle` * can be used to control the notification afterwards. @@ -381,13 +393,6 @@ export interface INotificationService { * @returns a disposable to hide the status message */ status(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable; - - /** - * Allows to configure a filter for notifications. - * - * @param filter the filter to use - */ - setFilter(filter: NotificationsFilter): void; } export class NoOpNotification implements INotificationHandle { diff --git a/src/vs/platform/notification/test/common/testNotificationService.ts b/src/vs/platform/notification/test/common/testNotificationService.ts index fe227b1f9fd..9d248ee5cd5 100644 --- a/src/vs/platform/notification/test/common/testNotificationService.ts +++ b/src/vs/platform/notification/test/common/testNotificationService.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, NotificationsFilter, Severity } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, Severity } from 'vs/platform/notification/common/notification'; export class TestNotificationService implements INotificationService { @@ -13,8 +13,12 @@ export class TestNotificationService implements INotificationService { readonly onDidRemoveNotification: Event = Event.None; + readonly onDidChangeDoNotDisturbMode: Event = Event.None; + declare readonly _serviceBrand: undefined; + doNotDisturbMode: boolean = false; + private static readonly NO_OP: INotificationHandle = new NoOpNotification(); info(message: string): INotificationHandle { @@ -40,6 +44,4 @@ export class TestNotificationService implements INotificationService { status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - - setFilter(filter: NotificationsFilter): void { } } diff --git a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts index 052719d3f41..e158253f4dc 100644 --- a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts +++ b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { MainThreadMessageService } from 'vs/workbench/api/browser/mainThreadMessageService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { mock } from 'vs/base/test/common/mock'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; @@ -24,8 +24,10 @@ const emptyCommandService: ICommandService = { const emptyNotificationService = new class implements INotificationService { declare readonly _serviceBrand: undefined; + doNotDisturbMode: boolean = false; onDidAddNotification: Event = Event.None; onDidRemoveNotification: Event = Event.None; + onDidChangeDoNotDisturbMode: Event = Event.None; notify(...args: any[]): never { throw new Error('not implemented'); } @@ -44,19 +46,17 @@ const emptyNotificationService = new class implements INotificationService { status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - setFilter(filter: NotificationsFilter): void { - throw new Error('not implemented.'); - } }; class EmptyNotificationService implements INotificationService { declare readonly _serviceBrand: undefined; - + doNotDisturbMode: boolean = false; constructor(private withNotify: (notification: INotification) => void) { } onDidAddNotification: Event = Event.None; onDidRemoveNotification: Event = Event.None; + onDidChangeDoNotDisturbMode: Event = Event.None; notify(notification: INotification): INotificationHandle { this.withNotify(notification); @@ -77,9 +77,6 @@ class EmptyNotificationService implements INotificationService { status(message: string, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - setFilter(filter: NotificationsFilter): void { - throw new Error('Method not implemented.'); - } } suite('ExtHostMessageService', function () { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 0258b818920..240b52cc2b9 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -33,7 +33,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { coalesce } from 'vs/base/common/arrays'; import { assertIsDefined, isNumber } from 'vs/base/common/types'; -import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/common/theme'; import { LineNumbersType } from 'vs/editor/common/config/editorOptions'; @@ -1087,6 +1087,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (!restoring) { zenModeExitInfo.transitionedToFullScreen = toggleFullScreen; zenModeExitInfo.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout; + zenModeExitInfo.handleNotificationsDoNotDisturbMode = !this.notificationService.doNotDisturbMode; zenModeExitInfo.wasVisible.sideBar = this.isVisible(Parts.SIDEBAR_PART); zenModeExitInfo.wasVisible.panel = this.isVisible(Parts.PANEL_PART); zenModeExitInfo.wasVisible.auxiliaryBar = this.isVisible(Parts.AUXILIARYBAR_PART); @@ -1114,13 +1115,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.windowState.runtime.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: false })); } - if (config.silentNotifications) { - this.notificationService.setFilter(NotificationsFilter.ERROR); + if (config.silentNotifications && zenModeExitInfo.handleNotificationsDoNotDisturbMode) { + this.notificationService.doNotDisturbMode = true; } this.windowState.runtime.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS)) { - const filter = this.configurationService.getValue(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; - this.notificationService.setFilter(filter); + const zenModeSilentNotifications = !!this.configurationService.getValue(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS); + if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) { + this.notificationService.doNotDisturbMode = zenModeSilentNotifications; + } } })); @@ -1155,13 +1158,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.centerEditorLayout(false, true); } + if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) { + this.notificationService.doNotDisturbMode = false; + } + setLineNumbers(); this.focus(); - // Clear notifications filter - this.notificationService.setFilter(NotificationsFilter.OFF); - toggleFullScreen = zenModeExitInfo.transitionedToFullScreen && this.windowState.runtime.fullscreen; } diff --git a/src/vs/workbench/browser/layoutState.ts b/src/vs/workbench/browser/layoutState.ts index 0e4cac0dddd..70a4e637a26 100644 --- a/src/vs/workbench/browser/layoutState.ts +++ b/src/vs/workbench/browser/layoutState.ts @@ -46,6 +46,7 @@ export const LayoutStateKeys = { ZEN_MODE_EXIT_INFO: new RuntimeStateKey('zenMode.exitInfo', StorageScope.WORKSPACE, StorageTarget.USER, { transitionedToCenteredEditorLayout: false, transitionedToFullScreen: false, + handleNotificationsDoNotDisturbMode: false, wasVisible: { auxiliaryBar: false, panel: false, diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index 3402b0d5987..b4b01e896e8 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { Action, IAction, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CLEAR_NOTIFICATION, EXPAND_NOTIFICATION, COLLAPSE_NOTIFICATION, CLEAR_ALL_NOTIFICATIONS, HIDE_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { CLEAR_NOTIFICATION, EXPAND_NOTIFICATION, COLLAPSE_NOTIFICATION, CLEAR_ALL_NOTIFICATIONS, HIDE_NOTIFICATIONS_CENTER, TOGGLE_DO_NOT_DISTURB_MODE } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { Codicon } from 'vs/base/common/codicons'; @@ -23,6 +23,7 @@ const hideIcon = registerIcon('notifications-hide', Codicon.chevronDown, localiz const expandIcon = registerIcon('notifications-expand', Codicon.chevronUp, localize('expandIcon', 'Icon for the expand action in notifications.')); const collapseIcon = registerIcon('notifications-collapse', Codicon.chevronDown, localize('collapseIcon', 'Icon for the collapse action in notifications.')); const configureIcon = registerIcon('notifications-configure', Codicon.gear, localize('configureIcon', 'Icon for the configure action in notifications.')); +const doNotDisturbIcon = registerIcon('notifications-do-not-disturb', Codicon.bellSlash, localize('doNotDisturbIcon', 'Icon for the mute all action in notifications.')); export class ClearNotificationAction extends Action { @@ -60,6 +61,24 @@ export class ClearAllNotificationsAction extends Action { } } +export class ToggleDoNotDisturbAction extends Action { + + static readonly ID = TOGGLE_DO_NOT_DISTURB_MODE; + static readonly LABEL = localize('toggleDoNotDisturbMode', "Toggle Do Not Disturb Mode"); + + constructor( + id: string, + label: string, + @ICommandService private readonly commandService: ICommandService + ) { + super(id, label, ThemeIcon.asClassName(doNotDisturbIcon)); + } + + override async run(): Promise { + this.commandService.executeCommand(TOGGLE_DO_NOT_DISTURB_MODE); + } +} + export class HideNotificationsCenterAction extends Action { static readonly ID = HIDE_NOTIFICATIONS_CENTER; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index c99553bf978..8a6cafd8971 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -19,11 +19,12 @@ import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { localize } from 'vs/nls'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ClearAllNotificationsAction, HideNotificationsCenterAction, NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions'; +import { ClearAllNotificationsAction, HideNotificationsCenterAction, NotificationActionRunner, ToggleDoNotDisturbAction } from 'vs/workbench/browser/parts/notifications/notificationsActions'; import { IAction } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; import { NotificationsCenterVisibleContext } from 'vs/workbench/common/contextkeys'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class NotificationsCenter extends Themable implements INotificationsCenterController { @@ -40,6 +41,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente private workbenchDimensions: Dimension | undefined; private readonly notificationsCenterVisibleContextKey = NotificationsCenterVisibleContext.bindTo(this.contextKeyService); private clearAllAction: ClearAllNotificationsAction | undefined; + private toggleDoNotDisturbAction: ToggleDoNotDisturbAction | undefined; constructor( private readonly container: HTMLElement, @@ -49,7 +51,8 @@ export class NotificationsCenter extends Themable implements INotificationsCente @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @INotificationService private readonly notificationService: INotificationService, ) { super(themeService); @@ -61,6 +64,13 @@ export class NotificationsCenter extends Themable implements INotificationsCente private registerListeners(): void { this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.layoutService.onDidLayout(dimension => this.layout(Dimension.lift(dimension)))); + this._register(this.notificationService.onDidChangeDoNotDisturbMode(() => this.onDidChangeDoNotDisturbMode())); + } + + private onDidChangeDoNotDisturbMode(): void { + if (this.notificationService.doNotDisturbMode) { + this.hide(); // hide the notification center when do not disturb is enabled + } } get isVisible(): boolean { @@ -154,6 +164,9 @@ export class NotificationsCenter extends Themable implements INotificationsCente this.clearAllAction = this._register(this.instantiationService.createInstance(ClearAllNotificationsAction, ClearAllNotificationsAction.ID, ClearAllNotificationsAction.LABEL)); notificationsToolBar.push(this.clearAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.clearAllAction) }); + this.toggleDoNotDisturbAction = this._register(this.instantiationService.createInstance(ToggleDoNotDisturbAction, ToggleDoNotDisturbAction.ID, ToggleDoNotDisturbAction.LABEL)); + notificationsToolBar.push(this.toggleDoNotDisturbAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.toggleDoNotDisturbAction) }); + const hideAllAction = this._register(this.instantiationService.createInstance(HideNotificationsCenterAction, HideNotificationsCenterAction.ID, HideNotificationsCenterAction.LABEL)); notificationsToolBar.push(hideAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(hideAllAction) }); @@ -316,6 +329,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente } } + registerThemingParticipant((theme, collector) => { const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER); if (notificationBorderColor) { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index ee0cd3f780c..7649aba7b08 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -14,6 +14,7 @@ import { IListService, WorkbenchList } from 'vs/platform/list/browser/listServic import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NotificationMetrics, NotificationMetricsClassification, notificationToMetrics } from 'vs/workbench/browser/parts/notifications/notificationsTelemetry'; import { NotificationFocusedContext, NotificationsCenterVisibleContext, NotificationsToastsVisibleContext } from 'vs/workbench/common/contextkeys'; +import { INotificationService } from 'vs/platform/notification/common/notification'; // Center export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList'; @@ -34,6 +35,7 @@ export const EXPAND_NOTIFICATION = 'notification.expand'; const TOGGLE_NOTIFICATION = 'notification.toggle'; export const CLEAR_NOTIFICATION = 'notification.clear'; export const CLEAR_ALL_NOTIFICATIONS = 'notifications.clearAll'; +export const TOGGLE_DO_NOT_DISTURB_MODE = 'notifications.toggleDoNotDisturbMode'; export interface INotificationsCenterController { readonly isVisible: boolean; @@ -240,13 +242,21 @@ export function registerNotificationCommands(center: INotificationsCenterControl } }); - /// Clear All Notifications + // Clear All Notifications CommandsRegistry.registerCommand(CLEAR_ALL_NOTIFICATIONS, () => center.clearAll()); + // Toggle Do Not Disturb Mode + CommandsRegistry.registerCommand(TOGGLE_DO_NOT_DISTURB_MODE, accessor => { + const notificationService = accessor.get(INotificationService); + + notificationService.doNotDisturbMode = !notificationService.doNotDisturbMode; + }); + // Commands for Command Palette const category = { value: localize('notifications', "Notifications"), original: 'Notifications' }; MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: SHOW_NOTIFICATIONS_CENTER, title: { value: localize('showNotifications', "Show Notifications"), original: 'Show Notifications' }, category } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: HIDE_NOTIFICATIONS_CENTER, title: { value: localize('hideNotifications', "Hide Notifications"), original: 'Hide Notifications' }, category }, when: NotificationsCenterVisibleContext }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLEAR_ALL_NOTIFICATIONS, title: { value: localize('clearAllNotifications', "Clear All Notifications"), original: 'Clear All Notifications' }, category } }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DO_NOT_DISTURB_MODE, title: { value: localize('toggleDoNotDisturbMode', "Toggle Do Not Disturb Mode"), original: 'Toggle Do Not Disturb Mode' }, category } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: FOCUS_NOTIFICATION_TOAST, title: { value: localize('focusNotificationToasts', "Focus Notification Toast"), original: 'Focus Notification Toast' }, category }, when: NotificationsToastsVisibleContext }); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index ec31db99758..9b15769a6c3 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -8,6 +8,7 @@ import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatus import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { localize } from 'vs/nls'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class NotificationsStatus extends Disposable { @@ -21,7 +22,8 @@ export class NotificationsStatus extends Disposable { constructor( private readonly model: INotificationsModel, - @IStatusbarService private readonly statusbarService: IStatusbarService + @IStatusbarService private readonly statusbarService: IStatusbarService, + @INotificationService private readonly notificationService: INotificationService ) { super(); @@ -37,6 +39,7 @@ export class NotificationsStatus extends Disposable { private registerListeners(): void { this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.model.onDidChangeStatusMessage(e => this.onDidChangeStatusMessage(e))); + this._register(this.notificationService.onDidChangeDoNotDisturbMode(() => this.updateNotificationsCenterStatusItem())); } private onDidChangeNotification(e: INotificationChangeEvent): void { @@ -69,8 +72,9 @@ export class NotificationsStatus extends Disposable { } } - // Show the bell with a dot if there are unread or in-progress notifications - const statusProperties: IStatusbarEntry = { + // Show the status bar entry depending on do not disturb setting + + let statusProperties: IStatusbarEntry = { name: localize('status.notifications', "Notifications"), text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-dot)' : '$(bell)'}`, ariaLabel: localize('status.notifications', "Notifications"), @@ -79,6 +83,16 @@ export class NotificationsStatus extends Disposable { showBeak: this.isNotificationsCenterVisible }; + if (this.notificationService.doNotDisturbMode) { + statusProperties = { + ...statusProperties, + name: localize('status.doNotDisturb', "Do Not Disturb"), + text: '$(bell-slash)', + ariaLabel: localize('status.doNotDisturb', "Do Not Disturb"), + tooltip: localize('status.doNotDisturbTooltip', "Do Not Disturb Mode is Enabled") + }; + } + if (!this.notificationsCenterStatusItem) { this.notificationsCenterStatusItem = this.statusbarService.addEntry( statusProperties, diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 017f41f481a..fd5b98e1620 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -677,7 +677,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'zenMode.silentNotifications': { 'type': 'boolean', 'default': true, - 'description': localize('zenMode.silentNotifications', "Controls whether notifications are shown while in zen mode. If true, only error notifications will pop out.") + 'description': localize('zenMode.silentNotifications', "Controls whether notifications do not disturb mode should be enabled while in zen mode. If true, only error notifications will pop out.") } } }); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 8b7fb8df39f..243add76058 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -192,7 +192,7 @@ suite('ConfigurationEditingService', () => { test('do not notify error', async () => { instantiationService.stub(ITextFileService, 'isDirty', true); const target = sinon.stub(); - instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: undefined, onDidAddNotification: undefined!, onDidRemoveNotification: undefined!, notify: null!, error: null!, info: null!, warn: null!, status: null!, setFilter: null! }); + instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: undefined, doNotDisturbMode: false, onDidAddNotification: undefined!, onDidRemoveNotification: undefined!, onDidChangeDoNotDisturbMode: undefined!, notify: null!, error: null!, info: null!, warn: null!, status: null! }); try { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true }); } catch (error) { diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 32ade1ac946..1a768532b79 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -24,11 +24,15 @@ export class NotificationService extends Disposable implements INotificationServ private readonly _onDidRemoveNotification = this._register(new Emitter()); readonly onDidRemoveNotification = this._onDidRemoveNotification.event; + private readonly _onDidChangeDoNotDisturbMode = this._register(new Emitter()); + readonly onDidChangeDoNotDisturbMode = this._onDidChangeDoNotDisturbMode.event; + constructor( @IStorageService private readonly storageService: IStorageService ) { super(); + this.updateDoNotDisturbFilters(); this.registerListeners(); } @@ -58,10 +62,44 @@ export class NotificationService extends Disposable implements INotificationServ })); } - setFilter(filter: NotificationsFilter): void { + //#region Do not disturb mode + + static readonly DND_SETTINGS_KEY = 'notifications.doNotDisturbMode'; + + private _doNotDisturbMode = this.storageService.getBoolean(NotificationService.DND_SETTINGS_KEY, StorageScope.APPLICATION, false); + + get doNotDisturbMode() { + return this._doNotDisturbMode; + } + + set doNotDisturbMode(enabled: boolean) { + if (this._doNotDisturbMode === enabled) { + return; // no change + } + + this.storageService.store(NotificationService.DND_SETTINGS_KEY, enabled, StorageScope.APPLICATION, StorageTarget.MACHINE); + this._doNotDisturbMode = enabled; + + // Toggle via filter + this.updateDoNotDisturbFilters(); + + // Events + this._onDidChangeDoNotDisturbMode.fire(); + } + + private updateDoNotDisturbFilters(): void { + let filter: NotificationsFilter; + if (this._doNotDisturbMode) { + filter = NotificationsFilter.ERROR; + } else { + filter = NotificationsFilter.OFF; + } + this.model.setFilter(filter); } + //#endregion + info(message: NotificationMessage | NotificationMessage[]): void { if (Array.isArray(message)) { message.forEach(m => this.info(m)); diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index e4bffd568d0..8172f41fa24 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -70,7 +70,7 @@ export class ProgressService extends Disposable implements IProgressService { switch (location) { case ProgressLocation.Notification: - return this.withNotificationProgress({ ...options, location }, task, onDidCancel); + return this.withNotificationProgress({ ...options, location, silent: this.notificationService.doNotDisturbMode }, task, onDidCancel); case ProgressLocation.Window: if ((options as IProgressWindowOptions).command) { // Window progress with command get's shown in the status bar