mirror of
https://github.com/Microsoft/vscode
synced 2024-09-19 18:48:00 +00:00
Merge pull request #36232 from Microsoft/isidorn/compositeBar
Composite Bar
This commit is contained in:
commit
ff87265b2c
|
@ -9,72 +9,20 @@ import 'vs/css!./media/activityaction';
|
|||
import nls = require('vs/nls');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { BaseActionItem, Separator, IBaseActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IActivityBarService, ProgressBadge, TextBadge, NumberBadge, IconBadge, IBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IActivityBarService } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { IActivity, IGlobalActivity } from 'vs/workbench/common/activity';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { IViewletService, } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder, activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export interface IViewletActivity {
|
||||
badge: IBadge;
|
||||
clazz: string;
|
||||
}
|
||||
|
||||
export class ActivityAction extends Action {
|
||||
private badge: IBadge;
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
|
||||
constructor(private _activity: IActivity) {
|
||||
super(_activity.id, _activity.name, _activity.cssClass);
|
||||
|
||||
this.badge = null;
|
||||
}
|
||||
|
||||
public get activity(): IActivity {
|
||||
return this._activity;
|
||||
}
|
||||
|
||||
public get onDidChangeBadge(): Event<this> {
|
||||
return this._onDidChangeBadge.event;
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
if (!this.checked) {
|
||||
this._setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
if (this.checked) {
|
||||
this._setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public getBadge(): IBadge {
|
||||
return this.badge;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.badge = badge;
|
||||
this._onDidChangeBadge.fire(this);
|
||||
}
|
||||
}
|
||||
import { ActivityAction, ActivityActionItem } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
|
||||
export class ViewletActivityAction extends ActivityAction {
|
||||
|
||||
|
@ -83,15 +31,11 @@ export class ViewletActivityAction extends ActivityAction {
|
|||
private lastRun: number = 0;
|
||||
|
||||
constructor(
|
||||
private viewlet: ViewletDescriptor,
|
||||
activity: IActivity,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(viewlet);
|
||||
}
|
||||
|
||||
public get descriptor(): ViewletDescriptor {
|
||||
return this.viewlet;
|
||||
super(activity);
|
||||
}
|
||||
|
||||
public run(event: any): TPromise<any> {
|
||||
|
@ -110,495 +54,15 @@ export class ViewletActivityAction extends ActivityAction {
|
|||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
// Hide sidebar if selected viewlet already visible
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) {
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.activity.id) {
|
||||
return this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return this.viewletService.openViewlet(this.viewlet.id, true).then(() => this.activate());
|
||||
return this.viewletService.openViewlet(this.activity.id, true).then(() => this.activate());
|
||||
}
|
||||
}
|
||||
|
||||
export class ActivityActionItem extends BaseActionItem {
|
||||
protected $container: Builder;
|
||||
protected $label: Builder;
|
||||
protected $badge: Builder;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private mouseUpTimeout: number;
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
options: IBaseActionItemOptions,
|
||||
@IThemeService protected themeService: IThemeService
|
||||
) {
|
||||
super(null, action, options);
|
||||
|
||||
this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose);
|
||||
action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose);
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
return (this._action as ActivityAction).activity;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
|
||||
// Label
|
||||
if (this.$label) {
|
||||
const background = theme.getColor(ACTIVITY_BAR_FOREGROUND);
|
||||
|
||||
this.$label.style('background-color', background ? background.toString() : null);
|
||||
}
|
||||
|
||||
// Badge
|
||||
if (this.$badgeContent) {
|
||||
const badgeForeground = theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND);
|
||||
const badgeBackground = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND);
|
||||
const contrastBorderColor = theme.getColor(contrastBorder);
|
||||
|
||||
this.$badgeContent.style('color', badgeForeground ? badgeForeground.toString() : null);
|
||||
this.$badgeContent.style('background-color', badgeBackground ? badgeBackground.toString() : null);
|
||||
|
||||
this.$badgeContent.style('border-style', contrastBorderColor ? 'solid' : null);
|
||||
this.$badgeContent.style('border-width', contrastBorderColor ? '1px' : null);
|
||||
this.$badgeContent.style('border-color', contrastBorderColor ? contrastBorderColor.toString() : null);
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Make the container tab-able for keyboard navigation
|
||||
this.$container = $(container).attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
title: this.activity.name
|
||||
});
|
||||
|
||||
// Try hard to prevent keyboard only focus feedback when using mouse
|
||||
this.$container.on(DOM.EventType.MOUSE_DOWN, () => {
|
||||
this.$container.addClass('clicked');
|
||||
});
|
||||
|
||||
this.$container.on(DOM.EventType.MOUSE_UP, () => {
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.mouseUpTimeout = setTimeout(() => {
|
||||
this.$container.removeClass('clicked');
|
||||
}, 800); // delayed to prevent focus feedback from showing on mouse up
|
||||
});
|
||||
|
||||
// Label
|
||||
this.$label = $('a.action-label').appendTo(this.builder);
|
||||
if (this.activity.cssClass) {
|
||||
this.$label.addClass(this.activity.cssClass);
|
||||
}
|
||||
|
||||
this.$badge = this.builder.clone().div({ 'class': 'badge' }, (badge: Builder) => {
|
||||
this.$badgeContent = badge.div({ 'class': 'badge-content' });
|
||||
});
|
||||
|
||||
this.$badge.hide();
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onThemeChange(theme: ITheme): void {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.updateBadge(badge);
|
||||
}
|
||||
|
||||
protected updateBadge(badge: IBadge): void {
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
|
||||
if (badge) {
|
||||
|
||||
// Number
|
||||
if (badge instanceof NumberBadge) {
|
||||
if (badge.number) {
|
||||
this.$badgeContent.text(badge.number > 99 ? '99+' : badge.number.toString());
|
||||
this.$badge.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof TextBadge) {
|
||||
this.$badgeContent.text(badge.text);
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof IconBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Progress
|
||||
else if (badge instanceof ProgressBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
let title: string;
|
||||
if (badge && badge.getDescription()) {
|
||||
if (this.activity.name) {
|
||||
title = nls.localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription());
|
||||
} else {
|
||||
title = badge.getDescription();
|
||||
}
|
||||
} else {
|
||||
title = this.activity.name;
|
||||
}
|
||||
|
||||
[this.$label, this.$badge, this.$container].forEach(b => {
|
||||
if (b) {
|
||||
b.attr('aria-label', title);
|
||||
b.title(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleBadgeChangeEvenet(): void {
|
||||
const action = this.getAction();
|
||||
if (action instanceof ActivityAction) {
|
||||
this.updateBadge(action.getBadge());
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.$badge.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletActionItem extends ActivityActionItem {
|
||||
|
||||
private static manageExtensionAction: ManageExtensionAction;
|
||||
private static toggleViewletPinnedAction: ToggleViewletPinnedAction;
|
||||
private static draggedViewlet: ViewletDescriptor;
|
||||
|
||||
private viewletActivity: IActivity;
|
||||
private cssClass: string;
|
||||
|
||||
constructor(
|
||||
private action: ViewletActivityAction,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IActivityBarService private activityBarService: IActivityBarService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, { draggable: true }, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
|
||||
if (!ViewletActionItem.manageExtensionAction) {
|
||||
ViewletActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
|
||||
if (!ViewletActionItem.toggleViewletPinnedAction) {
|
||||
ViewletActionItem.toggleViewletPinnedAction = instantiationService.createInstance(ToggleViewletPinnedAction, void 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
if (!this.viewletActivity) {
|
||||
let activityName: string;
|
||||
|
||||
const keybinding = this.getKeybindingLabel(this.viewlet.id);
|
||||
if (keybinding) {
|
||||
activityName = nls.localize('titleKeybinding', "{0} ({1})", this.viewlet.name, keybinding);
|
||||
} else {
|
||||
activityName = this.viewlet.name;
|
||||
}
|
||||
|
||||
this.viewletActivity = {
|
||||
id: this.viewlet.id,
|
||||
cssClass: this.cssClass,
|
||||
name: activityName
|
||||
};
|
||||
}
|
||||
|
||||
return this.viewletActivity;
|
||||
}
|
||||
|
||||
private get viewlet(): ViewletDescriptor {
|
||||
return this.action.descriptor;
|
||||
}
|
||||
|
||||
private getKeybindingLabel(id: string): string {
|
||||
const kb = this.keybindingService.lookupKeybinding(id);
|
||||
if (kb) {
|
||||
return kb.getLabel();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this.$container.on('contextmenu', e => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(container);
|
||||
});
|
||||
|
||||
// Allow to drag
|
||||
this.$container.on(DOM.EventType.DRAG_START, (e: DragEvent) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
this.setDraggedViewlet(this.viewlet);
|
||||
|
||||
// Trigger the action even on drag start to prevent clicks from failing that started a drag
|
||||
if (!this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
});
|
||||
|
||||
// Drag enter
|
||||
let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
|
||||
this.$container.on(DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) {
|
||||
counter++;
|
||||
this.updateFromDragging(container, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag leave
|
||||
this.$container.on(DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
this.updateFromDragging(container, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Drag end
|
||||
this.$container.on(DOM.EventType.DRAG_END, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
counter = 0;
|
||||
this.updateFromDragging(container, false);
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
}
|
||||
});
|
||||
|
||||
// Drop
|
||||
this.$container.on(DOM.EventType.DROP, (e: DragEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) {
|
||||
this.updateFromDragging(container, false);
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
this.activityBarService.move(draggedViewlet.id, this.viewlet.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Activate on drag over to reveal targets
|
||||
[this.$badge, this.$label].forEach(b => new DelayedDragHandler(b.getHTMLElement(), () => {
|
||||
if (!ViewletActionItem.getDraggedViewlet() && !this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
}));
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
const dragBackground = theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND);
|
||||
|
||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
|
||||
}
|
||||
|
||||
public static getDraggedViewlet(): ViewletDescriptor {
|
||||
return ViewletActionItem.draggedViewlet;
|
||||
}
|
||||
|
||||
private setDraggedViewlet(viewlet: ViewletDescriptor): void {
|
||||
ViewletActionItem.draggedViewlet = viewlet;
|
||||
}
|
||||
|
||||
public static clearDraggedViewlet(): void {
|
||||
ViewletActionItem.draggedViewlet = void 0;
|
||||
}
|
||||
|
||||
private showContextMenu(container: HTMLElement): void {
|
||||
const actions: Action[] = [ViewletActionItem.toggleViewletPinnedAction];
|
||||
if (this.viewlet.extensionId) {
|
||||
actions.push(new Separator());
|
||||
actions.push(ViewletActionItem.manageExtensionAction);
|
||||
}
|
||||
|
||||
const isPinned = this.activityBarService.isPinned(this.viewlet.id);
|
||||
if (isPinned) {
|
||||
ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('removeFromActivityBar', "Hide from Activity Bar");
|
||||
} else {
|
||||
ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar");
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => container,
|
||||
getActionsContext: () => this.viewlet,
|
||||
getActions: () => TPromise.as(actions)
|
||||
});
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.$container.domFocus();
|
||||
}
|
||||
|
||||
protected _updateClass(): void {
|
||||
if (this.cssClass) {
|
||||
this.$badge.removeClass(this.cssClass);
|
||||
}
|
||||
|
||||
this.cssClass = this.getAction().class;
|
||||
this.$badge.addClass(this.cssClass);
|
||||
}
|
||||
|
||||
protected _updateChecked(): void {
|
||||
if (this.getAction().checked) {
|
||||
this.$container.addClass('checked');
|
||||
} else {
|
||||
this.$container.removeClass('checked');
|
||||
}
|
||||
}
|
||||
|
||||
protected _updateEnabled(): void {
|
||||
if (this.getAction().enabled) {
|
||||
this.builder.removeClass('disabled');
|
||||
} else {
|
||||
this.builder.addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
this.$label.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletOverflowActivityAction extends ActivityAction {
|
||||
|
||||
constructor(
|
||||
private showMenu: () => void
|
||||
) {
|
||||
super({
|
||||
id: 'activitybar.additionalViewlets.action',
|
||||
name: nls.localize('additionalViews', "Additional Views"),
|
||||
cssClass: 'toggle-more'
|
||||
});
|
||||
}
|
||||
|
||||
public run(event: any): TPromise<any> {
|
||||
this.showMenu();
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletOverflowActivityActionItem extends ActivityActionItem {
|
||||
private name: string;
|
||||
private cssClass: string;
|
||||
private actions: OpenViewletAction[];
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
private getOverflowingViewlets: () => ViewletDescriptor[],
|
||||
private getBadge: (viewlet: ViewletDescriptor) => IBadge,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, null, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
this.name = action.label;
|
||||
}
|
||||
|
||||
public showMenu(): void {
|
||||
if (this.actions) {
|
||||
dispose(this.actions);
|
||||
}
|
||||
|
||||
this.actions = this.getActions();
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.builder.getHTMLElement(),
|
||||
getActions: () => TPromise.as(this.actions),
|
||||
onHide: () => dispose(this.actions)
|
||||
});
|
||||
}
|
||||
|
||||
private getActions(): OpenViewletAction[] {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
return this.getOverflowingViewlets().map(viewlet => {
|
||||
const action = this.instantiationService.createInstance(OpenViewletAction, viewlet);
|
||||
action.radio = activeViewlet && activeViewlet.getId() === action.id;
|
||||
|
||||
const badge = this.getBadge(action.viewlet);
|
||||
let suffix: string | number;
|
||||
if (badge instanceof NumberBadge) {
|
||||
suffix = badge.number;
|
||||
} else if (badge instanceof TextBadge) {
|
||||
suffix = badge.text;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
action.label = nls.localize('numberBadge', "{0} ({1})", action.viewlet.name, suffix);
|
||||
} else {
|
||||
action.label = action.viewlet.name;
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.actions = dispose(this.actions);
|
||||
}
|
||||
}
|
||||
|
||||
class ManageExtensionAction extends Action {
|
||||
|
||||
constructor(
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
public run(viewlet: ViewletDescriptor): TPromise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', viewlet.extensionId);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenViewletAction extends Action {
|
||||
export class OpenViewletAction extends Action {
|
||||
|
||||
constructor(
|
||||
private _viewlet: ViewletDescriptor,
|
||||
|
@ -608,41 +72,37 @@ class OpenViewletAction extends Action {
|
|||
super(_viewlet.id, _viewlet.name);
|
||||
}
|
||||
|
||||
public get viewlet(): ViewletDescriptor {
|
||||
return this._viewlet;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
// Hide sidebar if selected viewlet already visible
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) {
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this._viewlet.id) {
|
||||
return this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return this.viewletService.openViewlet(this.viewlet.id, true);
|
||||
return this.viewletService.openViewlet(this._viewlet.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleViewletPinnedAction extends Action {
|
||||
|
||||
constructor(
|
||||
private viewlet: ViewletDescriptor,
|
||||
private activity: IActivity,
|
||||
@IActivityBarService private activityBarService: IActivityBarService
|
||||
) {
|
||||
super('activitybar.show.toggleViewletPinned', viewlet ? viewlet.name : nls.localize('toggle', "Toggle View Pinned"));
|
||||
super('activitybar.show.toggleViewletPinned', activity ? activity.name : nls.localize('toggle', "Toggle View Pinned"));
|
||||
|
||||
this.checked = this.viewlet && this.activityBarService.isPinned(this.viewlet.id);
|
||||
this.checked = this.activity && this.activityBarService.isPinned(this.activity.id);
|
||||
}
|
||||
|
||||
public run(context?: ViewletDescriptor): TPromise<any> {
|
||||
const viewlet = this.viewlet || context;
|
||||
public run(context: string): TPromise<any> {
|
||||
const id = this.activity ? this.activity.id : context;
|
||||
|
||||
if (this.activityBarService.isPinned(viewlet.id)) {
|
||||
this.activityBarService.unpin(viewlet.id);
|
||||
if (this.activityBarService.isPinned(id)) {
|
||||
this.activityBarService.unpin(id);
|
||||
} else {
|
||||
this.activityBarService.pin(viewlet.id);
|
||||
this.activityBarService.pin(id);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
|
|
|
@ -8,25 +8,20 @@
|
|||
import 'vs/css!./media/activitybarpart';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { Builder, $, Dimension } from 'vs/base/browser/builder';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ActionsOrientation, ActionBar, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { ActionsOrientation, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { ToggleViewletPinnedAction, ViewletActivityAction, ActivityAction, GlobalActivityActionItem, ViewletActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem, GlobalActivityAction, IViewletActivity } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { ToggleViewletPinnedAction, GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, OpenViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IActivityBarService, IBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import { IPartService, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Scope as MementoScope } from 'vs/workbench/common/memento';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
@ -34,6 +29,7 @@ import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/
|
|||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
|
||||
|
||||
export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
|
@ -47,17 +43,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
private globalActionBar: ActionBar;
|
||||
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; };
|
||||
|
||||
private viewletSwitcherBar: ActionBar;
|
||||
private viewletOverflowAction: ViewletOverflowActivityAction;
|
||||
private viewletOverflowActionItem: ViewletOverflowActivityActionItem;
|
||||
|
||||
private viewletIdToActions: { [viewletId: string]: ActivityAction; };
|
||||
private viewletIdToActionItems: { [viewletId: string]: IActionItem; };
|
||||
private viewletIdToActivityStack: { [viewletId: string]: IViewletActivity[]; };
|
||||
|
||||
private memento: object;
|
||||
private pinnedViewlets: string[];
|
||||
private activeUnpinnedViewlet: ViewletDescriptor;
|
||||
private compositeBar: CompositeBar;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
|
@ -72,58 +58,33 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
super(id, { hasTitle: false }, themeService);
|
||||
|
||||
this.globalActivityIdToActions = Object.create(null);
|
||||
|
||||
this.viewletIdToActionItems = Object.create(null);
|
||||
this.viewletIdToActions = Object.create(null);
|
||||
this.viewletIdToActivityStack = Object.create(null);
|
||||
|
||||
this.memento = this.getMemento(this.storageService, MementoScope.GLOBAL);
|
||||
|
||||
const pinnedViewlets = this.memento[ActivitybarPart.PINNED_VIEWLETS] as string[];
|
||||
|
||||
if (pinnedViewlets) {
|
||||
this.pinnedViewlets = pinnedViewlets;
|
||||
} else {
|
||||
this.pinnedViewlets = this.viewletService.getViewlets().map(v => v.id);
|
||||
}
|
||||
|
||||
this.compositeBar = this.instantiationService.createInstance(CompositeBar, {
|
||||
label: 'icon',
|
||||
storageId: ActivitybarPart.PINNED_VIEWLETS,
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
composites: this.viewletService.getViewlets(),
|
||||
getCompositeSize: (compositeId: string) => ActivitybarPart.ACTIVITY_ACTION_HEIGHT,
|
||||
getActivityAction: (compositeId: string) => this.instantiationService.createInstance(ViewletActivityAction, this.viewletService.getViewlet(compositeId)),
|
||||
getCompositePinnedAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletPinnedAction, this.viewletService.getViewlet(compositeId)),
|
||||
getOpenCompositeAction: (compositeId: string) => this.instantiationService.createInstance(OpenViewletAction, this.viewletService.getViewlet(compositeId))
|
||||
});
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Activate viewlet action on opening of a viewlet
|
||||
this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.onDidViewletOpen(viewlet)));
|
||||
this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId())));
|
||||
|
||||
// Deactivate viewlet action on close
|
||||
this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.onDidViewletClose(viewlet)));
|
||||
}
|
||||
|
||||
private onDidViewletOpen(viewlet: IViewlet): void {
|
||||
const id = viewlet.getId();
|
||||
|
||||
if (this.viewletIdToActions[id]) {
|
||||
this.viewletIdToActions[id].activate();
|
||||
}
|
||||
|
||||
const activeUnpinnedViewletShouldClose = this.activeUnpinnedViewlet && this.activeUnpinnedViewlet.id !== viewlet.getId();
|
||||
const activeUnpinnedViewletShouldShow = !this.getPinnedViewlets().some(v => v.id === viewlet.getId());
|
||||
if (activeUnpinnedViewletShouldShow || activeUnpinnedViewletShouldClose) {
|
||||
this.updateViewletSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
private onDidViewletClose(viewlet: IViewlet): void {
|
||||
const id = viewlet.getId();
|
||||
|
||||
if (this.viewletIdToActions[id]) {
|
||||
this.viewletIdToActions[id].deactivate();
|
||||
}
|
||||
this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId())));
|
||||
this.toUnbind.push(this.compositeBar.onDidDropComposite(data => this.move(data.compositeId, data.toCompositeId)));
|
||||
this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
|
||||
}
|
||||
|
||||
public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (this.viewletService.getViewlet(viewletOrActionId)) {
|
||||
return this.showViewletActivity(viewletOrActionId, badge, clazz);
|
||||
return this.compositeBar.showActivity(viewletOrActionId, badge, clazz);
|
||||
}
|
||||
|
||||
return this.showGlobalActivity(viewletOrActionId, badge);
|
||||
|
@ -144,94 +105,16 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
return toDisposable(() => action.setBadge(undefined));
|
||||
}
|
||||
|
||||
private showViewletActivity(viewletId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const activity = <IViewletActivity>{ badge, clazz };
|
||||
const stack = this.viewletIdToActivityStack[viewletId] || (this.viewletIdToActivityStack[viewletId] = []);
|
||||
stack.unshift(activity);
|
||||
|
||||
this.updateViewletActivity(viewletId);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
const stack = this.viewletIdToActivityStack[viewletId];
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = stack.indexOf(activity);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack.splice(idx, 1);
|
||||
if (stack.length === 0) {
|
||||
delete this.viewletIdToActivityStack[viewletId];
|
||||
}
|
||||
|
||||
this.updateViewletActivity(viewletId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private updateViewletActivity(viewletId: string) {
|
||||
const action = this.viewletIdToActions[viewletId];
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stack = this.viewletIdToActivityStack[viewletId];
|
||||
|
||||
// reset
|
||||
if (!stack || !stack.length) {
|
||||
action.setBadge(undefined);
|
||||
}
|
||||
|
||||
// update
|
||||
else {
|
||||
const [{ badge, clazz }] = stack;
|
||||
action.setBadge(badge);
|
||||
if (clazz) {
|
||||
action.class = clazz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public createContentArea(parent: Builder): Builder {
|
||||
const $el = $(parent);
|
||||
const $result = $('.content').appendTo($el);
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createViewletSwitcher($result.clone());
|
||||
this.compositeBar.create($result.clone().getHTMLElement());
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createGlobalActivityActionBar($result.getHTMLElement());
|
||||
|
||||
// Contextmenu for viewlets
|
||||
$(parent).on('contextmenu', (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(e);
|
||||
}, this.toUnbind);
|
||||
|
||||
// Allow to drop at the end to move viewlet to the end
|
||||
$(parent).on(DOM.EventType.DROP, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
const targetId = this.pinnedViewlets[this.pinnedViewlets.length - 1];
|
||||
if (targetId !== draggedViewlet.id) {
|
||||
this.move(draggedViewlet.id, this.pinnedViewlets[this.pinnedViewlets.length - 1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
@ -268,20 +151,6 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
});
|
||||
}
|
||||
|
||||
private createViewletSwitcher(div: Builder): void {
|
||||
this.viewletSwitcherBar = new ActionBar(div, {
|
||||
actionItemProvider: (action: Action) => action instanceof ViewletOverflowActivityAction ? this.viewletOverflowActionItem : this.viewletIdToActionItems[action.id],
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
animated: false
|
||||
});
|
||||
|
||||
this.updateViewletSwitcher();
|
||||
|
||||
// Update viewlet switcher when external viewlets become ready
|
||||
this.extensionService.onReady().then(() => this.updateViewletSwitcher());
|
||||
}
|
||||
|
||||
private createGlobalActivityActionBar(container: HTMLElement): void {
|
||||
const activityRegistry = Registry.as<IGlobalActivityRegistry>(GlobalActivityExtensions);
|
||||
const descriptors = activityRegistry.getActivities();
|
||||
|
@ -302,152 +171,18 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
});
|
||||
}
|
||||
|
||||
private updateViewletSwitcher() {
|
||||
if (!this.viewletSwitcherBar) {
|
||||
return; // We have not been rendered yet so there is nothing to update.
|
||||
}
|
||||
|
||||
let viewletsToShow = this.getPinnedViewlets();
|
||||
|
||||
// Always show the active viewlet even if it is marked to be hidden
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet && !viewletsToShow.some(viewlet => viewlet.id === activeViewlet.getId())) {
|
||||
this.activeUnpinnedViewlet = this.viewletService.getViewlet(activeViewlet.getId());
|
||||
viewletsToShow.push(this.activeUnpinnedViewlet);
|
||||
} else {
|
||||
this.activeUnpinnedViewlet = void 0;
|
||||
}
|
||||
|
||||
// Ensure we are not showing more viewlets than we have height for
|
||||
let overflows = false;
|
||||
if (this.dimension) {
|
||||
let availableHeight = this.dimension.height;
|
||||
if (this.globalActionBar) {
|
||||
availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTIVITY_ACTION_HEIGHT); // adjust for global actions showing
|
||||
}
|
||||
|
||||
const maxVisible = Math.floor(availableHeight / ActivitybarPart.ACTIVITY_ACTION_HEIGHT);
|
||||
overflows = viewletsToShow.length > maxVisible;
|
||||
|
||||
if (overflows) {
|
||||
viewletsToShow = viewletsToShow.slice(0, maxVisible - 1 /* make room for overflow action */);
|
||||
}
|
||||
}
|
||||
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
const visibleViewletsChange = !arrays.equals(viewletsToShow.map(viewlet => viewlet.id), visibleViewlets);
|
||||
|
||||
// Pull out overflow action if there is a viewlet change so that we can add it to the end later
|
||||
if (this.viewletOverflowAction && visibleViewletsChange) {
|
||||
this.viewletSwitcherBar.pull(this.viewletSwitcherBar.length() - 1);
|
||||
|
||||
this.viewletOverflowAction.dispose();
|
||||
this.viewletOverflowAction = null;
|
||||
|
||||
this.viewletOverflowActionItem.dispose();
|
||||
this.viewletOverflowActionItem = null;
|
||||
}
|
||||
|
||||
// Pull out viewlets that overflow or got hidden
|
||||
const viewletIdsToShow = viewletsToShow.map(v => v.id);
|
||||
visibleViewlets.forEach(viewletId => {
|
||||
if (viewletIdsToShow.indexOf(viewletId) === -1) {
|
||||
this.pullViewlet(viewletId);
|
||||
}
|
||||
});
|
||||
|
||||
// Built actions for viewlets to show
|
||||
const newViewletsToShow = viewletsToShow
|
||||
.filter(viewlet => !this.viewletIdToActions[viewlet.id])
|
||||
.map(viewlet => this.toAction(viewlet));
|
||||
|
||||
// Update when we have new viewlets to show
|
||||
if (newViewletsToShow.length) {
|
||||
|
||||
// Add to viewlet switcher
|
||||
this.viewletSwitcherBar.push(newViewletsToShow, { label: true, icon: true });
|
||||
|
||||
// Make sure to activate the active one
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet) {
|
||||
const activeViewletEntry = this.viewletIdToActions[activeViewlet.getId()];
|
||||
if (activeViewletEntry) {
|
||||
activeViewletEntry.activate();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to restore activity
|
||||
Object.keys(this.viewletIdToActions).forEach(viewletId => {
|
||||
this.updateViewletActivity(viewletId);
|
||||
});
|
||||
}
|
||||
|
||||
// Add overflow action as needed
|
||||
if (visibleViewletsChange && overflows) {
|
||||
this.viewletOverflowAction = this.instantiationService.createInstance(ViewletOverflowActivityAction, () => this.viewletOverflowActionItem.showMenu());
|
||||
this.viewletOverflowActionItem = this.instantiationService.createInstance(ViewletOverflowActivityActionItem, this.viewletOverflowAction, () => this.getOverflowingViewlets(), (viewlet: ViewletDescriptor) => this.viewletIdToActivityStack[viewlet.id] && this.viewletIdToActivityStack[viewlet.id][0].badge);
|
||||
|
||||
this.viewletSwitcherBar.push(this.viewletOverflowAction, { label: true, icon: true });
|
||||
}
|
||||
}
|
||||
|
||||
private getOverflowingViewlets(): ViewletDescriptor[] {
|
||||
const viewlets = this.getPinnedViewlets();
|
||||
if (this.activeUnpinnedViewlet) {
|
||||
viewlets.push(this.activeUnpinnedViewlet);
|
||||
}
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
|
||||
return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) === -1);
|
||||
}
|
||||
|
||||
private getVisibleViewlets(): ViewletDescriptor[] {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
|
||||
return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) >= 0);
|
||||
}
|
||||
|
||||
private getPinnedViewlets(): ViewletDescriptor[] {
|
||||
return this.pinnedViewlets.map(viewletId => this.viewletService.getViewlet(viewletId)).filter(v => !!v); // ensure to remove those that might no longer exist
|
||||
}
|
||||
|
||||
private pullViewlet(viewletId: string): void {
|
||||
const index = Object.keys(this.viewletIdToActions).indexOf(viewletId);
|
||||
if (index >= 0) {
|
||||
this.viewletSwitcherBar.pull(index);
|
||||
|
||||
const action = this.viewletIdToActions[viewletId];
|
||||
action.dispose();
|
||||
delete this.viewletIdToActions[viewletId];
|
||||
|
||||
const actionItem = this.viewletIdToActionItems[action.id];
|
||||
actionItem.dispose();
|
||||
delete this.viewletIdToActionItems[action.id];
|
||||
}
|
||||
}
|
||||
|
||||
private toAction(viewlet: ViewletDescriptor): ActivityAction {
|
||||
const action = this.instantiationService.createInstance(ViewletActivityAction, viewlet);
|
||||
|
||||
this.viewletIdToActionItems[action.id] = this.instantiationService.createInstance(ViewletActionItem, action);
|
||||
this.viewletIdToActions[viewlet.id] = action;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
public getPinned(): string[] {
|
||||
return this.pinnedViewlets;
|
||||
return this.viewletService.getViewlets().map(v => v.id).filter(id => this.compositeBar.isPinned(id));;
|
||||
}
|
||||
|
||||
public unpin(viewletId: string): void {
|
||||
if (!this.isPinned(viewletId)) {
|
||||
if (!this.compositeBar.isPinned(viewletId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
const defaultViewletId = this.viewletService.getDefaultViewletId();
|
||||
const visibleViewlets = this.getVisibleViewlets();
|
||||
const visibleViewlets = this.compositeBar.getVisibleComposites();
|
||||
|
||||
let unpinPromise: TPromise<any>;
|
||||
|
||||
|
@ -459,7 +194,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
|
||||
// Case: viewlet is not the default viewlet and default viewlet is still showing
|
||||
// Solv: we open the default viewlet
|
||||
else if (defaultViewletId !== viewletId && this.isPinned(defaultViewletId)) {
|
||||
else if (defaultViewletId !== viewletId && this.compositeBar.isPinned(defaultViewletId)) {
|
||||
unpinPromise = this.viewletService.openViewlet(defaultViewletId, true);
|
||||
}
|
||||
|
||||
|
@ -472,21 +207,17 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
// Case: we closed the default viewlet
|
||||
// Solv: we open the next visible viewlet from top
|
||||
else {
|
||||
unpinPromise = this.viewletService.openViewlet(visibleViewlets.filter(viewlet => viewlet.id !== viewletId)[0].id, true);
|
||||
unpinPromise = this.viewletService.openViewlet(visibleViewlets.filter(viewletId => viewletId !== viewletId)[0], true);
|
||||
}
|
||||
|
||||
unpinPromise.then(() => {
|
||||
|
||||
// then remove from pinned and update switcher
|
||||
const index = this.pinnedViewlets.indexOf(viewletId);
|
||||
this.pinnedViewlets.splice(index, 1);
|
||||
|
||||
this.updateViewletSwitcher();
|
||||
this.compositeBar.unpin(viewletId);
|
||||
});
|
||||
}
|
||||
|
||||
public isPinned(viewletId: string): boolean {
|
||||
return this.pinnedViewlets.indexOf(viewletId) >= 0;
|
||||
return this.compositeBar.isPinned(viewletId);
|
||||
}
|
||||
|
||||
public pin(viewletId: string, update = true): void {
|
||||
|
@ -495,41 +226,17 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
}
|
||||
|
||||
// first open that viewlet
|
||||
this.viewletService.openViewlet(viewletId, true).then(() => {
|
||||
|
||||
// then update
|
||||
this.pinnedViewlets.push(viewletId);
|
||||
this.pinnedViewlets = arrays.distinct(this.pinnedViewlets);
|
||||
|
||||
if (update) {
|
||||
this.updateViewletSwitcher();
|
||||
}
|
||||
});
|
||||
this.viewletService.openViewlet(viewletId, true)
|
||||
.then(() => this.compositeBar.pin(viewletId, update));
|
||||
}
|
||||
|
||||
public move(viewletId: string, toViewletId: string): void {
|
||||
|
||||
// Make sure a moved viewlet gets pinned
|
||||
if (!this.isPinned(viewletId)) {
|
||||
this.pin(viewletId, false /* defer update, we take care of it */);
|
||||
}
|
||||
|
||||
const fromIndex = this.pinnedViewlets.indexOf(viewletId);
|
||||
const toIndex = this.pinnedViewlets.indexOf(toViewletId);
|
||||
|
||||
this.pinnedViewlets.splice(fromIndex, 1);
|
||||
this.pinnedViewlets.splice(toIndex, 0, viewletId);
|
||||
|
||||
// Clear viewlets that are impacted by the move
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
for (let i = Math.min(fromIndex, toIndex); i < visibleViewlets.length; i++) {
|
||||
this.pullViewlet(visibleViewlets[i]);
|
||||
}
|
||||
|
||||
// timeout helps to prevent artifacts from showing up
|
||||
setTimeout(() => {
|
||||
this.updateViewletSwitcher();
|
||||
}, 0);
|
||||
this.compositeBar.move(viewletId, toViewletId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -542,16 +249,20 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
|
||||
this.dimension = sizes[1];
|
||||
|
||||
// Update switcher to handle overflow issues
|
||||
this.updateViewletSwitcher();
|
||||
let availableHeight = this.dimension.height;
|
||||
if (this.globalActionBar) {
|
||||
// adjust height for global actions showing
|
||||
availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTIVITY_ACTION_HEIGHT);
|
||||
}
|
||||
this.compositeBar.layout(new Dimension(dimension.width, availableHeight));
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.viewletSwitcherBar) {
|
||||
this.viewletSwitcherBar.dispose();
|
||||
this.viewletSwitcherBar = null;
|
||||
if (this.compositeBar) {
|
||||
this.compositeBar.dispose();
|
||||
this.compositeBar = null;
|
||||
}
|
||||
|
||||
if (this.globalActionBar) {
|
||||
|
@ -565,9 +276,9 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||
public shutdown(): void {
|
||||
|
||||
// Persist Hidden State
|
||||
this.memento[ActivitybarPart.PINNED_VIEWLETS] = this.pinnedViewlets;
|
||||
this.compositeBar.store();
|
||||
|
||||
// Pass to super
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
380
src/vs/workbench/browser/parts/compositebar/compositeBar.ts
Normal file
380
src/vs/workbench/browser/parts/compositebar/compositeBar.ts
Normal file
|
@ -0,0 +1,380 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ActionBar, IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
|
||||
export interface ICompositeBarOptions {
|
||||
label: 'icon' | 'name';
|
||||
storageId: string;
|
||||
orientation: ActionsOrientation;
|
||||
composites: { id: string, name: string }[];
|
||||
getActivityAction: (compositeId: string) => ActivityAction;
|
||||
getCompositePinnedAction: (compositeId: string) => Action;
|
||||
getOpenCompositeAction: (compositeId: string) => Action;
|
||||
getCompositeSize: (compositeId: string) => number;
|
||||
}
|
||||
|
||||
export class CompositeBar {
|
||||
|
||||
private _onDidContextMenu: Emitter<MouseEvent>;
|
||||
private _onDidDropComposite: Emitter<{ compositeId: string, toCompositeId: string }>;
|
||||
|
||||
private dimension: Dimension;
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
private compositeSwitcherBar: ActionBar;
|
||||
private compositeOverflowAction: CompositeOverflowActivityAction;
|
||||
private compositeOverflowActionItem: CompositeOverflowActivityActionItem;
|
||||
|
||||
private compositeIdToActions: { [compositeId: string]: ActivityAction; };
|
||||
private compositeIdToActionItems: { [compositeId: string]: IActionItem; };
|
||||
private compositeIdToActivityStack: { [compositeId: string]: ICompositeActivity[]; };
|
||||
|
||||
private pinnedComposites: string[];
|
||||
private activeCompositeId: string;
|
||||
private activeUnpinnedCompositeId: string;
|
||||
|
||||
constructor(
|
||||
private options: ICompositeBarOptions,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.compositeIdToActionItems = Object.create(null);
|
||||
this.compositeIdToActions = Object.create(null);
|
||||
this.compositeIdToActivityStack = Object.create(null);
|
||||
|
||||
this._onDidContextMenu = new Emitter<MouseEvent>();
|
||||
this._onDidDropComposite = new Emitter<{ compositeId: string, toCompositeId: string }>();
|
||||
|
||||
const pinnedComposites = JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, null)) as string[];
|
||||
if (pinnedComposites) {
|
||||
this.pinnedComposites = pinnedComposites;
|
||||
} else {
|
||||
this.pinnedComposites = this.options.composites.map(c => c.id);
|
||||
}
|
||||
}
|
||||
|
||||
public get onDidContextMenu(): Event<MouseEvent> {
|
||||
return this._onDidContextMenu.event;
|
||||
}
|
||||
|
||||
public get onDidDropComposite(): Event<{ compositeId: string, toCompositeId: string }> {
|
||||
return this._onDidDropComposite.event;
|
||||
}
|
||||
|
||||
public activateComposite(id: string): void {
|
||||
if (this.compositeIdToActions[id]) {
|
||||
this.compositeIdToActions[id].activate();
|
||||
}
|
||||
this.activeCompositeId = id;
|
||||
|
||||
const activeUnpinnedCompositeShouldClose = this.activeUnpinnedCompositeId && this.activeUnpinnedCompositeId !== id;
|
||||
const activeUnpinnedCompositeShouldShow = !this.pinnedComposites.some(pid => pid === id);
|
||||
if (activeUnpinnedCompositeShouldShow || activeUnpinnedCompositeShouldClose) {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
public deactivateComposite(id: string): void {
|
||||
if (this.compositeIdToActions[id]) {
|
||||
this.compositeIdToActions[id].deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
public showActivity(compositeId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const activity = <ICompositeActivity>{ badge, clazz };
|
||||
const stack = this.compositeIdToActivityStack[compositeId] || (this.compositeIdToActivityStack[compositeId] = []);
|
||||
stack.unshift(activity);
|
||||
|
||||
this.updateActivity(compositeId);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
const stack = this.compositeIdToActivityStack[compositeId];
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = stack.indexOf(activity);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack.splice(idx, 1);
|
||||
if (stack.length === 0) {
|
||||
delete this.compositeIdToActivityStack[compositeId];
|
||||
}
|
||||
|
||||
this.updateActivity(compositeId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private updateActivity(compositeId: string) {
|
||||
const action = this.compositeIdToActions[compositeId];
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stack = this.compositeIdToActivityStack[compositeId];
|
||||
|
||||
// reset
|
||||
if (!stack || !stack.length) {
|
||||
action.setBadge(undefined);
|
||||
}
|
||||
|
||||
// update
|
||||
else {
|
||||
const [{ badge, clazz }] = stack;
|
||||
action.setBadge(badge);
|
||||
if (clazz) {
|
||||
action.class = clazz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public create(container: HTMLElement): void {
|
||||
this.compositeSwitcherBar = new ActionBar(container, {
|
||||
actionItemProvider: (action: Action) => action instanceof CompositeOverflowActivityAction ? this.compositeOverflowActionItem : this.compositeIdToActionItems[action.id],
|
||||
orientation: this.options.orientation,
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
animated: false
|
||||
});
|
||||
this.updateCompositeSwitcher();
|
||||
|
||||
// Contextmenu for composites
|
||||
this.toDispose.push(dom.addDisposableListener(container, dom.EventType.CONTEXT_MENU, (e: MouseEvent) => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
this._onDidContextMenu.fire(e);
|
||||
}));
|
||||
|
||||
// Allow to drop at the end to move composites to the end
|
||||
this.toDispose.push(dom.addDisposableListener(container, dom.EventType.DROP, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId) {
|
||||
dom.EventHelper.stop(e, true);
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
|
||||
const targetId = this.pinnedComposites[this.pinnedComposites.length - 1];
|
||||
if (targetId !== draggedCompositeId) {
|
||||
this._onDidDropComposite.fire({ compositeId: draggedCompositeId, toCompositeId: this.pinnedComposites[this.pinnedComposites.length - 1] });
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private updateCompositeSwitcher(): void {
|
||||
if (!this.compositeSwitcherBar) {
|
||||
return; // We have not been rendered yet so there is nothing to update.
|
||||
}
|
||||
|
||||
let compositesToShow = this.pinnedComposites;
|
||||
|
||||
// Always show the active composite even if it is marked to be hidden
|
||||
if (this.activeCompositeId && !compositesToShow.some(id => id === this.activeCompositeId)) {
|
||||
this.activeUnpinnedCompositeId = this.activeCompositeId;
|
||||
compositesToShow = compositesToShow.concat(this.activeUnpinnedCompositeId);
|
||||
} else {
|
||||
this.activeUnpinnedCompositeId = void 0;
|
||||
}
|
||||
|
||||
// Ensure we are not showing more composites than we have height for
|
||||
let overflows = false;
|
||||
if (this.dimension) {
|
||||
let maxVisible = compositesToShow.length;
|
||||
let size = 0;
|
||||
const limit = this.options.orientation === ActionsOrientation.VERTICAL ? this.dimension.height : this.dimension.width;
|
||||
for (let i = 0; i < compositesToShow.length && size <= limit; i++) {
|
||||
size += this.options.getCompositeSize(compositesToShow[i]);
|
||||
if (size > limit) {
|
||||
maxVisible = i;
|
||||
}
|
||||
}
|
||||
overflows = compositesToShow.length > maxVisible;
|
||||
|
||||
if (overflows) {
|
||||
compositesToShow = compositesToShow.slice(0, maxVisible - 1 /* make room for overflow action */);
|
||||
}
|
||||
}
|
||||
|
||||
const visibleComposites = Object.keys(this.compositeIdToActions);
|
||||
const visibleCompositesChange = !arrays.equals(compositesToShow, visibleComposites);
|
||||
|
||||
// Pull out overflow action if there is a composite change so that we can add it to the end later
|
||||
if (this.compositeOverflowAction && visibleCompositesChange) {
|
||||
this.compositeSwitcherBar.pull(this.compositeSwitcherBar.length() - 1);
|
||||
|
||||
this.compositeOverflowAction.dispose();
|
||||
this.compositeOverflowAction = null;
|
||||
|
||||
this.compositeOverflowActionItem.dispose();
|
||||
this.compositeOverflowActionItem = null;
|
||||
}
|
||||
|
||||
// Pull out composites that overflow or got hidden
|
||||
visibleComposites.forEach(compositeId => {
|
||||
if (compositesToShow.indexOf(compositeId) === -1) {
|
||||
this.pullComposite(compositeId);
|
||||
}
|
||||
});
|
||||
|
||||
// Built actions for composites to show
|
||||
const newCompositesToShow = compositesToShow
|
||||
.filter(compositeId => !this.compositeIdToActions[compositeId])
|
||||
.map(compositeId => this.toAction(compositeId));
|
||||
|
||||
// Update when we have new composites to show
|
||||
if (newCompositesToShow.length) {
|
||||
|
||||
// Add to composite switcher
|
||||
this.compositeSwitcherBar.push(newCompositesToShow, { label: true, icon: true });
|
||||
|
||||
// Make sure to activate the active one
|
||||
if (this.activeCompositeId) {
|
||||
const activeCompositeEntry = this.compositeIdToActions[this.activeCompositeId];
|
||||
if (activeCompositeEntry) {
|
||||
activeCompositeEntry.activate();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to restore activity
|
||||
Object.keys(this.compositeIdToActions).forEach(compositeId => {
|
||||
this.updateActivity(compositeId);
|
||||
});
|
||||
}
|
||||
|
||||
// Add overflow action as needed
|
||||
if (visibleCompositesChange && overflows) {
|
||||
this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => this.compositeOverflowActionItem.showMenu());
|
||||
this.compositeOverflowActionItem = this.instantiationService.createInstance(
|
||||
CompositeOverflowActivityActionItem,
|
||||
this.compositeOverflowAction,
|
||||
() => this.getOverflowingComposites(),
|
||||
() => this.activeCompositeId,
|
||||
(compositeId: string) => this.compositeIdToActivityStack[compositeId] && this.compositeIdToActivityStack[compositeId][0].badge,
|
||||
this.options.getOpenCompositeAction
|
||||
);
|
||||
|
||||
this.compositeSwitcherBar.push(this.compositeOverflowAction, { label: true, icon: true });
|
||||
}
|
||||
}
|
||||
|
||||
private getOverflowingComposites(): { id: string, name: string }[] {
|
||||
let overflowingIds = this.pinnedComposites;
|
||||
if (this.activeUnpinnedCompositeId) {
|
||||
overflowingIds = overflowingIds.concat(this.activeUnpinnedCompositeId);
|
||||
}
|
||||
const visibleComposites = Object.keys(this.compositeIdToActions);
|
||||
|
||||
overflowingIds = overflowingIds.filter(compositeId => visibleComposites.indexOf(compositeId) === -1);
|
||||
return this.options.composites.filter(c => overflowingIds.indexOf(c.id) !== -1);
|
||||
}
|
||||
|
||||
public getVisibleComposites(): string[] {
|
||||
return Object.keys(this.compositeIdToActions);
|
||||
}
|
||||
|
||||
private pullComposite(compositeId: string): void {
|
||||
const index = Object.keys(this.compositeIdToActions).indexOf(compositeId);
|
||||
if (index >= 0) {
|
||||
this.compositeSwitcherBar.pull(index);
|
||||
|
||||
const action = this.compositeIdToActions[compositeId];
|
||||
action.dispose();
|
||||
delete this.compositeIdToActions[compositeId];
|
||||
|
||||
const actionItem = this.compositeIdToActionItems[action.id];
|
||||
actionItem.dispose();
|
||||
delete this.compositeIdToActionItems[action.id];
|
||||
}
|
||||
}
|
||||
|
||||
private toAction(compositeId: string): ActivityAction {
|
||||
const compositeActivityAction = this.options.getActivityAction(compositeId);
|
||||
const pinnedAction = this.options.getCompositePinnedAction(compositeId);
|
||||
this.compositeIdToActionItems[compositeId] = this.instantiationService.createInstance(CompositeActionItem, compositeActivityAction, pinnedAction);
|
||||
this.compositeIdToActions[compositeId] = compositeActivityAction;
|
||||
|
||||
return compositeActivityAction;
|
||||
}
|
||||
|
||||
public unpin(compositeId: string): void {
|
||||
const index = this.pinnedComposites.indexOf(compositeId);
|
||||
this.pinnedComposites.splice(index, 1);
|
||||
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
public isPinned(compositeId: string): boolean {
|
||||
return this.pinnedComposites.indexOf(compositeId) >= 0;
|
||||
}
|
||||
|
||||
public pin(compositeId: string, update = true): void {
|
||||
this.pinnedComposites.push(compositeId);
|
||||
this.pinnedComposites = arrays.distinct(this.pinnedComposites);
|
||||
|
||||
if (update) {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
public move(compositeId: string, toCompositeId: string): void {
|
||||
|
||||
const fromIndex = this.pinnedComposites.indexOf(compositeId);
|
||||
const toIndex = this.pinnedComposites.indexOf(toCompositeId);
|
||||
|
||||
this.pinnedComposites.splice(fromIndex, 1);
|
||||
this.pinnedComposites.splice(toIndex, 0, compositeId);
|
||||
|
||||
// Clear composites that are impacted by the move
|
||||
const visibleComposites = Object.keys(this.compositeIdToActions);
|
||||
for (let i = Math.min(fromIndex, toIndex); i < visibleComposites.length; i++) {
|
||||
this.pullComposite(visibleComposites[i]);
|
||||
}
|
||||
|
||||
// timeout helps to prevent artifacts from showing up
|
||||
setTimeout(() => {
|
||||
this.updateCompositeSwitcher();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
public store(): void {
|
||||
this.storageService.store(this.options.storageId, JSON.stringify(this.pinnedComposites), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,542 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { IActivityBarService, TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface ICompositeActivity {
|
||||
badge: IBadge;
|
||||
clazz: string;
|
||||
}
|
||||
|
||||
export class ActivityAction extends Action {
|
||||
private badge: IBadge;
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
|
||||
constructor(private _activity: IActivity) {
|
||||
super(_activity.id, _activity.name, _activity.cssClass);
|
||||
|
||||
this.badge = null;
|
||||
}
|
||||
|
||||
public get activity(): IActivity {
|
||||
return this._activity;
|
||||
}
|
||||
|
||||
public get onDidChangeBadge(): Event<this> {
|
||||
return this._onDidChangeBadge.event;
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
if (!this.checked) {
|
||||
this._setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
if (this.checked) {
|
||||
this._setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public getBadge(): IBadge {
|
||||
return this.badge;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.badge = badge;
|
||||
this._onDidChangeBadge.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ActivityActionItem extends BaseActionItem {
|
||||
protected $container: Builder;
|
||||
protected $label: Builder;
|
||||
protected $badge: Builder;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private mouseUpTimeout: number;
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
options: IBaseActionItemOptions,
|
||||
@IThemeService protected themeService: IThemeService
|
||||
) {
|
||||
super(null, action, options);
|
||||
|
||||
this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose);
|
||||
action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose);
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
return (this._action as ActivityAction).activity;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
|
||||
// Label
|
||||
if (this.$label) {
|
||||
const background = theme.getColor(ACTIVITY_BAR_FOREGROUND);
|
||||
|
||||
this.$label.style('background-color', background ? background.toString() : null);
|
||||
}
|
||||
|
||||
// Badge
|
||||
if (this.$badgeContent) {
|
||||
const badgeForeground = theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND);
|
||||
const badgeBackground = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND);
|
||||
const contrastBorderColor = theme.getColor(contrastBorder);
|
||||
|
||||
this.$badgeContent.style('color', badgeForeground ? badgeForeground.toString() : null);
|
||||
this.$badgeContent.style('background-color', badgeBackground ? badgeBackground.toString() : null);
|
||||
|
||||
this.$badgeContent.style('border-style', contrastBorderColor ? 'solid' : null);
|
||||
this.$badgeContent.style('border-width', contrastBorderColor ? '1px' : null);
|
||||
this.$badgeContent.style('border-color', contrastBorderColor ? contrastBorderColor.toString() : null);
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Make the container tab-able for keyboard navigation
|
||||
this.$container = $(container).attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
title: this.activity.name
|
||||
});
|
||||
|
||||
// Try hard to prevent keyboard only focus feedback when using mouse
|
||||
this.$container.on(dom.EventType.MOUSE_DOWN, () => {
|
||||
this.$container.addClass('clicked');
|
||||
});
|
||||
|
||||
this.$container.on(dom.EventType.MOUSE_UP, () => {
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.mouseUpTimeout = setTimeout(() => {
|
||||
this.$container.removeClass('clicked');
|
||||
}, 800); // delayed to prevent focus feedback from showing on mouse up
|
||||
});
|
||||
|
||||
// Label
|
||||
this.$label = $('a.action-label').appendTo(this.builder);
|
||||
if (this.activity.cssClass) {
|
||||
this.$label.addClass(this.activity.cssClass);
|
||||
}
|
||||
|
||||
this.$badge = this.builder.clone().div({ 'class': 'badge' }, (badge: Builder) => {
|
||||
this.$badgeContent = badge.div({ 'class': 'badge-content' });
|
||||
});
|
||||
|
||||
this.$badge.hide();
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onThemeChange(theme: ITheme): void {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.updateBadge(badge);
|
||||
}
|
||||
|
||||
protected updateBadge(badge: IBadge): void {
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
|
||||
if (badge) {
|
||||
|
||||
// Number
|
||||
if (badge instanceof NumberBadge) {
|
||||
if (badge.number) {
|
||||
this.$badgeContent.text(badge.number > 99 ? '99+' : badge.number.toString());
|
||||
this.$badge.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof TextBadge) {
|
||||
this.$badgeContent.text(badge.text);
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof IconBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Progress
|
||||
else if (badge instanceof ProgressBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
let title: string;
|
||||
if (badge && badge.getDescription()) {
|
||||
if (this.activity.name) {
|
||||
title = nls.localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription());
|
||||
} else {
|
||||
title = badge.getDescription();
|
||||
}
|
||||
} else {
|
||||
title = this.activity.name;
|
||||
}
|
||||
|
||||
[this.$label, this.$badge, this.$container].forEach(b => {
|
||||
if (b) {
|
||||
b.attr('aria-label', title);
|
||||
b.title(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleBadgeChangeEvenet(): void {
|
||||
const action = this.getAction();
|
||||
if (action instanceof ActivityAction) {
|
||||
this.updateBadge(action.getBadge());
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.$badge.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeOverflowActivityAction extends ActivityAction {
|
||||
|
||||
constructor(
|
||||
private showMenu: () => void
|
||||
) {
|
||||
super({
|
||||
id: 'activitybar.additionalComposites.action',
|
||||
name: nls.localize('additionalViews', "Additional Views"),
|
||||
cssClass: 'toggle-more'
|
||||
});
|
||||
}
|
||||
|
||||
public run(event: any): TPromise<any> {
|
||||
this.showMenu();
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeOverflowActivityActionItem extends ActivityActionItem {
|
||||
private name: string;
|
||||
private cssClass: string;
|
||||
private actions: Action[];
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
private getOverflowingComposites: () => { id: string, name: string }[],
|
||||
private getActiveCompositeId: () => string,
|
||||
private getBadge: (compositeId: string) => IBadge,
|
||||
private getCompositeOpenAction: (compositeId: string) => Action,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, null, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
this.name = action.label;
|
||||
}
|
||||
|
||||
public showMenu(): void {
|
||||
if (this.actions) {
|
||||
dispose(this.actions);
|
||||
}
|
||||
|
||||
this.actions = this.getActions();
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.builder.getHTMLElement(),
|
||||
getActions: () => TPromise.as(this.actions),
|
||||
onHide: () => dispose(this.actions)
|
||||
});
|
||||
}
|
||||
|
||||
private getActions(): Action[] {
|
||||
return this.getOverflowingComposites().map(composite => {
|
||||
const action = this.getCompositeOpenAction(composite.id);
|
||||
action.radio = this.getActiveCompositeId() === action.id;
|
||||
|
||||
const badge = this.getBadge(composite.id);
|
||||
let suffix: string | number;
|
||||
if (badge instanceof NumberBadge) {
|
||||
suffix = badge.number;
|
||||
} else if (badge instanceof TextBadge) {
|
||||
suffix = badge.text;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
action.label = nls.localize('numberBadge', "{0} ({1})", composite.name, suffix);
|
||||
} else {
|
||||
action.label = composite.name;
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.actions = dispose(this.actions);
|
||||
}
|
||||
}
|
||||
|
||||
class ManageExtensionAction extends Action {
|
||||
|
||||
constructor(
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
public run(id: string): TPromise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', id);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeActionItem extends ActivityActionItem {
|
||||
|
||||
private static manageExtensionAction: ManageExtensionAction;
|
||||
private static draggedCompositeId: string;
|
||||
|
||||
private compositeActivity: IActivity;
|
||||
private cssClass: string;
|
||||
|
||||
constructor(
|
||||
private compositeActivityAction: ActivityAction,
|
||||
private toggleCompositePinnedAction: Action,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IActivityBarService private activityBarService: IActivityBarService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(compositeActivityAction, { draggable: true }, themeService);
|
||||
|
||||
this.cssClass = compositeActivityAction.class;
|
||||
|
||||
if (!CompositeActionItem.manageExtensionAction) {
|
||||
CompositeActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
if (!this.compositeActivity) {
|
||||
let activityName: string;
|
||||
|
||||
const keybinding = this.getKeybindingLabel(this.compositeActivityAction.activity.id);
|
||||
if (keybinding) {
|
||||
activityName = nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding);
|
||||
} else {
|
||||
activityName = this.compositeActivityAction.activity.name;
|
||||
}
|
||||
|
||||
this.compositeActivity = {
|
||||
id: this.compositeActivityAction.activity.id,
|
||||
cssClass: this.cssClass,
|
||||
name: activityName
|
||||
};
|
||||
}
|
||||
|
||||
return this.compositeActivity;
|
||||
}
|
||||
|
||||
private getKeybindingLabel(id: string): string {
|
||||
const kb = this.keybindingService.lookupKeybinding(id);
|
||||
if (kb) {
|
||||
return kb.getLabel();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this.$container.on('contextmenu', e => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(container);
|
||||
});
|
||||
|
||||
// Allow to drag
|
||||
this.$container.on(dom.EventType.DRAG_START, (e: DragEvent) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
this.setDraggedComposite(this.activity.id);
|
||||
|
||||
// Trigger the action even on drag start to prevent clicks from failing that started a drag
|
||||
if (!this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
});
|
||||
|
||||
// Drag enter
|
||||
let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
|
||||
this.$container.on(dom.EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId && draggedCompositeId !== this.activity.id) {
|
||||
counter++;
|
||||
this.updateFromDragging(container, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag leave
|
||||
this.$container.on(dom.EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId) {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
this.updateFromDragging(container, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Drag end
|
||||
this.$container.on(dom.EventType.DRAG_END, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId) {
|
||||
counter = 0;
|
||||
this.updateFromDragging(container, false);
|
||||
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
}
|
||||
});
|
||||
|
||||
// Drop
|
||||
this.$container.on(dom.EventType.DROP, (e: DragEvent) => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId && draggedCompositeId !== this.activity.id) {
|
||||
this.updateFromDragging(container, false);
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
|
||||
this.activityBarService.move(draggedCompositeId, this.activity.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Activate on drag over to reveal targets
|
||||
[this.$badge, this.$label].forEach(b => new DelayedDragHandler(b.getHTMLElement(), () => {
|
||||
if (!CompositeActionItem.getDraggedCompositeId() && !this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
}));
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
const dragBackground = theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND);
|
||||
|
||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
|
||||
}
|
||||
|
||||
public static getDraggedCompositeId(): string {
|
||||
return CompositeActionItem.draggedCompositeId;
|
||||
}
|
||||
|
||||
private setDraggedComposite(compositeId: string): void {
|
||||
CompositeActionItem.draggedCompositeId = compositeId;
|
||||
}
|
||||
|
||||
public static clearDraggedComposite(): void {
|
||||
CompositeActionItem.draggedCompositeId = void 0;
|
||||
}
|
||||
|
||||
private showContextMenu(container: HTMLElement): void {
|
||||
const actions: Action[] = [this.toggleCompositePinnedAction];
|
||||
if ((<any>this.compositeActivityAction.activity).extensionId) {
|
||||
actions.push(new Separator());
|
||||
actions.push(CompositeActionItem.manageExtensionAction);
|
||||
}
|
||||
|
||||
const isPinned = this.activityBarService.isPinned(this.activity.id);
|
||||
if (isPinned) {
|
||||
this.toggleCompositePinnedAction.label = nls.localize('removeFromActivityBar', "Hide from Activity Bar");
|
||||
this.toggleCompositePinnedAction.checked = false;
|
||||
} else {
|
||||
this.toggleCompositePinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar");
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => container,
|
||||
getActionsContext: () => this.activity.id,
|
||||
getActions: () => TPromise.as(actions)
|
||||
});
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.$container.domFocus();
|
||||
}
|
||||
|
||||
protected _updateClass(): void {
|
||||
if (this.cssClass) {
|
||||
this.$badge.removeClass(this.cssClass);
|
||||
}
|
||||
|
||||
this.cssClass = this.getAction().class;
|
||||
this.$badge.addClass(this.cssClass);
|
||||
}
|
||||
|
||||
protected _updateChecked(): void {
|
||||
if (this.getAction().checked) {
|
||||
this.$container.addClass('checked');
|
||||
} else {
|
||||
this.$container.removeClass('checked');
|
||||
}
|
||||
}
|
||||
|
||||
protected _updateEnabled(): void {
|
||||
if (this.getAction().enabled) {
|
||||
this.builder.removeClass('disabled');
|
||||
} else {
|
||||
this.builder.addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
|
||||
this.$label.destroy();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue