提交 f67097d4 编写于 作者: B Benjamin Pasero

notifications - extract a reusable notifications list

上级 b6bfdd4d
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { INotificationViewItem, isNotificationViewItem } from 'vs/workbench/common/notifications'; import { INotificationViewItem, isNotificationViewItem } from 'vs/workbench/common/notifications';
...@@ -15,6 +15,7 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; ...@@ -15,6 +15,7 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService';
export const SHOW_NOTFICATIONS_CENTER_COMMAND_ID = 'notifications.show'; export const SHOW_NOTFICATIONS_CENTER_COMMAND_ID = 'notifications.show';
export const HIDE_NOTFICATIONS_CENTER_COMMAND_ID = 'notifications.hide'; export const HIDE_NOTFICATIONS_CENTER_COMMAND_ID = 'notifications.hide';
...@@ -25,17 +26,14 @@ export const TOGGLE_NOTIFICATION = 'notification.toggle'; ...@@ -25,17 +26,14 @@ export const TOGGLE_NOTIFICATION = 'notification.toggle';
export const CLEAR_NOTFICATION = 'notification.clear'; export const CLEAR_NOTFICATION = 'notification.clear';
export const CLEAR_ALL_NOTFICATIONS = 'notifications.clearAll'; export const CLEAR_ALL_NOTFICATIONS = 'notifications.clearAll';
const notificationsCenterFocusedId = 'notificationsCenterFocus'; const notificationFocusedId = 'notificationFocus';
export const NotificationsCenterFocusedContext = new RawContextKey<boolean>(notificationsCenterFocusedId, true); export const NotificationFocusedContext = new RawContextKey<boolean>(notificationFocusedId, true);
const notificationsCenterVisibleId = 'notificationsCenterVisible'; const notificationsCenterVisibleId = 'notificationsCenterVisible';
export const NotificationsCenterVisibleContext = new RawContextKey<boolean>(notificationsCenterVisibleId, false); export const NotificationsCenterVisibleContext = new RawContextKey<boolean>(notificationsCenterVisibleId, false);
export const InNotificationsCenterContext = ContextKeyExpr.and(ContextKeyExpr.has(notificationsCenterFocusedId), ContextKeyExpr.has(notificationsCenterVisibleId));
export interface INotificationsCenterController { export interface INotificationsCenterController {
readonly isVisible: boolean; readonly isVisible: boolean;
readonly selected: INotificationViewItem;
show(): void; show(): void;
hide(): void; hide(): void;
...@@ -61,14 +59,30 @@ export function registerNotificationCommands(center: INotificationsCenterControl ...@@ -61,14 +59,30 @@ export function registerNotificationCommands(center: INotificationsCenterControl
} }
} }
function getNotificationFromContext(listService: IListService, context?: any): INotificationViewItem {
if (isNotificationViewItem(context)) {
return context;
}
const list = listService.lastFocusedList;
if (list instanceof WorkbenchList) {
const focusedElement = list.getFocusedElements()[0];
if (isNotificationViewItem(focusedElement)) {
return focusedElement;
}
}
return void 0;
}
// Show Notifications Cneter // Show Notifications Cneter
CommandsRegistry.registerCommand(SHOW_NOTFICATIONS_CENTER_COMMAND_ID, () => showCenter()); CommandsRegistry.registerCommand(SHOW_NOTFICATIONS_CENTER_COMMAND_ID, () => showCenter());
// Hide Notifications Center // Hide Notifications Center
KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: HIDE_NOTFICATIONS_CENTER_COMMAND_ID, id: HIDE_NOTFICATIONS_CENTER_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
when: InNotificationsCenterContext, when: NotificationsCenterVisibleContext,
primary: KeyCode.Escape, primary: KeyCode.Escape,
handler: accessor => hideCenter(accessor) handler: accessor => hideCenter(accessor)
}); });
...@@ -80,19 +94,13 @@ export function registerNotificationCommands(center: INotificationsCenterControl ...@@ -80,19 +94,13 @@ export function registerNotificationCommands(center: INotificationsCenterControl
KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLEAR_NOTFICATION, id: CLEAR_NOTFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: InNotificationsCenterContext, when: NotificationFocusedContext,
primary: KeyCode.Delete, primary: KeyCode.Delete,
mac: { mac: {
primary: KeyMod.CtrlCmd | KeyCode.Backspace primary: KeyMod.CtrlCmd | KeyCode.Backspace
}, },
handler: (accessor, args?: any) => { handler: (accessor, args?: any) => {
let notification: INotificationViewItem; const notification = getNotificationFromContext(accessor.get(IListService), args);
if (isNotificationViewItem(args)) {
notification = args;
} else {
notification = center.selected;
}
if (notification) { if (notification) {
notification.dispose(); notification.dispose();
} }
...@@ -103,16 +111,10 @@ export function registerNotificationCommands(center: INotificationsCenterControl ...@@ -103,16 +111,10 @@ export function registerNotificationCommands(center: INotificationsCenterControl
KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: EXPAND_NOTIFICATION, id: EXPAND_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: InNotificationsCenterContext, when: NotificationFocusedContext,
primary: KeyCode.RightArrow, primary: KeyCode.RightArrow,
handler: (accessor, args?: any) => { handler: (accessor, args?: any) => {
let notification: INotificationViewItem; const notification = getNotificationFromContext(accessor.get(IListService), args);
if (isNotificationViewItem(args)) {
notification = args;
} else {
notification = center.selected;
}
if (notification) { if (notification) {
notification.expand(); notification.expand();
} }
...@@ -123,16 +125,10 @@ export function registerNotificationCommands(center: INotificationsCenterControl ...@@ -123,16 +125,10 @@ export function registerNotificationCommands(center: INotificationsCenterControl
KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: COLLAPSE_NOTIFICATION, id: COLLAPSE_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: InNotificationsCenterContext, when: NotificationFocusedContext,
primary: KeyCode.LeftArrow, primary: KeyCode.LeftArrow,
handler: (accessor, args?: any) => { handler: (accessor, args?: any) => {
let notification: INotificationViewItem; const notification = getNotificationFromContext(accessor.get(IListService), args);
if (isNotificationViewItem(args)) {
notification = args;
} else {
notification = center.selected;
}
if (notification) { if (notification) {
notification.collapse(); notification.collapse();
} }
...@@ -143,10 +139,10 @@ export function registerNotificationCommands(center: INotificationsCenterControl ...@@ -143,10 +139,10 @@ export function registerNotificationCommands(center: INotificationsCenterControl
KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: TOGGLE_NOTIFICATION, id: TOGGLE_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: InNotificationsCenterContext, when: NotificationFocusedContext,
primary: KeyCode.Space, primary: KeyCode.Space,
handler: accessor => { handler: accessor => {
const notification = center.selected; const notification = getNotificationFromContext(accessor.get(IListService));
if (notification) { if (notification) {
notification.toggle(); notification.toggle();
} }
...@@ -165,7 +161,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl ...@@ -165,7 +161,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// TODO@Notification remove me // TODO@notifications remove me
CommandsRegistry.registerCommand('notifications.showInfo', accessor => { CommandsRegistry.registerCommand('notifications.showInfo', accessor => {
accessor.get(INotificationService).info('This is an information message!'); accessor.get(INotificationService).info('This is an information message!');
}); });
......
...@@ -13,7 +13,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; ...@@ -13,7 +13,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { toErrorMessage } from 'vs/base/common/errorMessage'; import { toErrorMessage } from 'vs/base/common/errorMessage';
export class NotificationsAlerts { export class NotificationsAlerts {
private toDispose: IDisposable[]; private toDispose: IDisposable[];
constructor(private model: INotificationsModel) { constructor(private model: INotificationsModel) {
......
...@@ -5,31 +5,22 @@ ...@@ -5,31 +5,22 @@
'use strict'; 'use strict';
import 'vs/css!./media/notificationsCenter'; import { Themable } from 'vs/workbench/common/theme';
import { addClass, removeClass, isAncestor } from 'vs/base/browser/dom'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { INotificationsModel, INotificationChangeEvent, NotificationChangeType } from 'vs/workbench/common/notifications';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IListOptions } from 'vs/base/browser/ui/list/listWidget';
import { localize } from 'vs/nls';
import { Themable, NOTIFICATIONS_BORDER, NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND } from 'vs/workbench/common/theme';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { INotificationViewItem, INotificationsModel, INotificationChangeEvent, NotificationChangeType } from 'vs/workbench/common/notifications';
import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/browser/parts/notifications/notificationsViewer';
import { NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions';
import { Dimension } from 'vs/base/browser/builder'; import { Dimension } from 'vs/base/browser/builder';
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
import Event, { Emitter } from 'vs/base/common/event'; import Event, { Emitter } from 'vs/base/common/event';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { NotificationsCenterFocusedContext, NotificationsCenterVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationCommands'; import { NotificationsCenterVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationCommands';
import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class NotificationsCenter extends Themable { export class NotificationsCenter extends Themable {
private static MAX_DIMENSIONS = new Dimension(600, 600); private static MAX_DIMENSIONS = new Dimension(600, 600);
private listContainer: HTMLElement; private notificationsList: NotificationsList;
private list: WorkbenchList<INotificationViewItem>;
private viewModel: INotificationViewItem[];
private _isVisible: boolean; private _isVisible: boolean;
private workbenchDimensions: Dimension; private workbenchDimensions: Dimension;
private _onDidChangeVisibility: Emitter<void>; private _onDidChangeVisibility: Emitter<void>;
...@@ -38,8 +29,8 @@ export class NotificationsCenter extends Themable { ...@@ -38,8 +29,8 @@ export class NotificationsCenter extends Themable {
constructor( constructor(
private container: HTMLElement, private container: HTMLElement,
private model: INotificationsModel, private model: INotificationsModel,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService, @IThemeService themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService,
@IPartService private partService: IPartService, @IPartService private partService: IPartService,
@IContextKeyService contextKeyService: IContextKeyService @IContextKeyService contextKeyService: IContextKeyService
) { ) {
...@@ -50,10 +41,13 @@ export class NotificationsCenter extends Themable { ...@@ -50,10 +41,13 @@ export class NotificationsCenter extends Themable {
this.notificationsCenterVisibleContextKey = NotificationsCenterVisibleContext.bindTo(contextKeyService); this.notificationsCenterVisibleContextKey = NotificationsCenterVisibleContext.bindTo(contextKeyService);
this.viewModel = [];
this.registerListeners(); this.registerListeners();
} }
private registerListeners(): void {
this.toUnbind.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
}
public get onDidChangeVisibility(): Event<void> { public get onDidChangeVisibility(): Event<void> {
return this._onDidChangeVisibility.event; return this._onDidChangeVisibility.event;
} }
...@@ -62,41 +56,22 @@ export class NotificationsCenter extends Themable { ...@@ -62,41 +56,22 @@ export class NotificationsCenter extends Themable {
return this._isVisible; return this._isVisible;
} }
public get selected(): INotificationViewItem {
if (!this._isVisible || !this.list) {
return null;
}
const focusedIndex = this.list.getFocus()[0];
return this.viewModel[focusedIndex];
}
private registerListeners(): void {
this.toUnbind.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
}
public show(): void { public show(): void {
if (this._isVisible) {
this.focusNotificationsList();
return; // already visible
}
// Lazily create if showing for the first time // Lazily create if showing for the first time
if (!this.list) { if (!this.notificationsList) {
this.createNotificationsList(); this.notificationsList = this.instantiationService.createInstance(NotificationsList, this.container);
} }
// Make visible // Make visible
this._isVisible = true; this._isVisible = true;
addClass(this.listContainer, 'visible'); this.notificationsList.show();
// Show all notifications that are present now // Layout
this.onNotificationsAdded(0, this.model.notifications); this.layoutList();
// Focus // Show all notifications that are present now
this.focusNotificationsList(); this.notificationsList.updateNotificationsList(0, 0, this.model.notifications);
// Context Key // Context Key
this.notificationsCenterVisibleContextKey.set(true); this.notificationsCenterVisibleContextKey.set(true);
...@@ -105,142 +80,38 @@ export class NotificationsCenter extends Themable { ...@@ -105,142 +80,38 @@ export class NotificationsCenter extends Themable {
this._onDidChangeVisibility.fire(); this._onDidChangeVisibility.fire();
} }
private focusNotificationsList(): void {
if (!this._isVisible) {
return;
}
this.list.domFocus();
}
private createNotificationsList(): void {
// List Container
this.listContainer = document.createElement('div');
addClass(this.listContainer, 'notifications-list-container');
// Notification Renderer
const renderer = this.instantiationService.createInstance(NotificationRenderer, this.instantiationService.createInstance(NotificationActionRunner));
this.toUnbind.push(renderer);
// List
this.list = this.instantiationService.createInstance(
WorkbenchList,
this.listContainer,
new NotificationsListDelegate(this.listContainer),
[renderer],
{
ariaLabel: localize('notificationsList', "Notifications List")
} as IListOptions<INotificationViewItem>
);
this.toUnbind.push(this.list);
this.toUnbind.push(this.list.onMouseDblClick(event => {
const item = event.element;
item.toggle();
}));
// Context key
NotificationsCenterFocusedContext.bindTo(this.list.contextKeyService);
// Only allow for focus in notifications, as the
// selection is too strong over the contents of
// the notification
this.toUnbind.push(this.list.onSelectionChange(e => {
if (e.indexes.length > 0) {
this.list.setSelection([]);
}
}));
this.container.appendChild(this.listContainer);
this.updateStyles();
this.layoutList();
}
private onDidNotificationChange(e: INotificationChangeEvent): void { private onDidNotificationChange(e: INotificationChangeEvent): void {
if (!this._isVisible) { if (!this._isVisible) {
return; // only if visible return; // only if visible
} }
// Update notifications list based on event
switch (e.kind) { switch (e.kind) {
case NotificationChangeType.ADD: case NotificationChangeType.ADD:
return this.onNotificationsAdded(e.index, [e.item]); this.notificationsList.updateNotificationsList(e.index, 0, [e.item]);
break;
case NotificationChangeType.CHANGE: case NotificationChangeType.CHANGE:
return this.onNotificationChanged(e.index, e.item); this.notificationsList.updateNotificationsList(e.index, 1, [e.item]);
break;
case NotificationChangeType.REMOVE: case NotificationChangeType.REMOVE:
return this.onNotificationRemoved(e.index, e.item); this.notificationsList.updateNotificationsList(e.index, 1);
break;
} }
}
private onNotificationsAdded(index: number, items: INotificationViewItem[]): void {
this.updateNotificationsList(index, 0, items);
}
private onNotificationChanged(index: number, item: INotificationViewItem): void {
this.updateNotificationsList(index, 1, [item]);
}
private onNotificationRemoved(index: number, item: INotificationViewItem): void {
this.updateNotificationsList(index, 1);
}
private updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) {
const listHasDOMFocus = isAncestor(document.activeElement, this.listContainer);
// Remember focus
const focusedIndex = this.list.getFocus()[0];
const focusedItem = this.viewModel[focusedIndex];
// Update view model
this.viewModel.splice(start, deleteCount, ...items);
// Update list
this.list.splice(start, deleteCount, items);
this.list.layout();
// Hide if no more notifications to show // Hide if no more notifications to show
if (this.viewModel.length === 0) { if (this.model.notifications.length === 0) {
this.hide(); this.hide();
} }
// Otherwise restore focus if we had
else if (typeof focusedIndex === 'number') {
let indexToFocus = 0;
if (focusedItem) {
let indexToFocusCandidate = this.viewModel.indexOf(focusedItem);
if (indexToFocusCandidate === -1) {
indexToFocusCandidate = focusedIndex - 1; // item could have been removed
}
if (indexToFocusCandidate < this.viewModel.length && indexToFocusCandidate >= 0) {
indexToFocus = indexToFocusCandidate;
}
}
this.list.setFocus([indexToFocus]);
}
// Restore DOM focus if we had focus before
if (listHasDOMFocus) {
this.list.domFocus();
}
} }
public hide(): void { public hide(): void {
if (!this._isVisible || !this.list) { if (!this._isVisible || !this.notificationsList) {
return; // already hidden return; // already hidden
} }
// Hide // Hide
this._isVisible = false; this._isVisible = false;
removeClass(this.listContainer, 'visible'); this.notificationsList.hide();
// Clear list
this.list.splice(0, this.viewModel.length);
// Clear view model
this.viewModel = [];
// Context Key // Context Key
this.notificationsCenterVisibleContextKey.set(false); this.notificationsCenterVisibleContextKey.set(false);
...@@ -249,26 +120,10 @@ export class NotificationsCenter extends Themable { ...@@ -249,26 +120,10 @@ export class NotificationsCenter extends Themable {
this._onDidChangeVisibility.fire(); this._onDidChangeVisibility.fire();
} }
protected updateStyles(): void {
if (this.listContainer) {
const foreground = this.getColor(NOTIFICATIONS_FOREGROUND);
this.listContainer.style.color = foreground ? foreground.toString() : null;
const background = this.getColor(NOTIFICATIONS_BACKGROUND);
this.listContainer.style.background = background ? background.toString() : null;
const outlineColor = this.getColor(contrastBorder);
this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : null;
const widgetShadowColor = this.getColor(widgetShadow);
this.listContainer.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null;
}
}
public layout(dimension: Dimension): void { public layout(dimension: Dimension): void {
this.workbenchDimensions = dimension; this.workbenchDimensions = dimension;
if (this._isVisible && this.listContainer) { if (this._isVisible && this.notificationsList) {
this.layoutList(); this.layoutList();
} }
} }
...@@ -304,9 +159,7 @@ export class NotificationsCenter extends Themable { ...@@ -304,9 +159,7 @@ export class NotificationsCenter extends Themable {
} }
} }
this.listContainer.style.width = `${width}px`; this.notificationsList.layout(new Dimension(width, maxHeight));
this.list.getHTMLElement().style.maxHeight = `${maxHeight}px`;
this.list.layout();
} }
public clearAll(): void { public clearAll(): void {
...@@ -319,16 +172,4 @@ export class NotificationsCenter extends Themable { ...@@ -319,16 +172,4 @@ export class NotificationsCenter extends Themable {
this.model.notifications[0].dispose(); this.model.notifications[0].dispose();
} }
} }
} }
\ No newline at end of file
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const linkColor = theme.getColor(NOTIFICATIONS_LINKS);
if (linkColor) {
collector.addRule(`.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-message a { color: ${linkColor}; }`);
}
const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER);
if (notificationBorderColor) {
collector.addRule(`.monaco-workbench > .notifications-list-container .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`);
}
});
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/notificationsList';
import { addClass, removeClass, isAncestor } from 'vs/base/browser/dom';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IListOptions } from 'vs/base/browser/ui/list/listWidget';
import { localize } from 'vs/nls';
import { Themable, NOTIFICATIONS_BORDER, NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND } from 'vs/workbench/common/theme';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { INotificationViewItem } from 'vs/workbench/common/notifications';
import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/browser/parts/notifications/notificationsViewer';
import { NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions';
import { Dimension } from 'vs/base/browser/builder';
import { NotificationFocusedContext } from 'vs/workbench/browser/parts/notifications/notificationCommands';
export class NotificationsList extends Themable {
private listContainer: HTMLElement;
private list: WorkbenchList<INotificationViewItem>;
private viewModel: INotificationViewItem[];
private dimensions: Dimension;
private isVisible: boolean;
constructor(
private container: HTMLElement,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
) {
super(themeService);
this.viewModel = [];
}
public show(): void {
if (this.isVisible) {
this.list.domFocus();
return; // already visible
}
// Lazily create if showing for the first time
if (!this.list) {
this.createNotificationsList();
}
// Make visible
this.isVisible = true;
addClass(this.listContainer, 'visible');
// Focus
this.list.domFocus();
}
private createNotificationsList(): void {
// List Container
this.listContainer = document.createElement('div');
addClass(this.listContainer, 'notifications-list-container');
// Notification Renderer
const renderer = this.instantiationService.createInstance(NotificationRenderer, this.instantiationService.createInstance(NotificationActionRunner));
this.toUnbind.push(renderer);
// List
this.list = this.instantiationService.createInstance(
WorkbenchList,
this.listContainer,
new NotificationsListDelegate(this.listContainer),
[renderer],
{
ariaLabel: localize('notificationsList', "Notifications List")
} as IListOptions<INotificationViewItem>
);
this.toUnbind.push(this.list);
// Toggle on double click
this.toUnbind.push(this.list.onMouseDblClick(event => (event.element as INotificationViewItem).toggle()));
// Context key
NotificationFocusedContext.bindTo(this.list.contextKeyService);
// Only allow for focus in notifications, as the
// selection is too strong over the contents of
// the notification
this.toUnbind.push(this.list.onSelectionChange(e => {
if (e.indexes.length > 0) {
this.list.setSelection([]);
}
}));
this.container.appendChild(this.listContainer);
this.updateStyles();
}
public updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) {
const listHasDOMFocus = isAncestor(document.activeElement, this.listContainer);
// Remember focus
const focusedIndex = this.list.getFocus()[0];
const focusedItem = this.viewModel[focusedIndex];
// Update view model
this.viewModel.splice(start, deleteCount, ...items);
// Update list
this.list.splice(start, deleteCount, items);
this.list.layout();
// Hide if no more notifications to show
if (this.viewModel.length === 0) {
this.hide();
}
// Otherwise restore focus if we had
else if (typeof focusedIndex === 'number') {
let indexToFocus = 0;
if (focusedItem) {
let indexToFocusCandidate = this.viewModel.indexOf(focusedItem);
if (indexToFocusCandidate === -1) {
indexToFocusCandidate = focusedIndex - 1; // item could have been removed
}
if (indexToFocusCandidate < this.viewModel.length && indexToFocusCandidate >= 0) {
indexToFocus = indexToFocusCandidate;
}
}
this.list.setFocus([indexToFocus]);
}
// Restore DOM focus if we had focus before
if (listHasDOMFocus) {
this.list.domFocus();
}
}
public hide(): void {
if (!this.isVisible || !this.list) {
return; // already hidden
}
// Hide
this.isVisible = false;
removeClass(this.listContainer, 'visible');
// Clear list
this.list.splice(0, this.viewModel.length);
// Clear view model
this.viewModel = [];
}
protected updateStyles(): void {
if (this.listContainer) {
const foreground = this.getColor(NOTIFICATIONS_FOREGROUND);
this.listContainer.style.color = foreground ? foreground.toString() : null;
const background = this.getColor(NOTIFICATIONS_BACKGROUND);
this.listContainer.style.background = background ? background.toString() : null;
const outlineColor = this.getColor(contrastBorder);
this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : null;
const widgetShadowColor = this.getColor(widgetShadow);
this.listContainer.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null;
}
}
public layout(dimension: Dimension): void {
this.dimensions = dimension;
this.layoutList();
}
private layoutList(): void {
if (this.list) {
this.listContainer.style.width = `${this.dimensions.width}px`;
this.list.getHTMLElement().style.maxHeight = `${this.dimensions.height}px`;
this.list.layout();
}
}
public dispose(): void {
this.hide();
super.dispose();
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const linkColor = theme.getColor(NOTIFICATIONS_LINKS);
if (linkColor) {
collector.addRule(`.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-message a { color: ${linkColor}; }`);
}
const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER);
if (notificationBorderColor) {
collector.addRule(`.monaco-workbench > .notifications-list-container .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`);
}
});
...@@ -14,7 +14,7 @@ import { localize } from 'vs/nls'; ...@@ -14,7 +14,7 @@ import { localize } from 'vs/nls';
export class NotificationsStatus { export class NotificationsStatus {
private statusItem: IDisposable; private statusItem: IDisposable;
private toDispose: IDisposable[]; private toDispose: IDisposable[];
private isCenterVisible: boolean; private isNotificationsCenterVisible: boolean;
constructor( constructor(
private model: INotificationsModel, private model: INotificationsModel,
...@@ -26,8 +26,8 @@ export class NotificationsStatus { ...@@ -26,8 +26,8 @@ export class NotificationsStatus {
} }
public update(isCenterVisible: boolean): void { public update(isCenterVisible: boolean): void {
if (this.isCenterVisible !== isCenterVisible) { if (this.isNotificationsCenterVisible !== isCenterVisible) {
this.isCenterVisible = isCenterVisible; this.isNotificationsCenterVisible = isCenterVisible;
this.updateNotificationsStatusItem(); this.updateNotificationsStatusItem();
} }
} }
...@@ -55,9 +55,9 @@ export class NotificationsStatus { ...@@ -55,9 +55,9 @@ export class NotificationsStatus {
const notificationsCount = this.model.notifications.length; const notificationsCount = this.model.notifications.length;
if (notificationsCount > 0) { if (notificationsCount > 0) {
this.statusItem = this.statusbarService.addEntry({ this.statusItem = this.statusbarService.addEntry({
text: this.isCenterVisible ? '$(megaphone) ' + localize('hideNotifications', "Hide Notifications") : `$(megaphone) ${notificationsCount}`, text: this.isNotificationsCenterVisible ? '$(megaphone) ' + localize('hideNotifications', "Hide Notifications") : `$(megaphone) ${notificationsCount}`,
command: this.isCenterVisible ? HIDE_NOTFICATIONS_CENTER_COMMAND_ID : SHOW_NOTFICATIONS_CENTER_COMMAND_ID, command: this.isNotificationsCenterVisible ? HIDE_NOTFICATIONS_CENTER_COMMAND_ID : SHOW_NOTFICATIONS_CENTER_COMMAND_ID,
tooltip: this.isCenterVisible ? localize('hideNotifications', "Hide Notifications") : localize('notifications', "{0} notifications", notificationsCount) tooltip: this.isNotificationsCenterVisible ? localize('hideNotifications', "Hide Notifications") : localize('notifications', "{0} notifications", notificationsCount)
}, StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */); }, StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */);
} }
} }
......
...@@ -260,7 +260,7 @@ export class NotificationViewItem implements INotificationViewItem { ...@@ -260,7 +260,7 @@ export class NotificationViewItem implements INotificationViewItem {
} }
public expand(): void { public expand(): void {
if (this._expanded) { if (this._expanded || !this.canCollapse) {
return; return;
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册