activitybarPart.ts 16.2 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

B
Benjamin Pasero 已提交
8
import 'vs/css!./media/activitybarpart';
9
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
10
import { illegalArgument } from 'vs/base/common/errors';
11
import { $ } from 'vs/base/browser/builder';
12
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
13
import { GlobalActivityExtensions, IGlobalActivityRegistry, IActivity } from 'vs/workbench/common/activity';
14
import { Registry } from 'vs/platform/registry/common/platform';
J
Johannes Rieken 已提交
15
import { Part } from 'vs/workbench/browser/part';
16
import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
B
Benjamin Pasero 已提交
17
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
I
isidor 已提交
18
import { IBadge } from 'vs/workbench/services/activity/common/activity';
I
isidor 已提交
19
import { IPartService, Parts, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService';
J
Johannes Rieken 已提交
20
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
21
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
22
import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/toggleActivityBarVisibility';
B
Benjamin Pasero 已提交
23
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
I
isidor 已提交
24
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';
25
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
26
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
B
Benjamin Pasero 已提交
27 28 29 30
import { isMacintosh } from 'vs/base/common/platform';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { scheduleAtNextAnimationFrame, Dimension, createCSSRule } from 'vs/base/browser/dom';
import { Color } from 'vs/base/common/color';
31
import { ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
32
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
33 34 35
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';
36
import URI from 'vs/base/common/uri';
37 38 39

interface IPlaceholderComposite {
	id: string;
40
	iconUrl: URI;
41
}
E
Erich Gamma 已提交
42

I
isidor 已提交
43
export class ActivitybarPart extends Part {
44

45
	private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets';
46
	private static readonly PLACEHOLDER_VIEWLETS = 'workbench.activity.placeholderViewlets';
47
	private static readonly COLORS = {
I
isidor 已提交
48 49 50 51 52
		backgroundColor: ACTIVITY_BAR_FOREGROUND,
		badgeBackground: ACTIVITY_BAR_BADGE_BACKGROUND,
		badgeForeground: ACTIVITY_BAR_BADGE_FOREGROUND,
		dragAndDropBackground: ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND
	};
I
isidor 已提交
53
	private static readonly ACTION_HEIGHT = 50;
54

55
	public _serviceBrand: any;
P
Pine Wu 已提交
56

57 58
	private dimension: Dimension;

59 60 61
	private globalActionBar: ActionBar;
	private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; };

62
	private placeholderComposites: IPlaceholderComposite[] = [];
63
	private compositeBar: CompositeBar;
64
	private compositeActions: { [compositeId: string]: { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } };
65

E
Erich Gamma 已提交
66
	constructor(
67 68
		id: string,
		@IViewletService private viewletService: IViewletService,
I
isidor 已提交
69
		@IInstantiationService private instantiationService: IInstantiationService,
B
Benjamin Pasero 已提交
70
		@IPartService private partService: IPartService,
71
		@IThemeService themeService: IThemeService,
B
Benjamin Pasero 已提交
72
		@ILifecycleService private lifecycleService: ILifecycleService,
73
		@IStorageService private storageService: IStorageService,
B
Benjamin Pasero 已提交
74
		@IExtensionService extensionService: IExtensionService
E
Erich Gamma 已提交
75
	) {
B
Benjamin Pasero 已提交
76
		super(id, { hasTitle: false }, themeService);
E
Erich Gamma 已提交
77

J
Joao Moreno 已提交
78
		this.globalActivityIdToActions = Object.create(null);
79

80
		this.compositeActions = Object.create(null);
81
		this.compositeBar = this.instantiationService.createInstance(CompositeBar, {
I
isidor 已提交
82
			icon: true,
83 84
			storageId: ActivitybarPart.PINNED_VIEWLETS,
			orientation: ActionsOrientation.VERTICAL,
85
			openComposite: (compositeId: string) => this.viewletService.openViewlet(compositeId, true),
86 87
			getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
			getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
88
			getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, this.viewletService.getViewlet(compositeId)),
89
			getContextMenuActions: () => [this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))],
90
			getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(),
I
isidor 已提交
91
			hidePart: () => this.partService.setSideBarHidden(true),
92
			compositeSize: 50,
I
isidor 已提交
93
			colors: ActivitybarPart.COLORS,
I
isidor 已提交
94
			overflowActionSize: ActivitybarPart.ACTION_HEIGHT
95
		});
