src/vs/workbench/browser/parts/activitybar/activitybarPart.ts (735 lines of code) (raw):
/*---------------------------------------------------------------------------------------------
* 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/activitybarpart';
import * as nls from 'vs/nls';
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIIVTY_ID } from 'vs/workbench/common/activity';
import { Part } from 'vs/workbench/browser/part';
import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActionViewItem, HomeAction, HomeActionViewItem, DeprecatedHomeAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workbench/browser/actions/layoutActions';
import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService';
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
import { Dimension, addClass, removeNode, createCSSRule, asCSSUrl, toggleClass } from 'vs/base/browser/dom';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions';
import { IViewDescriptorService, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { isUndefinedOrNull, assertIsDefined, isString } from 'vs/base/common/types';
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Schemas } from 'vs/base/common/network';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { isWeb } from 'vs/base/common/platform';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { Before2D } from 'vs/workbench/browser/dnd';
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
import { Action } from 'vs/base/common/actions';
import { Event } from 'vs/base/common/event';
interface IPlaceholderViewContainer {
id: string;
name?: string;
iconUrl?: UriComponents;
iconCSS?: string;
views?: { when?: string }[];
}
interface IPinnedViewContainer {
id: string;
pinned: boolean;
order?: number;
visible: boolean;
}
interface ICachedViewContainer {
id: string;
name?: string;
icon?: URI | string;
pinned: boolean;
order?: number;
visible: boolean;
views?: { when?: string }[];
}
export class ActivitybarPart extends Part implements IActivityBarService {
declare readonly _serviceBrand: undefined;
private static readonly ACTION_HEIGHT = 48;
static readonly PINNED_VIEW_CONTAINERS = 'workbench.activity.pinnedViewlets2';
private static readonly PLACEHOLDER_VIEW_CONTAINERS = 'workbench.activity.placeholderViewlets';
private static readonly HOME_BAR_VISIBILITY_PREFERENCE = 'workbench.activity.showHomeIndicator';
//#region IView
readonly minimumWidth: number = 48;
readonly maximumWidth: number = 48;
readonly minimumHeight: number = 0;
readonly maximumHeight: number = Number.POSITIVE_INFINITY;
//#endregion
private content: HTMLElement | undefined;
private homeBar: ActionBar | undefined;
private homeBarContainer: HTMLElement | undefined;
private menuBar: CustomMenubarControl | undefined;
private menuBarContainer: HTMLElement | undefined;
private compositeBar: CompositeBar;
private globalActivityAction: ActivityAction | undefined;
private globalActivityActionBar: ActionBar | undefined;
private readonly globalActivity: ICompositeActivity[] = [];
private accountsActivityAction: ActivityAction | undefined;
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction }>();
private readonly viewContainerDisposables = new Map<string, IDisposable>();
private readonly location = ViewContainerLocation.Sidebar;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IThemeService themeService: IThemeService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionService private readonly extensionService: IExtensionService,
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
@IViewsService private readonly viewsService: IViewsService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
) {
super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.PINNED_VIEW_CONTAINERS, version: 1 });
storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, version: 1 });
this.migrateFromOldCachedViewContainersValue();
for (const cachedViewContainer of this.cachedViewContainers) {
if (environmentService.configuration.remoteAuthority // In remote window, hide activity bar entries until registered.
|| this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer)
) {
cachedViewContainer.visible = false;
}
}
const cachedItems = this.cachedViewContainers
.map(v => ({ id: v.id, name: v.name, visible: v.visible, order: v.order, pinned: v.pinned }));
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, {
icon: true,
orientation: ActionsOrientation.VERTICAL,
openComposite: (compositeId: string) => this.viewsService.openViewContainer(compositeId, true),
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: (compositeId: string) => new Action(compositeId, '', '', true, () => this.viewsService.isViewContainerVisible(compositeId) ? Promise.resolve(this.viewsService.closeViewContainer(compositeId)) : this.viewsService.openViewContainer(compositeId)),
getContextMenuActions: () => {
const menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService);
const actions = [];
if (this.homeBarContainer) {
actions.push(new Action('toggleHomeBarAction',
this.homeBarVisibilityPreference ? nls.localize('hideHomeBar', "Hide Home Button") : nls.localize('showHomeBar', "Show Home Button"),
undefined,
true,
async () => { this.homeBarVisibilityPreference = !this.homeBarVisibilityPreference; }));
}
if (menuBarVisibility === 'compact' || (menuBarVisibility === 'hidden' && isWeb)) {
actions.push(this.instantiationService.createInstance(ToggleMenuBarAction, ToggleMenuBarAction.ID, menuBarVisibility === 'compact' ? nls.localize('hideMenu', "Hide Menu") : nls.localize('showMenu', "Show Menu")));
}
actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar")));
return actions;
},
getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId),
getDefaultCompositeId: () => this.viewDescriptorService.getDefaultViewContainer(this.location)!.id,
hidePart: () => this.layoutService.setSideBarHidden(true),
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Sidebar,
(id: string, focus?: boolean) => this.viewsService.openViewContainer(id, focus),
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.verticallyBefore),
() => this.compositeBar.getCompositeBarItems(),
),
compositeSize: 52,
colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme),
overflowActionSize: ActivitybarPart.ACTION_HEIGHT
}));
this.onDidRegisterViewContainers(this.getViewContainers());
this.registerListeners();
}
focusActivityBar(): void {
this.compositeBar.focus();
}
private getContextMenuActionsForComposite(compositeId: string): Action[] {
const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!;
const actions = [];
const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!;
if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) {
actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => {
this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation);
}));
} else {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
if (viewContainerModel.allViewDescriptors.length === 1) {
const viewToReset = viewContainerModel.allViewDescriptors[0];
const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!;
if (defaultContainer !== viewContainer) {
actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => {
this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer);
}));
}
}
}
return actions;
}
private registerListeners(): void {
// View Container Changes
this._register(this.viewDescriptorService.onDidChangeViewContainers(({ added, removed }) => this.onDidChangeViewContainers(added, removed)));
this._register(this.viewDescriptorService.onDidChangeContainerLocation(({ viewContainer, from, to }) => this.onDidChangeViewContainerLocation(viewContainer, from, to)));
// View Container Visibility Changes
this._register(Event.filter(this.viewsService.onDidChangeViewContainerVisibility, e => e.location === this.location)(({ id, visible }) => this.onDidChangeViewContainerVisibility(id, visible)));
// Extension registration
let disposables = this._register(new DisposableStore());
this._register(this.extensionService.onDidRegisterExtensions(() => {
disposables.clear();
this.onDidRegisterExtensions();
this.compositeBar.onDidChange(() => this.saveCachedViewContainers(), this, disposables);
this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e), this, disposables);
}));
// Register for configuration changes
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('window.menuBarVisibility')) {
if (getMenuBarVisibility(this.configurationService, this.environmentService) === 'compact') {
this.installMenubar();
} else {
this.uninstallMenubar();
}
}
}));
}
private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>) {
removed.filter(({ location }) => location === ViewContainerLocation.Sidebar).forEach(({ container }) => this.onDidDeregisterViewContainer(container));
this.onDidRegisterViewContainers(added.filter(({ location }) => location === ViewContainerLocation.Sidebar).map(({ container }) => container));
}
private onDidChangeViewContainerLocation(container: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation) {
if (from === this.location) {
this.onDidDeregisterViewContainer(container);
}
if (to === this.location) {
this.onDidRegisterViewContainers([container]);
}
}
private onDidChangeViewContainerVisibility(id: string, visible: boolean) {
if (visible) {
// Activate view container action on opening of a view container
this.onDidViewContainerVisible(id);
} else {
// Deactivate view container action on close
this.compositeBar.deactivateComposite(id);
}
}
private onDidChangeHomeBarVisibility(): void {
if (this.homeBarContainer) {
this.homeBarContainer.style.display = this.homeBarVisibilityPreference ? '' : 'none';
}
}
private onDidRegisterExtensions(): void {
this.removeNotExistingComposites();
this.saveCachedViewContainers();
}
private onDidViewContainerVisible(id: string): void {
const viewContainer = this.getViewContainer(id);
if (viewContainer) {
// Update the composite bar by adding
this.compositeBar.addComposite(viewContainer);
this.compositeBar.activateComposite(viewContainer.id);
if (viewContainer.hideIfEmpty) {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
if (viewContainerModel.activeViewDescriptors.length === 0) {
this.hideComposite(viewContainer.id); // Update the composite bar by hiding
}
}
}
}
showActivity(viewContainerOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
if (this.getViewContainer(viewContainerOrActionId)) {
return this.compositeBar.showActivity(viewContainerOrActionId, badge, clazz, priority);
}
if (viewContainerOrActionId === GLOBAL_ACTIVITY_ID) {
return this.showGlobalActivity(badge, clazz, priority);
}
if (viewContainerOrActionId === ACCOUNTS_ACTIIVTY_ID) {
if (this.accountsActivityAction) {
this.accountsActivityAction.setBadge(badge, clazz);
return toDisposable(() => this.accountsActivityAction?.setBadge(undefined));
}
}
return Disposable.None;
}
private showGlobalActivity(badge: IBadge, clazz?: string, priority?: number): IDisposable {
if (typeof priority !== 'number') {
priority = 0;
}
const activity: ICompositeActivity = { badge, clazz, priority };
for (let i = 0; i <= this.globalActivity.length; i++) {
if (i === this.globalActivity.length) {
this.globalActivity.push(activity);
break;
} else if (this.globalActivity[i].priority <= priority) {
this.globalActivity.splice(i, 0, activity);
break;
}
}
this.updateGlobalActivity();
return toDisposable(() => this.removeGlobalActivity(activity));
}
private removeGlobalActivity(activity: ICompositeActivity): void {
const index = this.globalActivity.indexOf(activity);
if (index !== -1) {
this.globalActivity.splice(index, 1);
this.updateGlobalActivity();
}
}
private updateGlobalActivity(): void {
const globalActivityAction = assertIsDefined(this.globalActivityAction);
if (this.globalActivity.length) {
const [{ badge, clazz, priority }] = this.globalActivity;
if (badge instanceof NumberBadge && this.globalActivity.length > 1) {
const cumulativeNumberBadge = this.getCumulativeNumberBadge(priority);
globalActivityAction.setBadge(cumulativeNumberBadge);
} else {
globalActivityAction.setBadge(badge, clazz);
}
} else {
globalActivityAction.setBadge(undefined);
}
}
private getCumulativeNumberBadge(priority: number): NumberBadge {
const numberActivities = this.globalActivity.filter(activity => activity.badge instanceof NumberBadge && activity.priority === priority);
let number = numberActivities.reduce((result, activity) => { return result + (<NumberBadge>activity.badge).number; }, 0);
let descriptorFn = (): string => {
return numberActivities.reduce((result, activity, index) => {
result = result + (<NumberBadge>activity.badge).getDescription();
if (index < numberActivities.length - 1) {
result = result + '\n';
}
return result;
}, '');
};
return new NumberBadge(number, descriptorFn);
}
private uninstallMenubar() {
if (this.menuBar) {
this.menuBar.dispose();
}
if (this.menuBarContainer) {
removeNode(this.menuBarContainer);
}
}
private installMenubar() {
this.menuBarContainer = document.createElement('div');
addClass(this.menuBarContainer, 'menubar');
const content = assertIsDefined(this.content);
content.prepend(this.menuBarContainer);
// Menubar: install a custom menu bar depending on configuration
this.menuBar = this._register(this.instantiationService.createInstance(CustomMenubarControl));
this.menuBar.create(this.menuBarContainer);
}
createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
this.content = document.createElement('div');
addClass(this.content, 'content');
parent.appendChild(this.content);
// Home action bar
const homeIndicator = this.environmentService.options?.homeIndicator;
if (homeIndicator) {
let codicon = iconRegistry.get(homeIndicator.icon);
if (!codicon) {
console.warn(`Unknown home indicator icon ${homeIndicator.icon}`);
codicon = Codicon.code;
}
this.createHomeBar(homeIndicator.href, homeIndicator.command, homeIndicator.title, codicon);
this.onDidChangeHomeBarVisibility();
}
// Install menubar if compact
if (getMenuBarVisibility(this.configurationService, this.environmentService) === 'compact') {
this.installMenubar();
}
// View Containers action bar
this.compositeBar.create(this.content);
// Global action bar
const globalActivities = document.createElement('div');
addClass(globalActivities, 'global-activity');
this.content.appendChild(globalActivities);
this.createGlobalActivityActionBar(globalActivities);
return this.content;
}
private createHomeBar(href: string, command: string | undefined, title: string, icon: Codicon): void {
this.homeBarContainer = document.createElement('div');
this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home"));
this.homeBarContainer.setAttribute('role', 'toolbar');
addClass(this.homeBarContainer, 'home-bar');
this.homeBar = this._register(new ActionBar(this.homeBarContainer, {
orientation: ActionsOrientation.VERTICAL,
animated: false,
ariaLabel: nls.localize('home', "Home"),
actionViewItemProvider: command ? undefined : action => new HomeActionViewItem(action),
allowContextMenu: true
}));
const homeBarIconBadge = document.createElement('div');
addClass(homeBarIconBadge, 'home-bar-icon-badge');
this.homeBarContainer.appendChild(homeBarIconBadge);
if (command) {
this.homeBar.push(this._register(this.instantiationService.createInstance(DeprecatedHomeAction, command, title, icon)), { icon: true, label: false });
} else {
this.homeBar.push(this._register(this.instantiationService.createInstance(HomeAction, href, title, icon)));
}
const content = assertIsDefined(this.content);
content.prepend(this.homeBarContainer);
}
updateStyles(): void {
super.updateStyles();
const container = assertIsDefined(this.getContainer());
const background = this.getColor(ACTIVITY_BAR_BACKGROUND) || '';
container.style.backgroundColor = background;
const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder) || '';
toggleClass(container, 'bordered', !!borderColor);
container.style.borderColor = borderColor ? borderColor : '';
}
private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors {
return {
activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND),
inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND),
activeBorderColor: theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER),
activeBackground: theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND),
badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
dragAndDropBorder: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BORDER),
activeBackgroundColor: undefined, inactiveBackgroundColor: undefined, activeBorderBottomColor: undefined,
};
}
private createGlobalActivityActionBar(container: HTMLElement): void {
this.globalActivityActionBar = this._register(new ActionBar(container, {
actionViewItemProvider: action => {
if (action.id === 'workbench.actions.manage') {
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme));
}
if (action.id === 'workbench.actions.accounts') {
return this.instantiationService.createInstance(AccountsActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme));
}
throw new Error(`No view item for action '${action.id}'`);
},
orientation: ActionsOrientation.VERTICAL,
ariaLabel: nls.localize('manage', "Manage"),
animated: false
}));
this.globalActivityAction = new ActivityAction({
id: 'workbench.actions.manage',
name: nls.localize('manage', "Manage"),
cssClass: Codicon.settingsGear.classNames
});
this.accountsActivityAction = new ActivityAction({
id: 'workbench.actions.accounts',
name: nls.localize('accounts', "Accounts"),
cssClass: Codicon.account.classNames
});
this.globalActivityActionBar.push(this.accountsActivityAction);
this.globalActivityActionBar.push(this.globalActivityAction);
}
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction } {
let compositeActions = this.compositeActions.get(compositeId);
if (!compositeActions) {
const viewContainer = this.getViewContainer(compositeId);
if (viewContainer) {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
compositeActions = {
activityAction: this.instantiationService.createInstance(ViewContainerActivityAction, this.toActivity(viewContainer, viewContainerModel)),
pinnedAction: new ToggleCompositePinnedAction(viewContainer, this.compositeBar)
};
} else {
const cachedComposite = this.cachedViewContainers.filter(c => c.id === compositeId)[0];
compositeActions = {
activityAction: this.instantiationService.createInstance(PlaceHolderViewContainerActivityAction, ActivitybarPart.toActivity(compositeId, compositeId, cachedComposite?.icon, undefined)),
pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar)
};
}
this.compositeActions.set(compositeId, compositeActions);
}
return compositeActions;
}
private onDidRegisterViewContainers(viewContainers: ReadonlyArray<ViewContainer>): void {
for (const viewContainer of viewContainers) {
const cachedViewContainer = this.cachedViewContainers.filter(({ id }) => id === viewContainer.id)[0];
const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location);
const isActive = visibleViewContainer?.id === viewContainer.id;
if (isActive || !this.shouldBeHidden(viewContainer.id, cachedViewContainer)) {
this.compositeBar.addComposite(viewContainer);
// Pin it by default if it is new
if (!cachedViewContainer) {
this.compositeBar.pin(viewContainer.id);
}
if (isActive) {
this.compositeBar.activateComposite(viewContainer.id);
}
}
}
for (const viewContainer of viewContainers) {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
this.updateActivity(viewContainer, viewContainerModel);
this.onDidChangeActiveViews(viewContainer, viewContainerModel);
const disposables = new DisposableStore();
disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewContainer, viewContainerModel)));
disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewContainer, viewContainerModel)));
this.viewContainerDisposables.set(viewContainer.id, disposables);
}
}
private onDidDeregisterViewContainer(viewContainer: ViewContainer): void {
const disposable = this.viewContainerDisposables.get(viewContainer.id);
if (disposable) {
disposable.dispose();
}
this.viewContainerDisposables.delete(viewContainer.id);
this.removeComposite(viewContainer.id);
}
private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
const activity: IActivity = this.toActivity(viewContainer, viewContainerModel);
const { activityAction, pinnedAction } = this.getCompositeActions(viewContainer.id);
activityAction.updateActivity(activity);
if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) {
pinnedAction.setActivity(activity);
}
this.saveCachedViewContainers();
}
private toActivity({ id, focusCommand }: ViewContainer, { icon, title: name }: IViewContainerModel): IActivity {
return ActivitybarPart.toActivity(id, name, icon, focusCommand?.id || id);
}
private static toActivity(id: string, name: string, icon: URI | string | undefined, keybindingId: string | undefined): IActivity {
let cssClass: string | undefined = undefined;
let iconUrl: URI | undefined = undefined;
if (URI.isUri(icon)) {
iconUrl = icon;
cssClass = `activity-${id.replace(/\./g, '-')}`;
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
createCSSRule(iconClass, `
mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
mask-size: 24px;
-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
-webkit-mask-size: 24px;
`);
} else if (isString(icon)) {
cssClass = icon;
}
return { id, name, cssClass, iconUrl, keybindingId };
}
private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
if (viewContainerModel.activeViewDescriptors.length) {
this.compositeBar.addComposite(viewContainer);
} else if (viewContainer.hideIfEmpty) {
this.hideComposite(viewContainer.id);
}
}
private shouldBeHidden(viewContainerId: string, cachedViewContainer?: ICachedViewContainer): boolean {
const viewContainer = this.getViewContainer(viewContainerId);
if (!viewContainer || !viewContainer.hideIfEmpty) {
return false;
}
return cachedViewContainer?.views && cachedViewContainer.views.length
? cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)))
: viewContainerId === TEST_VIEW_CONTAINER_ID /* Hide Test view container for the first time or it had no views registered before */;
}
private removeNotExistingComposites(): void {
const viewContainers = this.getViewContainers();
for (const { id } of this.cachedViewContainers) {
if (viewContainers.every(viewContainer => viewContainer.id !== id)) {
this.hideComposite(id);
}
}
}
private hideComposite(compositeId: string): void {
this.compositeBar.hideComposite(compositeId);
const compositeActions = this.compositeActions.get(compositeId);
if (compositeActions) {
compositeActions.activityAction.dispose();
compositeActions.pinnedAction.dispose();
this.compositeActions.delete(compositeId);
}
}
private removeComposite(compositeId: string): void {
this.compositeBar.removeComposite(compositeId);
const compositeActions = this.compositeActions.get(compositeId);
if (compositeActions) {
compositeActions.activityAction.dispose();
compositeActions.pinnedAction.dispose();
this.compositeActions.delete(compositeId);
}
}
getPinnedViewContainerIds(): string[] {
const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id);
return this.getViewContainers()
.filter(v => this.compositeBar.isPinned(v.id))
.sort((v1, v2) => pinnedCompositeIds.indexOf(v1.id) - pinnedCompositeIds.indexOf(v2.id))
.map(v => v.id);
}
getVisibleViewContainerIds(): string[] {
return this.compositeBar.getVisibleComposites()
.filter(v => this.viewsService.getVisibleViewContainer(this.location)?.id === v.id || this.compositeBar.isPinned(v.id))
.map(v => v.id);
}
layout(width: number, height: number): void {
if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) {
return;
}
// Layout contents
const contentAreaSize = super.layoutContents(width, height).contentSize;
// Layout composite bar
let availableHeight = contentAreaSize.height;
if (this.homeBarContainer) {
availableHeight -= this.homeBarContainer.clientHeight;
}
if (this.menuBarContainer) {
availableHeight -= this.menuBarContainer.clientHeight;
}
if (this.globalActivityActionBar) {
availableHeight -= (this.globalActivityActionBar.viewItems.length * ActivitybarPart.ACTION_HEIGHT); // adjust height for global actions showing
}
this.compositeBar.layout(new Dimension(width, availableHeight));
}
private getViewContainer(id: string): ViewContainer | undefined {
const viewContainer = this.viewDescriptorService.getViewContainerById(id);
return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined;
}
private getViewContainers(): ReadonlyArray<ViewContainer> {
return this.viewDescriptorService.getViewContainersByLocation(this.location);
}
private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void {
if (e.key === ActivitybarPart.PINNED_VIEW_CONTAINERS && e.scope === StorageScope.GLOBAL
&& this.pinnedViewContainersValue !== this.getStoredPinnedViewContainersValue() /* This checks if current window changed the value or not */) {
this._pinnedViewContainersValue = undefined;
this._cachedViewContainers = undefined;
const newCompositeItems: ICompositeBarItem[] = [];
const compositeItems = this.compositeBar.getCompositeBarItems();
for (const cachedViewContainer of this.cachedViewContainers) {
newCompositeItems.push({
id: cachedViewContainer.id,
name: cachedViewContainer.name,
order: cachedViewContainer.order,
pinned: cachedViewContainer.pinned,
visible: !!compositeItems.find(({ id }) => id === cachedViewContainer.id)
});
}
for (let index = 0; index < compositeItems.length; index++) {
// Add items currently exists but does not exist in new.
if (!newCompositeItems.some(({ id }) => id === compositeItems[index].id)) {
newCompositeItems.splice(index, 0, compositeItems[index]);
}
}
this.compositeBar.setCompositeBarItems(newCompositeItems);
}
if (e.key === ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE && e.scope === StorageScope.GLOBAL) {
this.onDidChangeHomeBarVisibility();
}
}
private saveCachedViewContainers(): void {
const state: ICachedViewContainer[] = [];
const compositeItems = this.compositeBar.getCompositeBarItems();
for (const compositeItem of compositeItems) {
const viewContainer = this.getViewContainer(compositeItem.id);
if (viewContainer) {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
const views: { when: string | undefined }[] = [];
for (const { when } of viewContainerModel.allViewDescriptors) {
views.push({ when: when ? when.serialize() : undefined });
}
const cacheIcon = URI.isUri(viewContainerModel.icon) ? viewContainerModel.icon.scheme === Schemas.file : true;
state.push({
id: compositeItem.id,
name: viewContainerModel.title,
icon: cacheIcon ? viewContainerModel.icon : undefined,
views,
pinned: compositeItem.pinned,
order: compositeItem.order,
visible: compositeItem.visible
});
} else {
state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false });
}
}
this.storeCachedViewContainersState(state);
}
private _cachedViewContainers: ICachedViewContainer[] | undefined = undefined;
private get cachedViewContainers(): ICachedViewContainer[] {
if (this._cachedViewContainers === undefined) {
this._cachedViewContainers = this.getPinnedViewContainers();
for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {
const cachedViewContainer = this._cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0];
if (cachedViewContainer) {
cachedViewContainer.name = placeholderViewContainer.name;
cachedViewContainer.icon = placeholderViewContainer.iconCSS ? placeholderViewContainer.iconCSS :
placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;
cachedViewContainer.views = placeholderViewContainer.views;
}
}
}
return this._cachedViewContainers;
}
private storeCachedViewContainersState(cachedViewContainers: ICachedViewContainer[]): void {
this.setPinnedViewContainers(cachedViewContainers.map(({ id, pinned, visible, order }) => (<IPinnedViewContainer>{
id,
pinned,
visible,
order
})));
this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views }) => (<IPlaceholderViewContainer>{
id,
iconUrl: URI.isUri(icon) ? icon : undefined,
iconCSS: isString(icon) ? icon : undefined,
name,
views
})));
}
private getPinnedViewContainers(): IPinnedViewContainer[] {
return JSON.parse(this.pinnedViewContainersValue);
}
private setPinnedViewContainers(pinnedViewContainers: IPinnedViewContainer[]): void {
this.pinnedViewContainersValue = JSON.stringify(pinnedViewContainers);
}
private _pinnedViewContainersValue: string | undefined;
private get pinnedViewContainersValue(): string {
if (!this._pinnedViewContainersValue) {
this._pinnedViewContainersValue = this.getStoredPinnedViewContainersValue();
}
return this._pinnedViewContainersValue;
}
private set pinnedViewContainersValue(pinnedViewContainersValue: string) {
if (this.pinnedViewContainersValue !== pinnedViewContainersValue) {
this._pinnedViewContainersValue = pinnedViewContainersValue;
this.setStoredPinnedViewContainersValue(pinnedViewContainersValue);
}
}
private getStoredPinnedViewContainersValue(): string {
return this.storageService.get(ActivitybarPart.PINNED_VIEW_CONTAINERS, StorageScope.GLOBAL, '[]');
}
private setStoredPinnedViewContainersValue(value: string): void {
this.storageService.store(ActivitybarPart.PINNED_VIEW_CONTAINERS, value, StorageScope.GLOBAL);
}
private getPlaceholderViewContainers(): IPlaceholderViewContainer[] {
return JSON.parse(this.placeholderViewContainersValue);
}
private setPlaceholderViewContainers(placeholderViewContainers: IPlaceholderViewContainer[]): void {
this.placeholderViewContainersValue = JSON.stringify(placeholderViewContainers);
}
private _placeholderViewContainersValue: string | undefined;
private get placeholderViewContainersValue(): string {
if (!this._placeholderViewContainersValue) {
this._placeholderViewContainersValue = this.getStoredPlaceholderViewContainersValue();
}
return this._placeholderViewContainersValue;
}
private set placeholderViewContainersValue(placeholderViewContainesValue: string) {
if (this.placeholderViewContainersValue !== placeholderViewContainesValue) {
this._placeholderViewContainersValue = placeholderViewContainesValue;
this.setStoredPlaceholderViewContainersValue(placeholderViewContainesValue);
}
}
private getStoredPlaceholderViewContainersValue(): string {
return this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEW_CONTAINERS, StorageScope.GLOBAL, '[]');
}
private setStoredPlaceholderViewContainersValue(value: string): void {
this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.GLOBAL);
}
private get homeBarVisibilityPreference(): boolean {
return this.storageService.getBoolean(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, StorageScope.GLOBAL, true);
}
private set homeBarVisibilityPreference(value: boolean) {
this.storageService.store(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, value, StorageScope.GLOBAL);
}
private migrateFromOldCachedViewContainersValue(): void {
const value = this.storageService.get('workbench.activity.pinnedViewlets', StorageScope.GLOBAL);
if (value !== undefined) {
const storedStates: Array<string | ICachedViewContainer> = JSON.parse(value);
const cachedViewContainers = storedStates.map(c => {
const serialized: ICachedViewContainer = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, icon: undefined, views: undefined } : c;
serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible;
return serialized;
});
this.storeCachedViewContainersState(cachedViewContainers);
this.storageService.remove('workbench.activity.pinnedViewlets', StorageScope.GLOBAL);
}
}
toJSON(): object {
return {
type: Parts.ACTIVITYBAR_PART
};
}
}
registerSingleton(IActivityBarService, ActivitybarPart);