提交 9a3b1b4a 编写于 作者: B Benjamin Pasero

notifications - first cut keybindings support with commands

上级 4eb771bf
......@@ -3,12 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .close-notification-action {
.vs .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action {
background-image: url('close.svg');
}
.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .close-notification-action,
.hc-black .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .close-notification-action {
.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action,
.hc-black .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action {
background-image: url('close-inverse.svg');
}
......
......@@ -7,28 +7,167 @@
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { INotificationViewItem, isNotificationViewItem } from 'vs/workbench/common/notifications';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { localize } from 'vs/nls';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
export const SHOW_NOTFICATIONS_CENTER_COMMAND_ID = 'notificationsCenter.show';
export const HIDE_NOTFICATIONS_CENTER_COMMAND_ID = 'notificationsCenter.hide';
export const TOGGLE_NOTFICATIONS_CENTER_COMMAND_ID = 'notificationsCenter.toggle';
export const SHOW_NOTFICATIONS_CENTER_COMMAND_ID = 'notifications.show';
export const HIDE_NOTFICATIONS_CENTER_COMMAND_ID = 'notifications.hide';
export const TOGGLE_NOTFICATIONS_CENTER_COMMAND_ID = 'notifications.toggle';
export const COLLAPSE_NOTIFICATION = 'notification.collapse';
export const EXPAND_NOTIFICATION = 'notification.expand';
export const TOGGLE_NOTIFICATION = 'notification.toggle';
export const CLEAR_NOTFICATION = 'notification.clear';
export const CLEAR_ALL_NOTFICATIONS = 'notifications.clearAll';
const notificationsCenterFocusedId = 'notificationsCenterFocus';
export const NotificationsCenterFocusedContext = new RawContextKey<boolean>(notificationsCenterFocusedId, true);
const notificationsCenterVisibleId = 'notificationsCenterVisible';
export const NotificationsCenterVisibleContext = new RawContextKey<boolean>(notificationsCenterVisibleId, false);
export const InNotificationsCenterContext = ContextKeyExpr.and(ContextKeyExpr.has(notificationsCenterFocusedId), ContextKeyExpr.has(notificationsCenterVisibleId));
export interface INotificationsCenterController {
isVisible: boolean;
readonly isVisible: boolean;
readonly selected: INotificationViewItem;
show(): void;
hide(): void;
clearAll(): void;
}
export function registerNotificationCommands(center: INotificationsCenterController): void {
// Show Center
CommandsRegistry.registerCommand(SHOW_NOTFICATIONS_CENTER_COMMAND_ID, () => center.show());
function showCenter(): void {
center.show();
}
function hideCenter(accessor: ServicesAccessor): void {
// Hide center
center.hide();
// Restore focus if we got an editor
const editor = accessor.get(IWorkbenchEditorService).getActiveEditor();
if (editor) {
editor.focus();
}
}
// Show Notifications Cneter
CommandsRegistry.registerCommand(SHOW_NOTFICATIONS_CENTER_COMMAND_ID, () => showCenter());
// Hide Notifications Center
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: HIDE_NOTFICATIONS_CENTER_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: InNotificationsCenterContext,
primary: KeyCode.Escape,
handler: accessor => hideCenter(accessor)
});
// Toggle Notifications Center
CommandsRegistry.registerCommand(TOGGLE_NOTFICATIONS_CENTER_COMMAND_ID, accessor => center.isVisible ? hideCenter(accessor) : showCenter());
// Clear Notification
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLEAR_NOTFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: InNotificationsCenterContext,
primary: KeyCode.Delete,
mac: {
primary: KeyMod.CtrlCmd | KeyCode.Backspace
},
handler: (accessor, args?: any) => {
let notification: INotificationViewItem;
if (isNotificationViewItem(args)) {
notification = args;
} else {
notification = center.selected;
}
if (notification) {
notification.dispose();
}
}
});
// Expand Notification
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: EXPAND_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: InNotificationsCenterContext,
primary: KeyCode.RightArrow,
handler: (accessor, args?: any) => {
let notification: INotificationViewItem;
if (isNotificationViewItem(args)) {
notification = args;
} else {
notification = center.selected;
}
if (notification) {
notification.expand();
}
}
});
// Collapse Notification
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: COLLAPSE_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: InNotificationsCenterContext,
primary: KeyCode.LeftArrow,
handler: (accessor, args?: any) => {
let notification: INotificationViewItem;
if (isNotificationViewItem(args)) {
notification = args;
} else {
notification = center.selected;
}
if (notification) {
notification.collapse();
}
}
});
// Toggle Notification
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: TOGGLE_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: InNotificationsCenterContext,
primary: KeyCode.Space,
handler: accessor => {
const notification = center.selected;
if (notification) {
if (notification.expanded) {
notification.collapse();
} else {
notification.expand();
}
}
}
});
/// Clear All Notifications
CommandsRegistry.registerCommand(CLEAR_ALL_NOTFICATIONS, () => center.clearAll());
// Commands for Command Palette
const category = localize('notifications', "Notifications");
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: SHOW_NOTFICATIONS_CENTER_COMMAND_ID, title: localize('showNotifications', "Show Notifications"), category }, when: NotificationsCenterVisibleContext.toNegated() });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: HIDE_NOTFICATIONS_CENTER_COMMAND_ID, title: localize('hideNotifications', "Hide Notifications"), category }, when: NotificationsCenterVisibleContext });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLEAR_ALL_NOTFICATIONS, title: localize('clearAllNotifications', "Clear All Notifications"), category } });
// Hide Center
CommandsRegistry.registerCommand(HIDE_NOTFICATIONS_CENTER_COMMAND_ID, () => center.hide());
// Toggle Center
CommandsRegistry.registerCommand(TOGGLE_NOTFICATIONS_CENTER_COMMAND_ID, () => center.isVisible ? center.hide() : center.show());
// TODO@Notification remove me
CommandsRegistry.registerCommand('notifications.showInfo', accessor => {
......
......@@ -12,21 +12,24 @@ import { Action, IAction, ActionRunner } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { CLEAR_NOTFICATION, EXPAND_NOTIFICATION, COLLAPSE_NOTIFICATION } from 'vs/workbench/browser/parts/notifications/notificationCommands';
import { ICommandService } from 'vs/platform/commands/common/commands';
export class CloseNotificationAction extends Action {
export class ClearNotificationAction extends Action {
public static readonly ID = 'workbench.action.closeNotification';
public static readonly ID = CLEAR_NOTFICATION;
public static readonly LABEL = localize('closeNotification', "Close Notification");
constructor(
id: string,
label: string
label: string,
@ICommandService private commandService: ICommandService
) {
super(id, label, 'close-notification-action');
super(id, label, 'clear-notification-action');
}
public run(notification: INotificationViewItem): TPromise<any> {
notification.dispose();
this.commandService.executeCommand(CLEAR_NOTFICATION, notification);
return TPromise.as(void 0);
}
......@@ -34,18 +37,19 @@ export class CloseNotificationAction extends Action {
export class ExpandNotificationAction extends Action {
public static readonly ID = 'workbench.action.expandNotification';
public static readonly ID = EXPAND_NOTIFICATION;
public static readonly LABEL = localize('expandNotification', "Expand Notification");
constructor(
id: string,
label: string
label: string,
@ICommandService private commandService: ICommandService
) {
super(id, label, 'expand-notification-action');
}
public run(notification: INotificationViewItem): TPromise<any> {
notification.expand();
this.commandService.executeCommand(EXPAND_NOTIFICATION, notification);
return TPromise.as(void 0);
}
......@@ -53,18 +57,19 @@ export class ExpandNotificationAction extends Action {
export class CollapseNotificationAction extends Action {
public static readonly ID = 'workbench.action.collapseNotification';
public static readonly ID = COLLAPSE_NOTIFICATION;
public static readonly LABEL = localize('collapseNotification', "Collapse Notification");
constructor(
id: string,
label: string
label: string,
@ICommandService private commandService: ICommandService
) {
super(id, label, 'collapse-notification-action');
}
public run(notification: INotificationViewItem): TPromise<any> {
notification.collapse();
this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification);
return TPromise.as(void 0);
}
......
......@@ -20,6 +20,8 @@ import { NotificationActionRunner } from 'vs/workbench/browser/parts/notificatio
import { Dimension } from 'vs/base/browser/builder';
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
import Event, { Emitter } from 'vs/base/common/event';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { NotificationsCenterFocusedContext, NotificationsCenterVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationCommands';
export class NotificationsCenter extends Themable {
......@@ -31,19 +33,23 @@ export class NotificationsCenter extends Themable {
private _isVisible: boolean;
private workbenchDimensions: Dimension;
private _onDidChangeVisibility: Emitter<void>;
private notificationsCenterVisibleContextKey: IContextKey<boolean>;
constructor(
private container: HTMLElement,
private model: INotificationsModel,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IPartService private partService: IPartService
@IPartService private partService: IPartService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super(themeService);
this._onDidChangeVisibility = new Emitter<void>();
this.toUnbind.push(this._onDidChangeVisibility);
this.notificationsCenterVisibleContextKey = NotificationsCenterVisibleContext.bindTo(contextKeyService);
this.viewModel = [];
this.registerListeners();
}
......@@ -56,6 +62,16 @@ export class NotificationsCenter extends Themable {
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)));
}
......@@ -82,6 +98,9 @@ export class NotificationsCenter extends Themable {
// Focus
this.focusNotificationsList();
// Context Key
this.notificationsCenterVisibleContextKey.set(true);
// Event
this._onDidChangeVisibility.fire();
}
......@@ -92,10 +111,6 @@ export class NotificationsCenter extends Themable {
}
this.list.domFocus();
if (this.list.getFocus().length === 0) {
this.list.focusFirst();
}
}
private createNotificationsList(): void {
......@@ -120,6 +135,9 @@ export class NotificationsCenter extends Themable {
);
this.toUnbind.push(this.list);
// 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
......@@ -165,7 +183,8 @@ export class NotificationsCenter extends Themable {
private updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) {
// Remember focus
const focus = this.indexToItems(this.list.getFocus());
const focusedIndex = this.list.getFocus()[0];
const focusedItem = this.viewModel[focusedIndex];
// Update view model
this.viewModel.splice(start, deleteCount, ...items);
......@@ -181,15 +200,26 @@ export class NotificationsCenter extends Themable {
// Otherwise restore focus
else {
this.list.setFocus(focus.map(f => this.viewModel.indexOf(f)));
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;
}
}
private indexToItems(indeces: number[]): INotificationViewItem[] {
return indeces.map(index => this.viewModel[index]).filter(item => !!item);
this.list.setFocus([indexToFocus]);
}
}
public hide(): void {
if (!this._isVisible || !this.list) {
return; // already hidden
}
// Hide
this._isVisible = false;
......@@ -201,6 +231,9 @@ export class NotificationsCenter extends Themable {
// Clear view model
this.viewModel = [];
// Context Key
this.notificationsCenterVisibleContextKey.set(false);
// Event
this._onDidChangeVisibility.fire();
}
......@@ -264,6 +297,17 @@ export class NotificationsCenter extends Themable {
this.list.getHTMLElement().style.maxHeight = `${maxHeight}px`;
this.list.layout();
}
public clearAll(): void {
// Hide notifications center first
this.hide();
// Dispose all
while (this.model.notifications.length) {
this.model.notifications[0].dispose();
}
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
......
......@@ -24,7 +24,8 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { DropdownMenuActionItem } from 'vs/base/browser/ui/dropdown/dropdown';
import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/common/notifications';
import { CloseNotificationAction, ExpandNotificationAction, CollapseNotificationAction, DoNotShowNotificationAgainAction, ConfigureNotificationAction } from 'vs/workbench/browser/parts/notifications/notificationsActions';
import { ClearNotificationAction, ExpandNotificationAction, CollapseNotificationAction, DoNotShowNotificationAgainAction, ConfigureNotificationAction } from 'vs/workbench/browser/parts/notifications/notificationsActions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export class NotificationsListDelegate implements IDelegate<INotificationViewItem> {
......@@ -143,7 +144,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN
private toDispose: IDisposable[];
private closeNotificationAction: CloseNotificationAction;
private closeNotificationAction: ClearNotificationAction;
private expandNotificationAction: ExpandNotificationAction;
private collapseNotificationAction: CollapseNotificationAction;
private doNotShowNotificationAgainAction: DoNotShowNotificationAgainAction;
......@@ -153,11 +154,12 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN
@IOpenerService private openerService: IOpenerService,
@IThemeService private themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService,
@IContextMenuService private contextMenuService: IContextMenuService
@IContextMenuService private contextMenuService: IContextMenuService,
@IKeybindingService private keybindingService: IKeybindingService
) {
this.toDispose = [];
this.closeNotificationAction = instantiationService.createInstance(CloseNotificationAction, CloseNotificationAction.ID, CloseNotificationAction.LABEL);
this.closeNotificationAction = instantiationService.createInstance(ClearNotificationAction, ClearNotificationAction.ID, ClearNotificationAction.LABEL);
this.expandNotificationAction = instantiationService.createInstance(ExpandNotificationAction, ExpandNotificationAction.ID, ExpandNotificationAction.LABEL);
this.collapseNotificationAction = instantiationService.createInstance(CollapseNotificationAction, CollapseNotificationAction.ID, CollapseNotificationAction.LABEL);
this.doNotShowNotificationAgainAction = this.instantiationService.createInstance(DoNotShowNotificationAgainAction, DoNotShowNotificationAgainAction.ID, DoNotShowNotificationAgainAction.LABEL);
......@@ -206,7 +208,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN
return null;
}
},
}
);
// Details Row
......@@ -288,7 +290,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN
// Toolbar
data.toolbar.clear();
data.toolbar.context = notification;
data.toolbar.push(actions, { icon: true, label: false });
actions.forEach(action => data.toolbar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) }));
// Source
if (notification.expanded && notification.source) {
......@@ -304,6 +306,12 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN
}
}
private getKeybindingLabel(action: IAction): string {
const keybinding = this.keybindingService.lookupKeybinding(action.id);
return keybinding ? keybinding.getLabel() : void 0;
}
private createButton(notification: INotificationViewItem, action: IAction, data: INotificationTemplateData): Button {
const button = new Button(data.actionsContainer);
data.toDispose.push(attachButtonStyler(button, this.themeService));
......
......@@ -128,6 +128,10 @@ export interface INotificationViewItem {
dispose(): void;
}
export function isNotificationViewItem(obj: any): obj is INotificationViewItem {
return obj instanceof NotificationViewItem;
}
export class NotificationViewItem implements INotificationViewItem {
private static MAX_MESSAGE_LENGTH = 1000;
......@@ -216,11 +220,19 @@ export class NotificationViewItem implements INotificationViewItem {
}
public expand(): void {
if (this._expanded) {
return;
}
this._expanded = true;
this._onDidChange.fire();
}
public collapse(): void {
if (!this._expanded || !this.canCollapse) {
return;
}
this._expanded = false;
this._onDidChange.fire();
}
......
......@@ -224,7 +224,7 @@ export class FeedbackDropdown extends Dropdown {
this.toDispose.push(attachStylerCallback(this.themeService, { widgetShadow, editorWidgetBackground, inputBackground, inputForeground, inputBorder, editorBackground, contrastBorder }, colors => {
$form.style('background-color', colors.editorWidgetBackground);
$form.style('box-shadow', colors.widgetShadow ? `0 2px 8px ${colors.widgetShadow}` : null);
$form.style('box-shadow', colors.widgetShadow ? `0 5px 8px ${colors.widgetShadow}` : null);
if (this.feedbackDescriptionInput) {
this.feedbackDescriptionInput.style.backgroundColor = colors.inputBackground;
......
......@@ -88,7 +88,6 @@
position: absolute;
top: 0;
right: 0;
margin: .5em 0 0 0;
padding: .5em;
width: 22px;
height: 22px;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册