S
Sandeep Somavarapu 已提交
96
		const previousState = this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, void 0);
97 98 99 100 101 102 103 104 105 106 107 108 109
		if (previousState) {
			let parsedPreviousState = <IPlaceholderComposite[]>JSON.parse(previousState);
			parsedPreviousState.forEach((s) => {
				if (typeof s.iconUrl === 'object') {
					s.iconUrl = URI.revive(s.iconUrl);
				} else {
					s.iconUrl = void 0;
				}
			});
			this.placeholderComposites = parsedPreviousState;
		} else {
			this.placeholderComposites = this.compositeBar.getCompositesFromStorage().map(id => (<IPlaceholderComposite>{ id, iconUrl: void 0 }));
		}
110

E
Erich Gamma 已提交
111
		this.registerListeners();
112
		this.updateCompositebar();
113 114 115 116 117 118 119 120
		this.updatePlaceholderComposites();

		extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions());
	}

	private onDidRegisterExtensions(): void {
		this.removeNotExistingPlaceholderComposites();
		this.updateCompositebar();
E
Erich Gamma 已提交
121 122 123 124
	}

	private registerListeners(): void {

125
		this.toUnbind.push(this.viewletService.onDidViewletRegister(() => this.updateCompositebar()));
126

E
Erich Gamma 已提交
127
		// Activate viewlet action on opening of a viewlet
128
		this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId())));
E
Erich Gamma 已提交
129 130

		// Deactivate viewlet action on close
131
		this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId())));
I
isidor 已提交
132
		this.toUnbind.push(this.viewletService.onDidViewletEnablementChange(({ id, enabled }) => {
133
			if (enabled) {
S
Sandeep Somavarapu 已提交
134
				this.compositeBar.addComposite(this.viewletService.getViewlet(id));
135
			} else {
136
				this.removeComposite(id);
137 138
			}
		}));
E
Erich Gamma 已提交
139 140
	}

J
Joao Moreno 已提交
141
	public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
B
Benjamin Pasero 已提交
142
		if (this.viewletService.getViewlet(viewletOrActionId)) {
J
Joao Moreno 已提交
143
			return this.compositeBar.showActivity(viewletOrActionId, badge, clazz, priority);
B
Benjamin Pasero 已提交
144 145
		}

146
		return this.showGlobalActivity(viewletOrActionId, badge, clazz);
B
Benjamin Pasero 已提交
147 148
	}

149
	private showGlobalActivity(globalActivityId: string, badge: IBadge, clazz?: string): IDisposable {
J
Joao Moreno 已提交
150 151 152 153 154 155 156 157 158
		if (!badge) {
			throw illegalArgument('badge');
		}

		const action = this.globalActivityIdToActions[globalActivityId];
		if (!action) {
			throw illegalArgument('globalActivityId');
		}

159
		action.setBadge(badge, clazz);
B
Benjamin Pasero 已提交
160

J
Joao Moreno 已提交
161 162 163
		return toDisposable(() => action.setBadge(undefined));
	}

