diff --git a/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts b/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts index 6b91f2d66348f8d3c22ad72f42b2e566cae010b7..c2fde732a62141eeaeaed25eca31d6d4f97cd266 100644 --- a/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts @@ -172,7 +172,8 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution { id, descriptor.title, cssClass, - order + order, + descriptor.icon ); viewletRegistry.registerViewlet(viewletDescriptor); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 31c9b6ca46568b0eb6f210b8cdf833ae59d4b7f0..f0dcfa012c336b44a5f2b6435bc8640a24303a53 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -10,7 +10,7 @@ import * as nls from 'vs/nls'; import { illegalArgument } from 'vs/base/common/errors'; import { $ } from 'vs/base/browser/builder'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity'; +import { GlobalActivityExtensions, IGlobalActivityRegistry, IActivity } from 'vs/workbench/common/activity'; import { Registry } from 'vs/platform/registry/common/platform'; import { Part } from 'vs/workbench/browser/part'; import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; @@ -24,14 +24,23 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar'; -import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; +import { ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; import { ViewLocation, ViewsRegistry } from 'vs/workbench/common/views'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { Dimension } from 'vs/base/browser/dom'; +import { Dimension, createCSSRule } from 'vs/base/browser/dom'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +interface IPlaceholderComposite { + id: string; + iconUrl: string; +} export class ActivitybarPart extends Part { private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets'; + private static readonly PLACEHOLDER_VIEWLETS = 'workbench.activity.placeholderViewlets'; private static readonly COLORS = { backgroundColor: ACTIVITY_BAR_FOREGROUND, badgeBackground: ACTIVITY_BAR_BADGE_BACKGROUND, @@ -47,6 +56,8 @@ export class ActivitybarPart extends Part { private globalActionBar: ActionBar; private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; }; + private placeholderComposites: IPlaceholderComposite[] = []; + private extensionsRegistered: boolean = false; private compositeBar: CompositeBar; private compositeActions: { [compositeId: string]: { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } }; @@ -55,12 +66,15 @@ export class ActivitybarPart extends Part { @IViewletService private viewletService: IViewletService, @IInstantiationService private instantiationService: IInstantiationService, @IPartService private partService: IPartService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IStorageService private storageService: IStorageService, + @IExtensionService extensionService: IExtensionService, ) { super(id, { hasTitle: false }, themeService); this.globalActivityIdToActions = Object.create(null); + this.placeholderComposites = JSON.parse(this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, '[]')); this.compositeActions = Object.create(null); this.compositeBar = this.instantiationService.createInstance(CompositeBar, { icon: true, @@ -80,6 +94,15 @@ export class ActivitybarPart extends Part { this.registerListeners(); this.updateCompositebar(); + this.updatePlaceholderComposites(); + + extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions()); + } + + private onDidRegisterExtensions(): void { + this.extensionsRegistered = true; + this.removeNotExistingPlaceholderComposites(); + this.updateCompositebar(); } private registerListeners(): void { @@ -181,10 +204,19 @@ export class ActivitybarPart extends Part { private getCompositeActions(compositeId: string): { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } { let compositeActions = this.compositeActions[compositeId]; if (!compositeActions) { - compositeActions = { - activityAction: this.instantiationService.createInstance(ViewletActivityAction, this.viewletService.getViewlet(compositeId)), - pinnedAction: new ToggleCompositePinnedAction(this.viewletService.getViewlet(compositeId), this.compositeBar) - }; + const viewlet = this.viewletService.getViewlet(compositeId); + if (viewlet) { + compositeActions = { + activityAction: this.instantiationService.createInstance(ViewletActivityAction, viewlet), + pinnedAction: new ToggleCompositePinnedAction(viewlet, this.compositeBar) + }; + } else { + const placeHolderComposite = this.placeholderComposites.filter(c => c.id === compositeId)[0]; + compositeActions = { + activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, placeHolderComposite.iconUrl), + pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, placeHolderComposite.iconUrl, this.compositeBar) + }; + } this.compositeActions[compositeId] = compositeActions; } return compositeActions; @@ -193,9 +225,9 @@ export class ActivitybarPart extends Part { private updateCompositebar(): void { const viewlets = this.viewletService.getViewlets(); for (const viewlet of viewlets) { - const canShow = this.canShow(viewlet); - if (canShow) { + if (!this.extensionsRegistered || this.canShow(viewlet)) { this.compositeBar.addComposite(viewlet, false); + this.enableCompositeActions(viewlet); const activeViewlet = this.viewletService.getActiveViewlet(); if (activeViewlet && activeViewlet.getId() === viewlet.id) { this.compositeBar.pin(viewlet.id); @@ -207,6 +239,24 @@ export class ActivitybarPart extends Part { } } + private updatePlaceholderComposites(): void { + const viewlets = this.viewletService.getViewlets(); + for (const { id } of this.placeholderComposites) { + if (viewlets.every(viewlet => viewlet.id !== id)) { + this.compositeBar.addComposite({ id, name: id, order: void 0 }, false); + } + } + } + + private removeNotExistingPlaceholderComposites(): void { + const viewlets = this.viewletService.getViewlets(); + for (const { id } of this.placeholderComposites) { + if (viewlets.every(viewlet => viewlet.id !== id)) { + this.removeComposite(id); + } + } + } + private removeComposite(compositeId: string): void { this.compositeBar.removeComposite(compositeId); const compositeActions = this.compositeActions[compositeId]; @@ -217,6 +267,16 @@ export class ActivitybarPart extends Part { } } + private enableCompositeActions(viewlet: ViewletDescriptor): void { + const { activityAction, pinnedAction } = this.getCompositeActions(viewlet.id); + if (activityAction instanceof PlaceHolderViewletActivityAction) { + activityAction.enable(viewlet); + } + if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { + pinnedAction.enable(viewlet); + } + } + private canShow(viewlet: ViewletDescriptor): boolean { const viewLocation = ViewLocation.get(viewlet.id); if (viewLocation) { @@ -253,6 +313,8 @@ export class ActivitybarPart extends Part { } public shutdown(): void { + const state = this.viewletService.getViewlets().filter(viewlet => !!viewlet.iconUrl && this.canShow(viewlet)).map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl })); + this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL); this.compositeBar.shutdown(); super.shutdown(); } @@ -270,4 +332,43 @@ export class ActivitybarPart extends Part { super.dispose(); } +} + +class PlaceHolderViewletActivityAction extends ViewletActivityAction { + + constructor( + id: string, iconUrl: string, + @IViewletService viewletService: IViewletService, + @IPartService partService: IPartService, + @ITelemetryService telemetryService: ITelemetryService + ) { + super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, partService, telemetryService); + // Generate Placeholder CSS to show the icon in the activity bar + const iconClass = `.monaco-workbench > .activitybar .monaco-action-bar .action-label.${this.class}`; + createCSSRule(iconClass, `-webkit-mask: url('${iconUrl}') no-repeat 50% 50%`); + this.enabled = false; + } + + enable(activity: IActivity): void { + this.label = activity.name; + this.class = activity.cssClass; + this.enabled = true; + } + +} + +class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction { + + constructor( + id: string, iconUrl: string, compositeBar: ICompositeBar + ) { + super({ id, name: id, cssClass: void 0 }, compositeBar); + this.enabled = false; + } + + enable(activity: IActivity): void { + this.label = activity.name; + this.enabled = true; + } + } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts index 74cd45674465bc1a12087b4a208a0f0c47167b4b..001dd3a6842d5e50422520de37ecadb64e16a7ef 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts @@ -567,11 +567,13 @@ export class CompositeActionItem extends ActivityActionItem { protected _updateClass(): void { if (this.cssClass) { - this.$badge.removeClass(this.cssClass); + this.$label.removeClass(this.cssClass); } this.cssClass = this.getAction().class; - this.$badge.addClass(this.cssClass); + if (this.cssClass) { + this.$label.addClass(this.cssClass); + } } protected _updateChecked(): void { diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index e6ed31632feec0cdb552d1f9b9d4025027763259..147331a83ee3092f934ca5941d04131ae5b89d04 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -54,13 +54,13 @@ export class ViewletDescriptor extends CompositeDescriptor { name: string, cssClass?: string, order?: number, - private _extensionId?: string + private _iconUrl?: string ) { super(ctor, id, name, cssClass, order, id); } - public get extensionId(): string { - return this._extensionId; + public get iconUrl(): string { + return this._iconUrl; } }