diff options
author | Isidor Nikolic <inikolic@microsoft.com> | 2017-05-29 17:19:40 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-29 17:19:40 +0300 |
commit | 9b7daa878b985fc38b7e5d40f458432c16d8dd79 (patch) | |
tree | 87e2b5b70be2aa6341f7a2cba38261714c2f47df /src | |
parent | 84e2178de0aafc0cacc767204de7baf64161db44 (diff) | |
parent | 862088c2b1b0ba7ebfba1d71fc90f16bd7e94523 (diff) |
Merge pull request #27453 from Microsoft/joao/light-update
Joao/light update
Diffstat (limited to 'src')
10 files changed, 408 insertions, 147 deletions
diff --git a/src/vs/platform/update/common/updateIpc.ts b/src/vs/platform/update/common/updateIpc.ts index 2076719e98a..0dd76ca51f6 100644 --- a/src/vs/platform/update/common/updateIpc.ts +++ b/src/vs/platform/update/common/updateIpc.ts @@ -7,7 +7,8 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; -import Event from 'vs/base/common/event'; +import Event, { Emitter } from 'vs/base/common/event'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { IUpdateService, IRawUpdate, State, IUpdate } from './update'; export interface IUpdateChannel extends IChannel { @@ -18,6 +19,7 @@ export interface IUpdateChannel extends IChannel { call(command: 'event:onStateChange'): TPromise<void>; call(command: 'checkForUpdates', arg: boolean): TPromise<IUpdate>; call(command: 'quitAndInstall'): TPromise<void>; + call(command: '_getInitialState'): TPromise<State>; call(command: string, arg?: any): TPromise<any>; } @@ -34,6 +36,7 @@ export class UpdateChannel implements IUpdateChannel { case 'event:onStateChange': return eventToCall(this.service.onStateChange); case 'checkForUpdates': return this.service.checkForUpdates(arg); case 'quitAndInstall': return this.service.quitAndInstall(); + case '_getInitialState': return TPromise.as(this.service.state); } return undefined; } @@ -55,14 +58,24 @@ export class UpdateChannelClient implements IUpdateService { private _onUpdateReady = eventFromCall<IRawUpdate>(this.channel, 'event:onUpdateReady'); get onUpdateReady(): Event<IRawUpdate> { return this._onUpdateReady; } - private _onStateChange = eventFromCall<State>(this.channel, 'event:onStateChange'); - get onStateChange(): Event<State> { return this._onStateChange; } + private _onRemoteStateChange = eventFromCall<State>(this.channel, 'event:onStateChange'); + private _onStateChange = new Emitter<State>(); + get onStateChange(): Event<State> { return this._onStateChange.event; } private _state: State = State.Uninitialized; get state(): State { return this._state; }; - constructor(private channel: IChannel) { + constructor(private channel: IUpdateChannel) { + // always set this._state as the state changes this.onStateChange(state => this._state = state); + + channel.call('_getInitialState').done(state => { + // fire initial state + this._onStateChange.fire(state); + + // fire subsequent states as they come in from remote + this._onRemoteStateChange(state => this._onStateChange.fire(state)); + }, onUnexpectedError); } checkForUpdates(explicit: boolean): TPromise<IUpdate> { diff --git a/src/vs/workbench/browser/activity.ts b/src/vs/workbench/browser/activity.ts new file mode 100644 index 00000000000..5f23bef3a5e --- /dev/null +++ b/src/vs/workbench/browser/activity.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/platform'; +import { IAction } from 'vs/base/common/actions'; +import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; + +export interface IActivity { + id: string; + name: string; + cssClass: string; +} + +export interface IGlobalActivity extends IActivity { + getActions(): IAction[]; +} + +export const GlobalActivityExtensions = 'workbench.contributions.globalActivities'; + +export interface IGlobalActivityRegistry { + registerActivity(descriptor: IConstructorSignature0<IGlobalActivity>): void; + getActivities(): IConstructorSignature0<IGlobalActivity>[]; +} + +export class GlobalActivityRegistry implements IGlobalActivityRegistry { + + private activityDescriptors = new Set<IConstructorSignature0<IGlobalActivity>>(); + + registerActivity(descriptor: IConstructorSignature0<IGlobalActivity>): void { + this.activityDescriptors.add(descriptor); + } + + getActivities(): IConstructorSignature0<IGlobalActivity>[] { + const result: IConstructorSignature0<IGlobalActivity>[] = []; + this.activityDescriptors.forEach(d => result.push(d)); + return result; + } +} + +Registry.add(GlobalActivityExtensions, new GlobalActivityRegistry());
\ No newline at end of file diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index e1133c4e5ce..cc95a89bdd0 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -20,6 +20,7 @@ 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 } from 'vs/workbench/browser/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'; @@ -31,8 +32,12 @@ export class ActivityAction extends Action { private badge: IBadge; private _onDidChangeBadge = new Emitter<this>(); - constructor(id: string, name: string, clazz: string) { - super(id, name, clazz); + get activity(): IActivity { + return this._activity; + } + + constructor(private _activity: IActivity) { + super(_activity.id, _activity.name, _activity.cssClass); this.badge = null; } @@ -74,7 +79,7 @@ export class ViewletActivityAction extends ActivityAction { @IViewletService private viewletService: IViewletService, @IPartService private partService: IPartService ) { - super(viewlet.id, viewlet.name, viewlet.cssClass); + super(viewlet); } public run(event): TPromise<any> { @@ -102,7 +107,15 @@ export class ViewletActivityAction extends ActivityAction { } } -export abstract class ThemableActivityActionItem extends BaseActionItem { +export class ActivityActionItem extends BaseActionItem { + + protected $label: Builder; + protected $badge: Builder; + private $badgeContent: Builder; + + protected get activity(): IActivity { + return (this._action as ActivityAction).activity; + } constructor( action: ActivityAction, @@ -112,33 +125,126 @@ export abstract class ThemableActivityActionItem extends BaseActionItem { super(null, action, options); this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose); + action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose); + } + + 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); + + container.title = this.activity.name; + + // 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(); } - protected abstract updateStyles(): void; + 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(); + } + + this.$label.attr('aria-label', `${this.activity.name} - ${badge.getDescription()}`); + } + } + + private handleBadgeChangeEvenet(): void { + const action = this.getAction(); + if (action instanceof ActivityAction) { + this.updateBadge(action.getBadge()); + } + } + + public dispose(): void { + super.dispose(); + this.$badge.destroy(); + } } -export class ActivityActionItem extends ThemableActivityActionItem { +export class ViewletActionItem extends ActivityActionItem { private static manageExtensionAction: ManageExtensionAction; private static toggleViewletPinnedAction: ToggleViewletPinnedAction; private static draggedViewlet: ViewletDescriptor; private $container: Builder; - private $label: Builder; - private name: string; private _keybinding: string; private cssClass: string; - private $badge: Builder; - private $badgeContent: Builder; private mouseUpTimeout: number; + private get viewlet(): ViewletDescriptor { + return this.action.activity as ViewletDescriptor; + } + constructor( - action: ActivityAction, - private viewlet: ViewletDescriptor, + private action: ViewletActivityAction, @IContextMenuService private contextMenuService: IContextMenuService, @IActivityBarService private activityBarService: IActivityBarService, @IKeybindingService private keybindingService: IKeybindingService, @@ -148,42 +254,14 @@ export class ActivityActionItem extends ThemableActivityActionItem { super(action, { draggable: true }, themeService); this.cssClass = action.class; - this.name = viewlet.name; - this._keybinding = this.getKeybindingLabel(viewlet.id); - - if (!ActivityActionItem.manageExtensionAction) { - ActivityActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); - } - - if (!ActivityActionItem.toggleViewletPinnedAction) { - ActivityActionItem.toggleViewletPinnedAction = instantiationService.createInstance(ToggleViewletPinnedAction, void 0); - } + this._keybinding = this.getKeybindingLabel(this.viewlet.id); - action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose); - } - - 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); + if (!ViewletActionItem.manageExtensionAction) { + ViewletActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); } - // 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); + if (!ViewletActionItem.toggleViewletPinnedAction) { + ViewletActionItem.toggleViewletPinnedAction = instantiationService.createInstance(ToggleViewletPinnedAction, void 0); } } @@ -240,7 +318,7 @@ export class ActivityActionItem extends ThemableActivityActionItem { // Drag enter let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470 this.$container.on(DOM.EventType.DRAG_ENTER, (e: DragEvent) => { - const draggedViewlet = ActivityActionItem.getDraggedViewlet(); + const draggedViewlet = ViewletActionItem.getDraggedViewlet(); if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) { counter++; this.updateFromDragging(container, true); @@ -249,7 +327,7 @@ export class ActivityActionItem extends ThemableActivityActionItem { // Drag leave this.$container.on(DOM.EventType.DRAG_LEAVE, (e: DragEvent) => { - const draggedViewlet = ActivityActionItem.getDraggedViewlet(); + const draggedViewlet = ViewletActionItem.getDraggedViewlet(); if (draggedViewlet) { counter--; if (counter === 0) { @@ -260,12 +338,12 @@ export class ActivityActionItem extends ThemableActivityActionItem { // Drag end this.$container.on(DOM.EventType.DRAG_END, (e: DragEvent) => { - const draggedViewlet = ActivityActionItem.getDraggedViewlet(); + const draggedViewlet = ViewletActionItem.getDraggedViewlet(); if (draggedViewlet) { counter = 0; this.updateFromDragging(container, false); - ActivityActionItem.clearDraggedViewlet(); + ViewletActionItem.clearDraggedViewlet(); } }); @@ -273,34 +351,21 @@ export class ActivityActionItem extends ThemableActivityActionItem { this.$container.on(DOM.EventType.DROP, (e: DragEvent) => { DOM.EventHelper.stop(e, true); - const draggedViewlet = ActivityActionItem.getDraggedViewlet(); + const draggedViewlet = ViewletActionItem.getDraggedViewlet(); if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) { this.updateFromDragging(container, false); - ActivityActionItem.clearDraggedViewlet(); + ViewletActionItem.clearDraggedViewlet(); this.activityBarService.move(draggedViewlet.id, this.viewlet.id); } }); - // Label - this.$label = $('a.action-label').appendTo(this.builder); - if (this.cssClass) { - this.$label.addClass(this.cssClass); - } - - // Badge - this.$badge = this.builder.div({ 'class': 'badge' }, (badge: Builder) => { - this.$badgeContent = badge.div({ 'class': 'badge-content' }); - }); - - this.$badge.hide(); - // Keybinding this.keybinding = this._keybinding; // force update // Activate on drag over to reveal targets [this.$badge, this.$label].forEach(b => new DelayedDragHandler(b.getHTMLElement(), () => { - if (!ActivityActionItem.getDraggedViewlet() && !this.getAction().checked) { + if (!ViewletActionItem.getDraggedViewlet() && !this.getAction().checked) { this.getAction().run(); } })); @@ -316,29 +381,29 @@ export class ActivityActionItem extends ThemableActivityActionItem { } public static getDraggedViewlet(): ViewletDescriptor { - return ActivityActionItem.draggedViewlet; + return ViewletActionItem.draggedViewlet; } private setDraggedViewlet(viewlet: ViewletDescriptor): void { - ActivityActionItem.draggedViewlet = viewlet; + ViewletActionItem.draggedViewlet = viewlet; } public static clearDraggedViewlet(): void { - ActivityActionItem.draggedViewlet = void 0; + ViewletActionItem.draggedViewlet = void 0; } private showContextMenu(container: HTMLElement): void { - const actions: Action[] = [ActivityActionItem.toggleViewletPinnedAction]; + const actions: Action[] = [ViewletActionItem.toggleViewletPinnedAction]; if (this.viewlet.extensionId) { actions.push(new Separator()); - actions.push(ActivityActionItem.manageExtensionAction); + actions.push(ViewletActionItem.manageExtensionAction); } const isPinned = this.activityBarService.isPinned(this.viewlet.id); if (isPinned) { - ActivityActionItem.toggleViewletPinnedAction.label = nls.localize('removeFromActivityBar', "Remove from Activity Bar"); + ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('removeFromActivityBar', "Remove from Activity Bar"); } else { - ActivityActionItem.toggleViewletPinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar"); + ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar"); } this.contextMenuService.showContextMenu({ @@ -352,10 +417,6 @@ export class ActivityActionItem extends ThemableActivityActionItem { this.$container.domFocus(); } - public setBadge(badge: IBadge): void { - this.updateBadge(badge); - } - public set keybinding(keybinding: string) { this._keybinding = keybinding; @@ -365,49 +426,15 @@ export class ActivityActionItem extends ThemableActivityActionItem { let title: string; if (keybinding) { - title = nls.localize('titleKeybinding', "{0} ({1})", this.name, keybinding); + title = nls.localize('titleKeybinding', "{0} ({1})", this.activity.name, keybinding); } else { - title = this.name; + title = this.activity.name; } this.$label.title(title); this.$badge.title(title); } - private 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(); - } - - this.$label.attr('aria-label', `${this.name} - ${badge.getDescription()}`); - } - } - protected _updateClass(): void { if (this.cssClass) { this.$badge.removeClass(this.cssClass); @@ -425,13 +452,6 @@ export class ActivityActionItem extends ThemableActivityActionItem { } } - private handleBadgeChangeEvenet(): void { - const action = this.getAction(); - if (action instanceof ActivityAction) { - this.updateBadge(action.getBadge()); - } - } - protected _updateEnabled(): void { if (this.getAction().enabled) { this.builder.removeClass('disabled'); @@ -443,13 +463,12 @@ export class ActivityActionItem extends ThemableActivityActionItem { public dispose(): void { super.dispose(); - ActivityActionItem.clearDraggedViewlet(); + ViewletActionItem.clearDraggedViewlet(); if (this.mouseUpTimeout) { clearTimeout(this.mouseUpTimeout); } - this.$badge.destroy(); this.$label.destroy(); } } @@ -459,7 +478,11 @@ export class ViewletOverflowActivityAction extends ActivityAction { constructor( private showMenu: () => void ) { - super('activitybar.additionalViewlets.action', nls.localize('additionalViews', "Additional Views"), 'toggle-more'); + super({ + id: 'activitybar.additionalViewlets.action', + name: nls.localize('additionalViews', "Additional Views"), + cssClass: 'toggle-more' + }); } public run(event): TPromise<any> { @@ -469,8 +492,8 @@ export class ViewletOverflowActivityAction extends ActivityAction { } } -export class ViewletOverflowActivityActionItem extends ThemableActivityActionItem { - private $label: Builder; +export class ViewletOverflowActivityActionItem extends ActivityActionItem { + private name: string; private cssClass: string; private actions: OpenViewletAction[]; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index d824b944e0c..4085747c900 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -15,9 +15,11 @@ 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 { IGlobalActivity, GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/browser/activity'; +import { Registry } from 'vs/platform/platform'; import { Part } from 'vs/workbench/browser/part'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { ToggleViewletPinnedAction, ViewletActivityAction, ActivityAction, ActivityActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; +import { ToggleViewletPinnedAction, ViewletActivityAction, ActivityAction, ActivityActionItem, ViewletActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem } 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'; @@ -27,7 +29,7 @@ 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 } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/toggleActivityBarVisibility'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER } from 'vs/workbench/common/theme'; @@ -38,6 +40,40 @@ interface IViewletActivity { clazz: string; } +class GlobalActivityAction extends ActivityAction { + + constructor(activity: IGlobalActivity) { + super(activity); + } +} + +class GlobalActivityActionItem extends ActivityActionItem { + + constructor( + action: GlobalActivityAction, + @IThemeService themeService: IThemeService, + @IContextMenuService protected contextMenuService: IContextMenuService + ) { + super(action, { draggable: false }, themeService); + } + + onClick(e: MouseEvent): void { + const globalAction = this._action as GlobalActivityAction; + const activity = globalAction.activity as IGlobalActivity; + const actions = activity.getActions(); + + const event = new StandardMouseEvent(e); + event.stopPropagation(); + event.preventDefault(); + + this.contextMenuService.showContextMenu({ + getAnchor: () => ({ x: event.posx, y: event.posy }), + getActions: () => TPromise.as(actions), + onHide: () => dispose(actions) + }); + } +} + export class ActivitybarPart extends Part implements IActivityBarService { private static readonly ACTIVITY_ACTION_HEIGHT = 50; @@ -48,9 +84,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { private dimension: Dimension; private viewletSwitcherBar: ActionBar; + private activityActionBar: ActionBar; private viewletOverflowAction: ViewletOverflowActivityAction; private viewletOverflowActionItem: ViewletOverflowActivityActionItem; + private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; }; private viewletIdToActions: { [viewletId: string]: ActivityAction; }; private viewletIdToActionItems: { [viewletId: string]: IActionItem; }; private viewletIdToActivityStack: { [viewletId: string]: IViewletActivity[]; }; @@ -71,6 +109,7 @@ 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); @@ -123,6 +162,21 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } + public showGlobalActivity(globalActivityId: string, badge: IBadge): IDisposable { + if (!badge) { + throw illegalArgument('badge'); + } + + const action = this.globalActivityIdToActions[globalActivityId]; + + if (!action) { + throw illegalArgument('globalActivityId'); + } + + action.setBadge(badge); + return toDisposable(() => action.setBadge(undefined)); + } + public showActivity(viewletId: string, badge: IBadge, clazz?: string): IDisposable { if (!badge) { throw illegalArgument('badge'); @@ -180,6 +234,9 @@ export class ActivitybarPart extends Part implements IActivityBarService { // Top Actionbar with action items for each viewlet action this.createViewletSwitcher($result.clone()); + // 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); @@ -189,11 +246,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { // Allow to drop at the end to move viewlet to the end $(parent).on(DOM.EventType.DROP, (e: DragEvent) => { - const draggedViewlet = ActivityActionItem.getDraggedViewlet(); + const draggedViewlet = ViewletActionItem.getDraggedViewlet(); if (draggedViewlet) { DOM.EventHelper.stop(e, true); - ActivityActionItem.clearDraggedViewlet(); + ViewletActionItem.clearDraggedViewlet(); const targetId = this.pinnedViewlets[this.pinnedViewlets.length - 1]; if (targetId !== draggedViewlet.id) { @@ -252,6 +309,26 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.extensionService.onReady().then(() => this.updateViewletSwitcher()); } + private createGlobalActivityActionBar(container: HTMLElement): void { + const activityRegistry = Registry.as<IGlobalActivityRegistry>(GlobalActivityExtensions); + const descriptors = activityRegistry.getActivities(); + const actions = descriptors + .map(d => this.instantiationService.createInstance(d)) + .map(a => new GlobalActivityAction(a)); + + this.activityActionBar = new ActionBar(container, { + actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a), + orientation: ActionsOrientation.VERTICAL, + ariaLabel: nls.localize('globalActions', "Global Actions"), + animated: false + }); + + actions.forEach(a => { + this.globalActivityIdToActions[a.id] = a; + this.activityActionBar.push(a); + }); + } + private updateViewletSwitcher() { let viewletsToShow = this.getPinnedViewlets(); @@ -371,7 +448,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private toAction(viewlet: ViewletDescriptor): ActivityAction { const action = this.instantiationService.createInstance(ViewletActivityAction, viewlet); - this.viewletIdToActionItems[action.id] = this.instantiationService.createInstance(ActivityActionItem, action, viewlet); + this.viewletIdToActionItems[action.id] = this.instantiationService.createInstance(ViewletActionItem, action); this.viewletIdToActions[viewlet.id] = action; return action; diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 7acd81393fb..50061325e7e 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -9,6 +9,9 @@ .monaco-workbench > .activitybar > .content { height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; } .monaco-workbench > .activitybar > .content .monaco-action-bar { diff --git a/src/vs/workbench/parts/update/electron-browser/media/update.contribution.css b/src/vs/workbench/parts/update/electron-browser/media/update.contribution.css new file mode 100644 index 00000000000..a12923fce44 --- /dev/null +++ b/src/vs/workbench/parts/update/electron-browser/media/update.contribution.css @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.update-activity { + -webkit-mask: url('update.svg') no-repeat 50% 50%; + -webkit-mask-size: 22px; +}
\ No newline at end of file diff --git a/src/vs/workbench/parts/update/electron-browser/media/update.svg b/src/vs/workbench/parts/update/electron-browser/media/update.svg new file mode 100644 index 00000000000..3dec2ba50fd --- /dev/null +++ b/src/vs/workbench/parts/update/electron-browser/media/update.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#424242"><path d="M12.714 9.603c-.07.207-.15.407-.246.601l1.017 2.139c-.335.424-.718.807-1.142 1.143l-2.14-1.018c-.193.097-.394.176-.601.247l-.795 2.235c-.265.03-.534.05-.807.05-.272 0-.541-.02-.806-.05l-.795-2.235c-.207-.071-.408-.15-.602-.247l-2.14 1.017c-.424-.336-.807-.719-1.143-1.143l1.017-2.139c-.094-.193-.175-.393-.245-.6l-2.236-.796c-.03-.265-.05-.534-.05-.807s.02-.542.05-.807l2.236-.795c.07-.207.15-.407.246-.601l-1.016-2.139c.336-.423.719-.807 1.143-1.142l2.14 1.017c.193-.096.394-.176.602-.247l.793-2.236c.265-.03.534-.05.806-.05.273 0 .542.02.808.05l.795 2.236c.207.07.407.15.601.246l2.14-1.017c.424.335.807.719 1.142 1.142l-1.017 2.139c.096.194.176.394.246.601l2.236.795c.029.266.049.535.049.808s-.02.542-.05.807l-2.236.796zm-4.714-4.603c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><circle cx="8" cy="8" r="1.5"/></g></svg>
\ No newline at end of file diff --git a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts index 52f121006f5..26cc50dfe8e 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts @@ -6,20 +6,31 @@ 'use strict'; import * as nls from 'vs/nls'; +import 'vs/css!./media/update.contribution'; import { Registry } from 'vs/platform/platform'; +import product from 'vs/platform/node/product'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { ShowCurrentReleaseNotesAction, UpdateContribution } from 'vs/workbench/parts/update/electron-browser/update'; import { ReleaseNotesEditor } from 'vs/workbench/parts/update/electron-browser/releaseNotesEditor'; import { ReleaseNotesInput } from 'vs/workbench/parts/update/electron-browser/releaseNotesInput'; import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { IGlobalActivityRegistry, GlobalActivityExtensions } from 'vs/workbench/browser/activity'; import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, LightUpdateContribution } from './update'; Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(UpdateContribution); + .registerWorkbenchContribution(ProductContribution); + +if (product.quality === 'insider') { + Registry.as<IGlobalActivityRegistry>(GlobalActivityExtensions) + .registerActivity(LightUpdateContribution); +} else { + Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(UpdateContribution); +} // Editor const editorDescriptor = new EditorDescriptor( @@ -35,7 +46,6 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors) Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions) .registerWorkbenchAction(new SyncActionDescriptor(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Open Release Notes'); - // Configuration: Update const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 644f7bd40df..4b8eff773df 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -8,24 +8,28 @@ import nls = require('vs/nls'); import severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; -import { Action } from 'vs/base/common/actions'; +import { IAction, Action } from 'vs/base/common/actions'; +import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMessageService, CloseAction, Severity } from 'vs/platform/message/common/message'; import pkg from 'vs/platform/node/package'; import product from 'vs/platform/node/product'; import URI from 'vs/base/common/uri'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IActivityBarService, TextBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ReleaseNotesInput } from 'vs/workbench/parts/update/electron-browser/releaseNotesInput'; +import { IGlobalActivity } from 'vs/workbench/browser/activity'; import { IRequestService } from 'vs/platform/request/node/request'; import { asText } from 'vs/base/node/request'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IUpdateService } from 'vs/platform/update/common/update'; +import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update'; import * as semver from 'semver'; -import { OS } from 'vs/base/common/platform'; +import { OS, isLinux, isWindows } from 'vs/base/common/platform'; class ApplyUpdateAction extends Action { constructor( @IUpdateService private updateService: IUpdateService) { @@ -189,24 +193,22 @@ const LinkAction = (id: string, message: string, licenseUrl: string) => new Acti () => { window.open(licenseUrl); return TPromise.as(null); } ); -export class UpdateContribution implements IWorkbenchContribution { +export class ProductContribution implements IWorkbenchContribution { private static KEY = 'releaseNotes/lastVersion'; - getId() { return 'vs.update'; } + getId() { return 'vs.product'; } constructor( @IStorageService storageService: IStorageService, @IInstantiationService instantiationService: IInstantiationService, @IMessageService messageService: IMessageService, - @IUpdateService updateService: IUpdateService, @IWorkbenchEditorService editorService: IWorkbenchEditorService ) { - const lastVersion = storageService.get(UpdateContribution.KEY, StorageScope.GLOBAL, ''); + const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); - // was there an update? + // was there an update? if so, open release notes if (product.releaseNotesUrl && lastVersion && pkg.version !== lastVersion) { - instantiationService.invokeFunction(loadReleaseNotes, pkg.version) - .then( + instantiationService.invokeFunction(loadReleaseNotes, pkg.version).then( text => editorService.openEditor(instantiationService.createInstance(ReleaseNotesInput, pkg.version, text), { pinned: true }), () => { messageService.show(Severity.Info, { @@ -230,8 +232,19 @@ export class UpdateContribution implements IWorkbenchContribution { }); } - storageService.store(UpdateContribution.KEY, pkg.version, StorageScope.GLOBAL); + storageService.store(ProductContribution.KEY, pkg.version, StorageScope.GLOBAL); + } +} +export class UpdateContribution implements IWorkbenchContribution { + + getId() { return 'vs.update'; } + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @IMessageService messageService: IMessageService, + @IUpdateService updateService: IUpdateService + ) { updateService.onUpdateReady(update => { const applyUpdateAction = instantiationService.createInstance(ApplyUpdateAction); const releaseNotesAction = instantiationService.createInstance(ShowReleaseNotesAction, false, update.version); @@ -262,4 +275,69 @@ export class UpdateContribution implements IWorkbenchContribution { updateService.onError(err => messageService.show(severity.Error, err)); } +} + +export class LightUpdateContribution implements IGlobalActivity { + + get id() { return 'vs.update'; } + get name() { return ''; } + get cssClass() { return 'update-activity'; } + + constructor( + @IStorageService storageService: IStorageService, + @ICommandService private commandService: ICommandService, + @IInstantiationService instantiationService: IInstantiationService, + @IMessageService messageService: IMessageService, + @IUpdateService private updateService: IUpdateService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @IActivityBarService activityBarService: IActivityBarService + ) { + this.updateService.onUpdateReady(() => { + const badge = new TextBadge('\u21e9', () => nls.localize('updateIsReady', "New update available.")); + activityBarService.showGlobalActivity(this.id, badge); + }); + + this.updateService.onError(err => messageService.show(severity.Error, err)); + } + + getActions(): IAction[] { + return [ + new Action('showCommandPalette', nls.localize('commandPalette', "Command Palette..."), undefined, true, () => this.commandService.executeCommand('workbench.action.showCommands')), + new Separator(), + new Action('openKeybindings', nls.localize('settings', "Settings"), null, true, () => this.commandService.executeCommand('workbench.action.openGlobalSettings')), + new Action('openSettings', nls.localize('keyboardShortcuts', "Keyboard Shortcuts"), null, true, () => this.commandService.executeCommand('workbench.action.openGlobalKeybindings')), + new Separator(), + this.getUpdateAction() + ]; + } + + private getUpdateAction(): IAction { + switch (this.updateService.state) { + case UpdateState.Uninitialized: + return new Action('update.notavailable', nls.localize('not available', "Updates Not Available"), undefined, false); + + case UpdateState.CheckingForUpdate: + return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false); + + case UpdateState.UpdateAvailable: + if (isLinux) { + return new Action('update.linux.available', nls.localize('DownloadUpdate', "Download Available Update"), undefined, true, () => + this.updateService.quitAndInstall()); + } + + const updateAvailableLabel = isWindows + ? nls.localize('DownloadingUpdate', "Downloading Update...") + : nls.localize('InstallingUpdate', "Installing Update..."); + + return new Action('update.available', updateAvailableLabel, undefined, false); + + case UpdateState.UpdateDownloaded: + return new Action('update.restart', nls.localize('restartToUpdate', "Restart To Update..."), undefined, true, () => + this.updateService.quitAndInstall()); + + default: + return new Action('update.check', nls.localize('checkForUpdates', "Check For Updates..."), undefined, this.updateService.state === UpdateState.Idle, () => + this.updateService.checkForUpdates(true)); + } + } }
\ No newline at end of file diff --git a/src/vs/workbench/services/activity/common/activityBarService.ts b/src/vs/workbench/services/activity/common/activityBarService.ts index 810b062b63f..5c5b18a2105 100644 --- a/src/vs/workbench/services/activity/common/activityBarService.ts +++ b/src/vs/workbench/services/activity/common/activityBarService.ts @@ -64,6 +64,11 @@ export interface IActivityBarService { _serviceBrand: any; /** + * Show activity in the activitybar for the given global activity. + */ + showGlobalActivity(globalActivityId: string, badge: IBadge): IDisposable; + + /** * Show activity in the activitybar for the given viewlet. */ showActivity(viewletId: string, badge: IBadge, clazz?: string): IDisposable; |