164
	public createContentArea(parent: HTMLElement): HTMLElement {
165 166
		const $el = $(parent);
		const $result = $('.content').appendTo($el);
E
Erich Gamma 已提交
167 168

		// Top Actionbar with action items for each viewlet action
I
isidor 已提交
169
		this.compositeBar.create($result.getHTMLElement());
E
Erich Gamma 已提交
170

171
		// Top Actionbar with action items for each viewlet action
I
isidor 已提交
172
		this.createGlobalActivityActionBar($('.global-activity').appendTo($result).getHTMLElement());
173

B
Benjamin Pasero 已提交
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
		// TODO@Ben: workaround for https://github.com/Microsoft/vscode/issues/45700
		// It looks like there are rendering glitches on macOS with Chrome 61 when
		// using --webkit-mask with a background color that is different from the image
		// The workaround is to promote the element onto its own drawing layer. We do
		// this only after the workbench has loaded because otherwise there is ugly flicker.
		if (isMacintosh) {
			this.lifecycleService.when(LifecyclePhase.Running).then(() => {
				scheduleAtNextAnimationFrame(() => { // another delay...
					scheduleAtNextAnimationFrame(() => { // ...to prevent more flickering on startup
						registerThemingParticipant((theme, collector) => {
							const activityBarForeground = theme.getColor(ACTIVITY_BAR_FOREGROUND);
							if (activityBarForeground && !activityBarForeground.equals(Color.white)) {
								// only apply this workaround if the color is different from the image one (white)
								collector.addRule('.monaco-workbench .activitybar > .content .monaco-action-bar .action-label { will-change: transform; }');
							}
						});
					});
				});
			});
		}

195
		return $result.getHTMLElement();
E
Erich Gamma 已提交
196 197
	}

198 199 200 201
	public updateStyles(): void {
		super.updateStyles();

		// Part container
202
		const container = $(this.getContainer());
203 204
		const background = this.getColor(ACTIVITY_BAR_BACKGROUND);
		container.style('background-color', background);
205

206
		const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder);
207
		const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT;
208 209 210 211 212 213 214
		container.style('box-sizing', borderColor && isPositionLeft ? 'border-box' : null);
		container.style('border-right-width', borderColor && isPositionLeft ? '1px' : null);
		container.style('border-right-style', borderColor && isPositionLeft ? 'solid' : null);
		container.style('border-right-color', isPositionLeft ? borderColor : null);
		container.style('border-left-width', borderColor && !isPositionLeft ? '1px' : null);
		container.style('border-left-style', borderColor && !isPositionLeft ? 'solid' : null);
		container.style('border-left-color', !isPositionLeft ? borderColor : null);
215 216
	}

J
Joao Moreno 已提交
217
	private createGlobalActivityActionBar(container: HTMLElement): void {
J
Joao Moreno 已提交
218
		const activityRegistry = Registry.as<IGlobalActivityRegistry>(GlobalActivityExtensions);
219 220 221 222 223
		const descriptors = activityRegistry.getActivities();
		const actions = descriptors
			.map(d => this.instantiationService.createInstance(d))
			.map(a => new GlobalActivityAction(a));

224
		this.globalActionBar = new ActionBar(container, {
I
isidor 已提交
225
			actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a, ActivitybarPart.COLORS),
226 227 228 229
			orientation: ActionsOrientation.VERTICAL,
			ariaLabel: nls.localize('globalActions', "Global Actions"),
			animated: false
		});
B
Benjamin Pasero 已提交
230
		this.toUnbind.push(this.globalActionBar);
231

J
Joao Moreno 已提交
232 233
		actions.forEach(a => {
			this.globalActivityIdToActions[a.id] = a;
234
			this.globalActionBar.push(a);
J
Joao Moreno 已提交
235
		});
236 237
	}

238 239 240
	private getCompositeActions(compositeId: string): { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } {
		let compositeActions = this.compositeActions[compositeId];
		if (!compositeActions) {
241 242 243 244 245 246 247 248 249 250
			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),
251
					pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar)
252 253
				};
			}
254 255 256 257 258
			this.compositeActions[compositeId] = compositeActions;
		}
		return compositeActions;
	}

259 260 261
	private updateCompositebar(): void {
		const viewlets = this.viewletService.getViewlets();
		for (const viewlet of viewlets) {
S
Sandeep Somavarapu 已提交
262 263 264 265 266 267 268 269 270 271 272 273
			this.compositeBar.addComposite(viewlet);

			// Pin it by default if it is new => it does not has a placeholder
			if (this.placeholderComposites.every(c => c.id !== viewlet.id)) {
				this.compositeBar.pin(viewlet.id);
			}

			this.enableCompositeActions(viewlet);
			const activeViewlet = this.viewletService.getActiveViewlet();
			if (activeViewlet && activeViewlet.getId() === viewlet.id) {
				this.compositeBar.pin(viewlet.id);
				this.compositeBar.activateComposite(viewlet.id);
274 275 276 277
			}
		}
	}

