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

import 'vs/css!./media/activityaction';
P
Pine Wu 已提交
7
import * as nls from 'vs/nls';
8
import * as DOM from 'vs/base/browser/dom';
P
Pine Wu 已提交
9
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
B
Benjamin Pasero 已提交
10
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
11
import { Action, IAction } from 'vs/base/common/actions';
P
Pine Wu 已提交
12 13 14
import { KeyCode } from 'vs/base/common/keyCodes';
import { dispose } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
15
import { SyncActionDescriptor, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
16
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
P
Pine Wu 已提交
17 18 19
import { Registry } from 'vs/platform/registry/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
M
Martin Aeschlimann 已提交
20
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
21
import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
22
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
P
Pine Wu 已提交
23
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
24
import { IActivity } from 'vs/workbench/common/activity';
25
import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
26
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
27
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
P
Pine Wu 已提交
28
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
29
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
30
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
B
Benjamin Pasero 已提交
31
import { ICommandService } from 'vs/platform/commands/common/commands';
M
Martin Aeschlimann 已提交
32
import { Codicon } from 'vs/base/common/codicons';
E
Erich Gamma 已提交
33

34 35
export class ViewletActivityAction extends ActivityAction {

36
	private static readonly preventDoubleClickDelay = 300;
37

S
Sandeep Somavarapu 已提交
38 39 40 41 42
	private readonly viewletService: IViewletService;
	private readonly layoutService: IWorkbenchLayoutService;
	private readonly telemetryService: ITelemetryService;

	private lastRun: number;
43 44

	constructor(
45
		activity: IActivity,
S
Sandeep Somavarapu 已提交
46 47 48
		@IViewletService viewletService: IViewletService,
		@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
		@ITelemetryService telemetryService: ITelemetryService
49
	) {
S
Sandeep Somavarapu 已提交
50
		ViewletActivityAction.generateIconCSS(activity);
51
		super(activity);
S
Sandeep Somavarapu 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

		this.lastRun = 0;
		this.viewletService = viewletService;
		this.layoutService = layoutService;
		this.telemetryService = telemetryService;
	}

	private static generateIconCSS(activity: IActivity): void {
		if (activity.iconUrl) {
			activity.cssClass = activity.cssClass || `activity-${activity.id.replace(/\./g, '-')}`;
			const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${activity.cssClass}`;
			DOM.createCSSRule(iconClass, `
				mask: ${DOM.asCSSUrl(activity.iconUrl)} no-repeat 50% 50%;
				mask-size: 24px;
				-webkit-mask: ${DOM.asCSSUrl(activity.iconUrl)} no-repeat 50% 50%;
				-webkit-mask-size: 24px;
			`);
		}
	}

	setActivity(activity: IActivity): void {
		if (activity.iconUrl && this.activity.cssClass !== activity.cssClass) {
			ViewletActivityAction.generateIconCSS(activity);
		}
		this.activity = activity;
77 78
	}

79
	async run(event: unknown): Promise<void> {
80
		if (event instanceof MouseEvent && event.button === 2) {
81
			return; // do not run on right click
82 83 84 85
		}

		// prevent accident trigger on a doubleclick (to help nervous people)
		const now = Date.now();
86
		if (now > this.lastRun /* https://github.com/Microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewletActivityAction.preventDoubleClickDelay) {
87
			return;
88 89 90
		}
		this.lastRun = now;

91
		const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART);
92 93 94
		const activeViewlet = this.viewletService.getActiveViewlet();

		// Hide sidebar if selected viewlet already visible
B
Benjamin Pasero 已提交
95
		if (sideBarVisible && activeViewlet?.getId() === this.activity.id) {
96
			this.logAction('hide');
97
			this.layoutService.setSideBarHidden(true);
98
			return;
99 100
		}

101
		this.logAction('show');
102 103
		await this.viewletService.openViewlet(this.activity.id, true);
		return this.activate();
104
	}
105 106

	private logAction(action: string) {
107 108 109 110 111
		type ActivityBarActionClassification = {
			viewletId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
			action: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
		};
		this.telemetryService.publicLog2<{ viewletId: String, action: String }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action });
112
	}
113 114
}

115
export class ToggleViewletAction extends Action {
116 117

	constructor(
B
Benjamin Pasero 已提交
118
		private _viewlet: ViewletDescriptor,
119
		@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
120
		@IViewletService private readonly viewletService: IViewletService
121
	) {
B
Benjamin Pasero 已提交
122 123 124
		super(_viewlet.id, _viewlet.name);
	}

125
	async run(): Promise<void> {
126
		const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART);
127 128 129
		const activeViewlet = this.viewletService.getActiveViewlet();

		// Hide sidebar if selected viewlet already visible
B
Benjamin Pasero 已提交
130
		if (sideBarVisible && activeViewlet?.getId() === this._viewlet.id) {
131
			this.layoutService.setSideBarHidden(true);
132
			return;
133 134
		}

135
		await this.viewletService.openViewlet(this._viewlet.id, true);
136 137 138
	}
}

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
export class AccountsActionViewItem extends ActivityActionViewItem {
	constructor(
		action: ActivityAction,
		colors: (theme: IColorTheme) => ICompositeBarColors,
		@IThemeService themeService: IThemeService,
		@IContextMenuService protected contextMenuService: IContextMenuService,
		@IMenuService protected menuService: IMenuService,
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
	) {
		super(action, { draggable: false, colors, icon: true }, themeService);
	}

	render(container: HTMLElement): void {
		super.render(container);

		// Context menus are triggered on mouse down so that an item can be picked
		// and executed with releasing the mouse over it

		this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
			DOM.EventHelper.stop(e, true);
			this.showContextMenu();
		}));

		this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
			let event = new StandardKeyboardEvent(e);
			if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
				DOM.EventHelper.stop(e, true);
				this.showContextMenu();
			}
		}));

		this._register(DOM.addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => {
			DOM.EventHelper.stop(e, true);
			this.showContextMenu();
		}));
	}

	private showContextMenu(): void {
		const accountsActions: IAction[] = [];
		const accountsMenu = this.menuService.createMenu(MenuId.AccountsContext, this.contextKeyService);
		const actionsDisposable = createAndFillInActionBarActions(accountsMenu, undefined, { primary: [], secondary: accountsActions });

		const containerPosition = DOM.getDomNodePagePosition(this.container);
		const location = { x: containerPosition.left + containerPosition.width / 2, y: containerPosition.top };
		this.contextMenuService.showContextMenu({
			getAnchor: () => location,
			getActions: () => accountsActions,
			onHide: () => {
				accountsMenu.dispose();
				dispose(actionsDisposable);
			}
		});
	}
}

194
export class GlobalActivityActionViewItem extends ActivityActionViewItem {
B
Benjamin Pasero 已提交
195 196

	constructor(
197
		action: ActivityAction,
M
Martin Aeschlimann 已提交
198
		colors: (theme: IColorTheme) => ICompositeBarColors,
B
Benjamin Pasero 已提交
199
		@IThemeService themeService: IThemeService,
200
		@IMenuService private readonly menuService: IMenuService,
B
Benjamin Pasero 已提交
201 202
		@IContextMenuService protected readonly contextMenuService: IContextMenuService,
		@IContextKeyService private readonly contextKeyService: IContextKeyService
B
Benjamin Pasero 已提交
203
	) {
I
isidor 已提交
204
		super(action, { draggable: false, colors, icon: true }, themeService);
B
Benjamin Pasero 已提交
205 206
	}

B
Benjamin Pasero 已提交
207
	render(container: HTMLElement): void {
208 209 210 211
		super.render(container);

		// Context menus are triggered on mouse down so that an item can be picked
		// and executed with releasing the mouse over it
B
Benjamin Pasero 已提交
212

B
Benjamin Pasero 已提交
213
		this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
214
			DOM.EventHelper.stop(e, true);
215
			this.showContextMenu();
B
Benjamin Pasero 已提交
216
		}));
217

B
Benjamin Pasero 已提交
218
		this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
219 220
			let event = new StandardKeyboardEvent(e);
			if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
221
				DOM.EventHelper.stop(e, true);
222
				this.showContextMenu();
223
			}
B
Benjamin Pasero 已提交
224
		}));
225

B
Benjamin Pasero 已提交
226
		this._register(DOM.addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => {
227
			DOM.EventHelper.stop(e, true);
228
			this.showContextMenu();
B
Benjamin Pasero 已提交
229
		}));
230 231
	}

232
	private showContextMenu(): void {
233 234
		const globalActivityActions: IAction[] = [];
		const globalActivityMenu = this.menuService.createMenu(MenuId.GlobalActivity, this.contextKeyService);
235
		const actionsDisposable = createAndFillInActionBarActions(globalActivityMenu, undefined, { primary: [], secondary: globalActivityActions });
236

237 238
		const containerPosition = DOM.getDomNodePagePosition(this.container);
		const location = { x: containerPosition.left + containerPosition.width / 2, y: containerPosition.top };
B
Benjamin Pasero 已提交
239
		this.contextMenuService.showContextMenu({
240
			getAnchor: () => location,
241 242 243
			getActions: () => globalActivityActions,
			onHide: () => {
				globalActivityMenu.dispose();
244
				dispose(actionsDisposable);
245
			}
B
Benjamin Pasero 已提交
246 247 248 249
		});
	}
}

250
export class PlaceHolderViewletActivityAction extends ViewletActivityAction {
B
Benjamin Pasero 已提交
251 252

	constructor(
B
Benjamin Pasero 已提交
253 254 255
		id: string,
		name: string,
		iconUrl: URI | undefined,
B
Benjamin Pasero 已提交
256
		@IViewletService viewletService: IViewletService,
257
		@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
B
Benjamin Pasero 已提交
258 259
		@ITelemetryService telemetryService: ITelemetryService
	) {
S
Sandeep Somavarapu 已提交
260
		super({ id, name: id, iconUrl }, viewletService, layoutService, telemetryService);
B
Benjamin Pasero 已提交
261 262 263
	}
}

264
export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction {
B
Benjamin Pasero 已提交
265 266

	constructor(id: string, compositeBar: ICompositeBar) {
R
Rob Lourens 已提交
267
		super({ id, name: id, cssClass: undefined }, compositeBar);
B
Benjamin Pasero 已提交
268 269 270 271 272 273 274
	}

	setActivity(activity: IActivity): void {
		this.label = activity.name;
	}
}

P
Pine Wu 已提交
275
class SwitchSideBarViewAction extends Action {
P
Pine Wu 已提交
276 277 278 279

	constructor(
		id: string,
		name: string,
280
		@IViewletService private readonly viewletService: IViewletService,
281
		@IActivityBarService private readonly activityBarService: IActivityBarService
P
Pine Wu 已提交
282 283 284 285
	) {
		super(id, name);
	}

286
	async run(offset: number): Promise<void> {
287
		const pinnedViewletIds = this.activityBarService.getPinnedViewletIds();
P
Pine Wu 已提交
288 289 290

		const activeViewlet = this.viewletService.getActiveViewlet();
		if (!activeViewlet) {
291
			return;
P
Pine Wu 已提交
292
		}
M
Matt Bierner 已提交
293
		let targetViewletId: string | undefined;
P
Pine Wu 已提交
294 295 296 297 298 299
		for (let i = 0; i < pinnedViewletIds.length; i++) {
			if (pinnedViewletIds[i] === activeViewlet.getId()) {
				targetViewletId = pinnedViewletIds[(i + pinnedViewletIds.length + offset) % pinnedViewletIds.length];
				break;
			}
		}
300 301

		await this.viewletService.openViewlet(targetViewletId, true);
P
Pine Wu 已提交
302 303 304
	}
}

P
Pine Wu 已提交
305
export class PreviousSideBarViewAction extends SwitchSideBarViewAction {
P
Pine Wu 已提交
306

P
Pine Wu 已提交
307
	static readonly ID = 'workbench.action.previousSideBarView';
308
	static readonly LABEL = nls.localize('previousSideBarView', 'Previous Side Bar View');
P
Pine Wu 已提交
309 310 311 312 313

	constructor(
		id: string,
		name: string,
		@IViewletService viewletService: IViewletService,
314
		@IActivityBarService activityBarService: IActivityBarService
P
Pine Wu 已提交
315
	) {
316
		super(id, name, viewletService, activityBarService);
P
Pine Wu 已提交
317 318
	}

319
	run(): Promise<void> {
P
Pine Wu 已提交
320 321 322 323
		return super.run(-1);
	}
}

P
Pine Wu 已提交
324
export class NextSideBarViewAction extends SwitchSideBarViewAction {
P
Pine Wu 已提交
325

P
Pine Wu 已提交
326
	static readonly ID = 'workbench.action.nextSideBarView';
327
	static readonly LABEL = nls.localize('nextSideBarView', 'Next Side Bar View');
P
Pine Wu 已提交
328 329 330 331 332

	constructor(
		id: string,
		name: string,
		@IViewletService viewletService: IViewletService,
333
		@IActivityBarService activityBarService: IActivityBarService
P
Pine Wu 已提交
334
	) {
335
		super(id, name, viewletService, activityBarService);
P
Pine Wu 已提交
336 337
	}

338
	run(): Promise<void> {
P
Pine Wu 已提交
339 340 341 342
		return super.run(1);
	}
}

B
Benjamin Pasero 已提交
343 344 345 346 347
export class HomeAction extends Action {

	constructor(
		private readonly command: string,
		name: string,
M
Martin Aeschlimann 已提交
348
		icon: Codicon,
B
Benjamin Pasero 已提交
349 350
		@ICommandService private readonly commandService: ICommandService
	) {
M
Martin Aeschlimann 已提交
351
		super('workbench.action.home', name, icon.classNames);
B
Benjamin Pasero 已提交
352 353 354 355 356 357
	}

	async run(): Promise<void> {
		this.commandService.executeCommand(this.command);
	}
}
P
Pine Wu 已提交
358

M
Martin Aeschlimann 已提交
359
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
360

361 362
	const activeForegroundColor = theme.getColor(ACTIVITY_BAR_FOREGROUND);
	if (activeForegroundColor) {
363
		collector.addRule(`
M
Miguel Solorio 已提交
364 365 366
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .action-label:not(.codicon),
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .action-label:not(.codicon),
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .action-label:not(.codicon) {
367
				background-color: ${activeForegroundColor} !important;
368
			}
B
Benjamin Pasero 已提交
369
			.monaco-workbench .activitybar > .content .home-bar > .monaco-action-bar .action-item .action-label.codicon,
M
Miguel Solorio 已提交
370 371 372 373 374
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .action-label.codicon,
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .action-label.codicon,
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .action-label.codicon {
				color: ${activeForegroundColor} !important;
			}
375 376 377
		`);
	}

378 379 380
	const activeBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER);
	if (activeBorderColor) {
		collector.addRule(`
381
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before {
382
				border-left-color: ${activeBorderColor};
383 384 385 386
			}
		`);
	}

387 388
	const activeFocusBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_FOCUS_BORDER);
	if (activeFocusBorderColor) {
389 390 391 392 393 394 395
		collector.addRule(`
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus::before {
				visibility: hidden;
			}

			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before {
				visibility: visible;
396
				border-left-color: ${activeFocusBorderColor};
397 398 399 400
			}
		`);
	}

401
	const activeBackgroundColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND);
M
Miguel Solorio 已提交
402
	if (activeBackgroundColor) {
403
		collector.addRule(`
404
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator {
405
				z-index: 0;
406 407 408 409 410
				background-color: ${activeBackgroundColor};
			}
		`);
	}

411
	// Styling with Outline color (e.g. high contrast theme)
412
	const outline = theme.getColor(activeContrastBorder);
413 414
	if (outline) {
		collector.addRule(`
S
SteVen Batten 已提交
415
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:before {
416 417
				content: "";
				position: absolute;
418 419 420
				top: 9px;
				left: 9px;
				height: 32px;
421 422 423
				width: 32px;
			}

S
SteVen Batten 已提交
424 425 426 427
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:before,
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:hover:before,
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:before,
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:hover:before {
428 429 430
				outline: 1px solid;
			}

S
SteVen Batten 已提交
431
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover:before {
432 433 434
				outline: 1px dashed;
			}

S
SteVen Batten 已提交
435
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
436 437 438
				border-left-color: ${outline};
			}

S
SteVen Batten 已提交
439 440 441 442 443
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:before,
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:hover:before,
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:before,
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:hover:before,
			.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover:before {
444 445 446 447 448 449 450
				outline-color: ${outline};
			}
		`);
	}

	// Styling without outline color
	else {
451
		const focusBorderColor = theme.getColor(focusBorder);
452 453
		if (focusBorderColor) {
			collector.addRule(`
S
SteVen Batten 已提交
454
					.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
455 456 457
						border-left-color: ${focusBorderColor};
					}
				`);
458
		}
459
	}
460
});
B
Benjamin Pasero 已提交
461 462 463 464

const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousSideBarViewAction, PreviousSideBarViewAction.ID, PreviousSideBarViewAction.LABEL), 'View: Previous Side Bar View', nls.localize('view', "View"));
registry.registerWorkbenchAction(SyncActionDescriptor.create(NextSideBarViewAction, NextSideBarViewAction.ID, NextSideBarViewAction.LABEL), 'View: Next Side Bar View', nls.localize('view', "View"));