278 279 280 281
	private updatePlaceholderComposites(): void {
		const viewlets = this.viewletService.getViewlets();
		for (const { id } of this.placeholderComposites) {
			if (viewlets.every(viewlet => viewlet.id !== id)) {
S
Sandeep Somavarapu 已提交
282
				this.compositeBar.addComposite({ id, name: id, order: void 0 });
283 284 285 286 287 288 289 290 291 292 293 294 295
			}
		}
	}

	private removeNotExistingPlaceholderComposites(): void {
		const viewlets = this.viewletService.getViewlets();
		for (const { id } of this.placeholderComposites) {
			if (viewlets.every(viewlet => viewlet.id !== id)) {
				this.removeComposite(id);
			}
		}
	}

296 297 298 299 300 301 302 303 304 305
	private removeComposite(compositeId: string): void {
		this.compositeBar.removeComposite(compositeId);
		const compositeActions = this.compositeActions[compositeId];
		if (compositeActions) {
			compositeActions.activityAction.dispose();
			compositeActions.pinnedAction.dispose();
			delete this.compositeActions[compositeId];
		}
	}

306 307 308 309 310 311 312 313 314 315
	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);
		}
	}

316
	public getPinned(): string[] {
317
		return this.viewletService.getViewlets().map(v => v.id).filter(id => this.compositeBar.isPinned(id));
318 319
	}

320 321 322 323
	/**
	 * Layout title, content and status area in the given dimension.
	 */
	public layout(dimension: Dimension): Dimension[] {
I
isidor 已提交
324 325 326
		if (!this.partService.isVisible(Parts.ACTIVITYBAR_PART)) {
			return [dimension];
		}
327 328 329 330 331 332

		// Pass to super
		const sizes = super.layout(dimension);

		this.dimension = sizes[1];

333 334 335
		let availableHeight = this.dimension.height;
		if (this.globalActionBar) {
			// adjust height for global actions showing
I
isidor 已提交
336
			availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTION_HEIGHT);
337 338
		}
		this.compositeBar.layout(new Dimension(dimension.width, availableHeight));
339 340 341

		return sizes;
	}
I
isidor 已提交
342

343
	public shutdown(): void {
344
		const state = this.viewletService.getViewlets().map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl }));
345
		this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL);
346 347 348
		super.shutdown();
	}

E
Erich Gamma 已提交
349
	public dispose(): void {
350 351 352
		if (this.compositeBar) {
			this.compositeBar.dispose();
			this.compositeBar = null;
I
isidor 已提交
353 354
		}

B
Benjamin Pasero 已提交
355 356 357 358 359
		if (this.globalActionBar) {
			this.globalActionBar.dispose();
			this.globalActionBar = null;
		}

E
Erich Gamma 已提交
360 361
		super.dispose();
	}
362 363 364 365 366
}

class PlaceHolderViewletActivityAction extends ViewletActivityAction {

	constructor(
367
		id: string, iconUrl: URI,
368 369 370 371 372
		@IViewletService viewletService: IViewletService,
		@IPartService partService: IPartService,
		@ITelemetryService telemetryService: ITelemetryService
	) {
		super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, partService, telemetryService);
S
Sandeep Somavarapu 已提交
373 374 375
		// 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%`);
376 377 378 379 380 381 382 383 384 385 386 387 388 389
		this.enabled = false;
	}

	enable(activity: IActivity): void {
		this.label = activity.name;
		this.class = activity.cssClass;
		this.enabled = true;
	}

}

class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction {

	constructor(
390
		id: string, compositeBar: ICompositeBar
391 392 393 394 395 396 397 398 399 400
	) {
		super({ id, name: id, cssClass: void 0 }, compositeBar);
		this.enabled = false;
	}

	enable(activity: IActivity): void {
		this.label = activity.name;
		this.enabled = true;
	}

401
}