mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
* - support hiding title in a part - support can move views in workbench - support globalLeftToolbar in pane composite part * clean up * chat feedback * fix compilation
722 lines
28 KiB
TypeScript
722 lines
28 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import './media/paneCompositePart.css';
|
|
import { Event } from '../../../base/common/event.js';
|
|
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
|
|
import { IProgressIndicator } from '../../../platform/progress/common/progress.js';
|
|
import { PaneComposite, PaneCompositeDescriptor, PaneCompositeRegistry } from '../panecomposite.js';
|
|
import { IPaneComposite } from '../../common/panecomposite.js';
|
|
import { IViewDescriptorService, ViewContainerLocation } from '../../common/views.js';
|
|
import { DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';
|
|
import { IView } from '../../../base/browser/ui/grid/grid.js';
|
|
import { IWorkbenchLayoutService, Parts } from '../../services/layout/browser/layoutService.js';
|
|
import { CompositePart, ICompositePartOptions, ICompositeTitleLabel } from './compositePart.js';
|
|
import { IPaneCompositeBarOptions, PaneCompositeBar } from './paneCompositeBar.js';
|
|
import { Dimension, EventHelper, trackFocus, $, addDisposableListener, EventType, prepend, getWindow } from '../../../base/browser/dom.js';
|
|
import { Registry } from '../../../platform/registry/common/platform.js';
|
|
import { INotificationService } from '../../../platform/notification/common/notification.js';
|
|
import { IStorageService } from '../../../platform/storage/common/storage.js';
|
|
import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js';
|
|
import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
|
|
import { IThemeService } from '../../../platform/theme/common/themeService.js';
|
|
import { IContextKey, IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
|
|
import { IExtensionService } from '../../services/extensions/common/extensions.js';
|
|
import { IComposite } from '../../common/composite.js';
|
|
import { localize } from '../../../nls.js';
|
|
import { CompositeDragAndDropObserver, toggleDropEffect } from '../dnd.js';
|
|
import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../common/theme.js';
|
|
import { IMenuService, MenuId } from '../../../platform/actions/common/actions.js';
|
|
import { ActionsOrientation } from '../../../base/browser/ui/actionbar/actionbar.js';
|
|
import { Gesture, EventType as GestureEventType } from '../../../base/browser/touch.js';
|
|
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
|
|
import { IAction, SubmenuAction } from '../../../base/common/actions.js';
|
|
import { Composite } from '../composite.js';
|
|
import { ViewsSubMenu } from './views/viewPaneContainer.js';
|
|
import { getActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js';
|
|
import { IHoverService } from '../../../platform/hover/browser/hover.js';
|
|
import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js';
|
|
import { DeferredPromise } from '../../../base/common/async.js';
|
|
|
|
export enum CompositeBarPosition {
|
|
TOP,
|
|
TITLE,
|
|
BOTTOM
|
|
}
|
|
|
|
export interface IPaneCompositePart extends IView {
|
|
|
|
readonly partId: Parts.PANEL_PART | Parts.AUXILIARYBAR_PART | Parts.SIDEBAR_PART;
|
|
|
|
readonly onDidPaneCompositeOpen: Event<IPaneComposite>;
|
|
readonly onDidPaneCompositeClose: Event<IPaneComposite>;
|
|
|
|
/**
|
|
* Opens a viewlet with the given identifier and pass keyboard focus to it if specified.
|
|
*/
|
|
openPaneComposite(id: string | undefined, focus?: boolean): Promise<IPaneComposite | undefined>;
|
|
|
|
/**
|
|
* Returns the current active viewlet if any.
|
|
*/
|
|
getActivePaneComposite(): IPaneComposite | undefined;
|
|
|
|
/**
|
|
* Returns the viewlet by id.
|
|
*/
|
|
getPaneComposite(id: string): PaneCompositeDescriptor | undefined;
|
|
|
|
/**
|
|
* Returns all enabled viewlets
|
|
*/
|
|
getPaneComposites(): PaneCompositeDescriptor[];
|
|
|
|
/**
|
|
* Returns the progress indicator for the side bar.
|
|
*/
|
|
getProgressIndicator(id: string): IProgressIndicator | undefined;
|
|
|
|
/**
|
|
* Hide the active viewlet.
|
|
*/
|
|
hideActivePaneComposite(): void;
|
|
|
|
/**
|
|
* Return the last active viewlet id.
|
|
*/
|
|
getLastActivePaneCompositeId(): string;
|
|
|
|
/**
|
|
* Returns id of pinned view containers following the visual order.
|
|
*/
|
|
getPinnedPaneCompositeIds(): string[];
|
|
|
|
/**
|
|
* Returns id of visible view containers following the visual order.
|
|
*/
|
|
getVisiblePaneCompositeIds(): string[];
|
|
|
|
/**
|
|
* Returns id of all view containers following the visual order.
|
|
*/
|
|
getPaneCompositeIds(): string[];
|
|
}
|
|
|
|
export abstract class AbstractPaneCompositePart extends CompositePart<PaneComposite> implements IPaneCompositePart {
|
|
|
|
private static readonly MIN_COMPOSITE_BAR_WIDTH = 50;
|
|
|
|
get snap(): boolean {
|
|
// Always allow snapping closed
|
|
// Only allow dragging open if the panel contains view containers
|
|
return this.layoutService.isVisible(this.partId) || !!this.paneCompositeBar.value?.getVisiblePaneCompositeIds().length;
|
|
}
|
|
|
|
get onDidPaneCompositeOpen(): Event<IPaneComposite> { return Event.map(this.onDidCompositeOpen.event, compositeEvent => <IPaneComposite>compositeEvent.composite); }
|
|
readonly onDidPaneCompositeClose = this.onDidCompositeClose.event as Event<IPaneComposite>;
|
|
|
|
private titleContainer: HTMLElement | undefined;
|
|
private headerFooterCompositeBarContainer: HTMLElement | undefined;
|
|
protected readonly headerFooterCompositeBarDispoables = this._register(new DisposableStore());
|
|
private paneCompositeBarContainer: HTMLElement | undefined;
|
|
private readonly paneCompositeBar = this._register(new MutableDisposable<PaneCompositeBar>());
|
|
private compositeBarPosition: CompositeBarPosition | undefined = undefined;
|
|
private emptyPaneMessageElement: HTMLElement | undefined;
|
|
|
|
private globalToolBar: MenuWorkbenchToolBar | undefined;
|
|
private globalLeftToolBar: MenuWorkbenchToolBar | undefined;
|
|
|
|
private blockOpening: DeferredPromise<PaneComposite | undefined> | undefined = undefined;
|
|
protected contentDimension: Dimension | undefined;
|
|
|
|
constructor(
|
|
readonly partId: Parts.PANEL_PART | Parts.AUXILIARYBAR_PART | Parts.SIDEBAR_PART,
|
|
partOptions: ICompositePartOptions,
|
|
activePaneCompositeSettingsKey: string,
|
|
private readonly activePaneContextKey: IContextKey<string>,
|
|
private paneFocusContextKey: IContextKey<boolean>,
|
|
nameForTelemetry: string,
|
|
compositeCSSClass: string,
|
|
titleForegroundColor: string | undefined,
|
|
titleBorderColor: string | undefined,
|
|
protected readonly location: ViewContainerLocation,
|
|
registryId: string,
|
|
private readonly globalActionsMenuId: MenuId,
|
|
@INotificationService notificationService: INotificationService,
|
|
@IStorageService storageService: IStorageService,
|
|
@IContextMenuService contextMenuService: IContextMenuService,
|
|
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
|
@IKeybindingService keybindingService: IKeybindingService,
|
|
@IHoverService hoverService: IHoverService,
|
|
@IInstantiationService instantiationService: IInstantiationService,
|
|
@IThemeService themeService: IThemeService,
|
|
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
|
|
@IContextKeyService protected readonly contextKeyService: IContextKeyService,
|
|
@IExtensionService private readonly extensionService: IExtensionService,
|
|
@IMenuService protected readonly menuService: IMenuService,
|
|
) {
|
|
super(
|
|
notificationService,
|
|
storageService,
|
|
contextMenuService,
|
|
layoutService,
|
|
keybindingService,
|
|
hoverService,
|
|
instantiationService,
|
|
themeService,
|
|
Registry.as<PaneCompositeRegistry>(registryId),
|
|
activePaneCompositeSettingsKey,
|
|
viewDescriptorService.getDefaultViewContainer(location)?.id || '',
|
|
nameForTelemetry,
|
|
compositeCSSClass,
|
|
titleForegroundColor,
|
|
titleBorderColor,
|
|
partId,
|
|
partOptions
|
|
);
|
|
this.registerListeners();
|
|
}
|
|
|
|
private registerListeners(): void {
|
|
this._register(this.onDidPaneCompositeOpen(composite => this.onDidOpen(composite)));
|
|
this._register(this.onDidPaneCompositeClose(this.onDidClose, this));
|
|
|
|
this._register(this.registry.onDidDeregister((viewletDescriptor: PaneCompositeDescriptor) => {
|
|
|
|
const activeContainers = this.viewDescriptorService.getViewContainersByLocation(this.location)
|
|
.filter(container => this.viewDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0);
|
|
|
|
if (activeContainers.length) {
|
|
if (this.getActiveComposite()?.getId() === viewletDescriptor.id) {
|
|
const defaultViewletId = this.viewDescriptorService.getDefaultViewContainer(this.location)?.id;
|
|
const containerToOpen = activeContainers.filter(c => c.id === defaultViewletId)[0] || activeContainers[0];
|
|
this.doOpenPaneComposite(containerToOpen.id);
|
|
}
|
|
} else {
|
|
this.layoutService.setPartHidden(true, this.partId);
|
|
}
|
|
|
|
this.removeComposite(viewletDescriptor.id);
|
|
}));
|
|
|
|
this._register(this.extensionService.onDidRegisterExtensions(() => {
|
|
this.layoutCompositeBar();
|
|
}));
|
|
}
|
|
|
|
private onDidOpen(composite: IComposite): void {
|
|
this.activePaneContextKey.set(composite.getId());
|
|
}
|
|
|
|
private onDidClose(composite: IComposite): void {
|
|
const id = composite.getId();
|
|
if (this.activePaneContextKey.get() === id) {
|
|
this.activePaneContextKey.reset();
|
|
}
|
|
}
|
|
|
|
protected override showComposite(composite: Composite): void {
|
|
super.showComposite(composite);
|
|
this.layoutCompositeBar();
|
|
this.layoutEmptyMessage();
|
|
}
|
|
|
|
protected override hideActiveComposite(): Composite | undefined {
|
|
const composite = super.hideActiveComposite();
|
|
this.layoutCompositeBar();
|
|
this.layoutEmptyMessage();
|
|
return composite;
|
|
}
|
|
|
|
override create(parent: HTMLElement): void {
|
|
this.element = parent;
|
|
this.element.classList.add('pane-composite-part');
|
|
|
|
super.create(parent);
|
|
|
|
if (this.contentArea) {
|
|
this.createEmptyPaneMessage(this.contentArea);
|
|
}
|
|
|
|
this.updateCompositeBar();
|
|
|
|
const focusTracker = this._register(trackFocus(parent));
|
|
this._register(focusTracker.onDidFocus(() => this.paneFocusContextKey.set(true)));
|
|
this._register(focusTracker.onDidBlur(() => this.paneFocusContextKey.set(false)));
|
|
}
|
|
|
|
private createEmptyPaneMessage(parent: HTMLElement): void {
|
|
this.emptyPaneMessageElement = $('.empty-pane-message-area');
|
|
|
|
const messageElement = $('.empty-pane-message');
|
|
messageElement.textContent = localize('pane.emptyMessage', "Drag a view here to display.");
|
|
|
|
this.emptyPaneMessageElement.appendChild(messageElement);
|
|
parent.appendChild(this.emptyPaneMessageElement);
|
|
|
|
const setDropBackgroundFeedback = (visible: boolean) => {
|
|
const updateActivityBarBackground = !this.getActiveComposite() || !visible;
|
|
const backgroundColor = visible ? this.theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND)?.toString() || '' : '';
|
|
|
|
if (this.titleContainer && updateActivityBarBackground) {
|
|
this.titleContainer.style.backgroundColor = backgroundColor;
|
|
}
|
|
if (this.headerFooterCompositeBarContainer && updateActivityBarBackground) {
|
|
this.headerFooterCompositeBarContainer.style.backgroundColor = backgroundColor;
|
|
}
|
|
|
|
this.emptyPaneMessageElement!.style.backgroundColor = backgroundColor;
|
|
};
|
|
|
|
if (this.viewDescriptorService.canMoveViews()) {
|
|
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, {
|
|
onDragOver: (e) => {
|
|
EventHelper.stop(e.eventData, true);
|
|
if (this.paneCompositeBar.value) {
|
|
const validDropTarget = this.paneCompositeBar.value.dndHandler.onDragEnter(e.dragAndDropData, undefined, e.eventData);
|
|
toggleDropEffect(e.eventData.dataTransfer, 'move', validDropTarget);
|
|
}
|
|
},
|
|
onDragEnter: (e) => {
|
|
EventHelper.stop(e.eventData, true);
|
|
if (this.paneCompositeBar.value) {
|
|
const validDropTarget = this.paneCompositeBar.value.dndHandler.onDragEnter(e.dragAndDropData, undefined, e.eventData);
|
|
setDropBackgroundFeedback(validDropTarget);
|
|
}
|
|
},
|
|
onDragLeave: (e) => {
|
|
EventHelper.stop(e.eventData, true);
|
|
setDropBackgroundFeedback(false);
|
|
},
|
|
onDragEnd: (e) => {
|
|
EventHelper.stop(e.eventData, true);
|
|
setDropBackgroundFeedback(false);
|
|
},
|
|
onDrop: (e) => {
|
|
EventHelper.stop(e.eventData, true);
|
|
setDropBackgroundFeedback(false);
|
|
if (this.paneCompositeBar.value) {
|
|
this.paneCompositeBar.value.dndHandler.drop(e.dragAndDropData, undefined, e.eventData);
|
|
} else {
|
|
// Allow opening views/composites if the composite bar is hidden
|
|
const dragData = e.dragAndDropData.getData();
|
|
|
|
if (dragData.type === 'composite') {
|
|
const currentContainer = this.viewDescriptorService.getViewContainerById(dragData.id)!;
|
|
this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.location, undefined, 'dnd');
|
|
this.openPaneComposite(currentContainer.id, true);
|
|
}
|
|
|
|
else if (dragData.type === 'view') {
|
|
const viewToMove = this.viewDescriptorService.getViewDescriptorById(dragData.id)!;
|
|
if (viewToMove.canMoveView) {
|
|
this.viewDescriptorService.moveViewToLocation(viewToMove, this.location, 'dnd');
|
|
|
|
const newContainer = this.viewDescriptorService.getViewContainerByViewId(viewToMove.id)!;
|
|
|
|
this.openPaneComposite(newContainer.id, true).then(composite => {
|
|
composite?.openView(viewToMove.id, true);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}));
|
|
}
|
|
}
|
|
|
|
protected override createTitleArea(parent: HTMLElement): HTMLElement | undefined {
|
|
const titleArea = super.createTitleArea(parent);
|
|
if (!titleArea) {
|
|
return undefined;
|
|
}
|
|
|
|
this._register(addDisposableListener(titleArea, EventType.CONTEXT_MENU, e => {
|
|
this.onTitleAreaContextMenu(new StandardMouseEvent(getWindow(titleArea), e));
|
|
}));
|
|
this._register(Gesture.addTarget(titleArea));
|
|
this._register(addDisposableListener(titleArea, GestureEventType.Contextmenu, e => {
|
|
this.onTitleAreaContextMenu(new StandardMouseEvent(getWindow(titleArea), e));
|
|
}));
|
|
|
|
// Global Left Actions Toolbar (optional, subclasses provide a menu ID)
|
|
const globalLeftActionsMenuId = this.getGlobalLeftActionsMenuId();
|
|
if (globalLeftActionsMenuId) {
|
|
const globalLeftTitleActionsContainer = titleArea.appendChild($('.global-actions-left'));
|
|
this.globalLeftToolBar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar,
|
|
globalLeftTitleActionsContainer,
|
|
globalLeftActionsMenuId,
|
|
{
|
|
actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options),
|
|
orientation: ActionsOrientation.HORIZONTAL,
|
|
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
|
anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(),
|
|
hoverDelegate: this.toolbarHoverDelegate,
|
|
hiddenItemStrategy: HiddenItemStrategy.NoHide,
|
|
highlightToggledItems: false,
|
|
telemetrySource: this.nameForTelemetry
|
|
}
|
|
));
|
|
}
|
|
|
|
const globalTitleActionsContainer = titleArea.appendChild($('.global-actions'));
|
|
|
|
// Global Actions Toolbar
|
|
this.globalToolBar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar,
|
|
globalTitleActionsContainer,
|
|
this.globalActionsMenuId,
|
|
{
|
|
actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options),
|
|
orientation: ActionsOrientation.HORIZONTAL,
|
|
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
|
anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(),
|
|
toggleMenuTitle: localize('moreActions', "More Actions..."),
|
|
hoverDelegate: this.toolbarHoverDelegate,
|
|
hiddenItemStrategy: HiddenItemStrategy.NoHide,
|
|
highlightToggledItems: true,
|
|
telemetrySource: this.nameForTelemetry
|
|
}
|
|
));
|
|
|
|
return titleArea;
|
|
}
|
|
|
|
protected override createTitleLabel(parent: HTMLElement): ICompositeTitleLabel {
|
|
this.titleContainer = parent;
|
|
|
|
const titleLabel = super.createTitleLabel(parent);
|
|
this.titleLabelElement!.draggable = this.viewDescriptorService.canMoveViews();
|
|
const draggedItemProvider = (): { type: 'view' | 'composite'; id: string } => {
|
|
const activeViewlet = this.getActivePaneComposite()!;
|
|
return { type: 'composite', id: activeViewlet.getId() };
|
|
};
|
|
this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.titleLabelElement!, draggedItemProvider, {}));
|
|
|
|
return titleLabel;
|
|
}
|
|
|
|
protected updateCompositeBar(updateCompositeBarOption: boolean = false): void {
|
|
const wasCompositeBarVisible = this.compositeBarPosition !== undefined;
|
|
const isCompositeBarVisible = this.shouldShowCompositeBar();
|
|
const previousPosition = this.compositeBarPosition;
|
|
const newPosition = isCompositeBarVisible ? this.getCompositeBarPosition() : undefined;
|
|
|
|
// Only update if the visibility or position has changed or if the composite bar options should be updated
|
|
if (!updateCompositeBarOption && previousPosition === newPosition) {
|
|
return;
|
|
}
|
|
|
|
// Remove old composite bar
|
|
if (wasCompositeBarVisible) {
|
|
const previousCompositeBarContainer = previousPosition === CompositeBarPosition.TITLE ? this.titleContainer : this.headerFooterCompositeBarContainer;
|
|
if (!this.paneCompositeBarContainer || !this.paneCompositeBar.value || !previousCompositeBarContainer) {
|
|
throw new Error('Composite bar containers should exist when removing the previous composite bar');
|
|
}
|
|
|
|
this.paneCompositeBarContainer.remove();
|
|
this.paneCompositeBarContainer = undefined;
|
|
this.paneCompositeBar.value = undefined;
|
|
|
|
previousCompositeBarContainer.classList.remove('has-composite-bar');
|
|
|
|
if (previousPosition === CompositeBarPosition.TOP) {
|
|
this.removeFooterHeaderArea(true);
|
|
} else if (previousPosition === CompositeBarPosition.BOTTOM) {
|
|
this.removeFooterHeaderArea(false);
|
|
}
|
|
}
|
|
|
|
// Create new composite bar
|
|
let newCompositeBarContainer;
|
|
switch (newPosition) {
|
|
case CompositeBarPosition.TOP: newCompositeBarContainer = this.createHeaderArea(); break;
|
|
case CompositeBarPosition.TITLE: newCompositeBarContainer = this.titleContainer; break;
|
|
case CompositeBarPosition.BOTTOM: newCompositeBarContainer = this.createFooterArea(); break;
|
|
}
|
|
if (isCompositeBarVisible) {
|
|
|
|
if (this.paneCompositeBarContainer || this.paneCompositeBar.value || !newCompositeBarContainer) {
|
|
throw new Error('Invalid composite bar state when creating the new composite bar');
|
|
}
|
|
|
|
newCompositeBarContainer.classList.add('has-composite-bar');
|
|
this.paneCompositeBarContainer = prepend(newCompositeBarContainer, $('.composite-bar-container'));
|
|
this.paneCompositeBar.value = this.createCompositeBar();
|
|
this.paneCompositeBar.value.create(this.paneCompositeBarContainer);
|
|
|
|
if (newPosition === CompositeBarPosition.TOP) {
|
|
this.setHeaderArea(newCompositeBarContainer);
|
|
} else if (newPosition === CompositeBarPosition.BOTTOM) {
|
|
this.setFooterArea(newCompositeBarContainer);
|
|
}
|
|
}
|
|
|
|
this.compositeBarPosition = newPosition;
|
|
|
|
if (updateCompositeBarOption) {
|
|
this.layoutCompositeBar();
|
|
}
|
|
}
|
|
|
|
protected override createHeaderArea(): HTMLElement {
|
|
const headerArea = super.createHeaderArea();
|
|
|
|
return this.createHeaderFooterCompositeBarArea(headerArea);
|
|
}
|
|
|
|
protected override createFooterArea(): HTMLElement {
|
|
const footerArea = super.createFooterArea();
|
|
|
|
return this.createHeaderFooterCompositeBarArea(footerArea);
|
|
}
|
|
|
|
protected createHeaderFooterCompositeBarArea(area: HTMLElement): HTMLElement {
|
|
if (this.headerFooterCompositeBarContainer) {
|
|
// A pane composite part has either a header or a footer, but not both
|
|
throw new Error('Header or Footer composite bar already exists');
|
|
}
|
|
this.headerFooterCompositeBarContainer = area;
|
|
|
|
this.headerFooterCompositeBarDispoables.add(addDisposableListener(area, EventType.CONTEXT_MENU, e => {
|
|
this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(area), e));
|
|
}));
|
|
this.headerFooterCompositeBarDispoables.add(Gesture.addTarget(area));
|
|
this.headerFooterCompositeBarDispoables.add(addDisposableListener(area, GestureEventType.Contextmenu, e => {
|
|
this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(area), e));
|
|
}));
|
|
|
|
return area;
|
|
}
|
|
|
|
private removeFooterHeaderArea(header: boolean): void {
|
|
this.headerFooterCompositeBarContainer = undefined;
|
|
this.headerFooterCompositeBarDispoables.clear();
|
|
if (header) {
|
|
this.removeHeaderArea();
|
|
} else {
|
|
this.removeFooterArea();
|
|
}
|
|
}
|
|
|
|
protected createCompositeBar(): PaneCompositeBar {
|
|
return this.instantiationService.createInstance(PaneCompositeBar, this.location, this.getCompositeBarOptions(), this.partId, this);
|
|
}
|
|
|
|
protected override onTitleAreaUpdate(compositeId: string): void {
|
|
super.onTitleAreaUpdate(compositeId);
|
|
|
|
// If title actions change, relayout the composite bar
|
|
this.layoutCompositeBar();
|
|
}
|
|
|
|
async openPaneComposite(id?: string, focus?: boolean): Promise<PaneComposite | undefined> {
|
|
if (typeof id === 'string' && this.getPaneComposite(id)) {
|
|
return this.doOpenPaneComposite(id, focus);
|
|
}
|
|
|
|
await this.extensionService.whenInstalledExtensionsRegistered();
|
|
|
|
if (typeof id === 'string' && this.getPaneComposite(id)) {
|
|
return this.doOpenPaneComposite(id, focus);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
private async doOpenPaneComposite(id: string, focus?: boolean): Promise<PaneComposite | undefined> {
|
|
if (this.blockOpening) {
|
|
// Workaround against a potential race condition when calling
|
|
// `setPartHidden` we may end up in `openPaneComposite` again.
|
|
// But we still want to return the result of the original call,
|
|
// so we return the promise of the original call.
|
|
return this.blockOpening.p;
|
|
}
|
|
|
|
let blockOpening: DeferredPromise<PaneComposite | undefined> | undefined;
|
|
if (!this.layoutService.isVisible(this.partId)) {
|
|
try {
|
|
blockOpening = this.blockOpening = new DeferredPromise<PaneComposite | undefined>();
|
|
this.layoutService.setPartHidden(false, this.partId);
|
|
} finally {
|
|
this.blockOpening = undefined;
|
|
}
|
|
}
|
|
|
|
try {
|
|
const result = this.openComposite(id, focus) as PaneComposite | undefined;
|
|
blockOpening?.complete(result);
|
|
|
|
return result;
|
|
} catch (error) {
|
|
blockOpening?.error(error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
getPaneComposite(id: string): PaneCompositeDescriptor | undefined {
|
|
return (this.registry as PaneCompositeRegistry).getPaneComposite(id);
|
|
}
|
|
|
|
getPaneComposites(): PaneCompositeDescriptor[] {
|
|
return (this.registry as PaneCompositeRegistry).getPaneComposites()
|
|
.sort((v1, v2) => {
|
|
if (typeof v1.order !== 'number') {
|
|
return 1;
|
|
}
|
|
|
|
if (typeof v2.order !== 'number') {
|
|
return -1;
|
|
}
|
|
|
|
return v1.order - v2.order;
|
|
});
|
|
}
|
|
|
|
getPinnedPaneCompositeIds(): string[] {
|
|
return this.paneCompositeBar.value?.getPinnedPaneCompositeIds() ?? [];
|
|
}
|
|
|
|
getVisiblePaneCompositeIds(): string[] {
|
|
return this.paneCompositeBar.value?.getVisiblePaneCompositeIds() ?? [];
|
|
}
|
|
|
|
getPaneCompositeIds(): string[] {
|
|
return this.paneCompositeBar.value?.getPaneCompositeIds() ?? [];
|
|
}
|
|
|
|
getActivePaneComposite(): IPaneComposite | undefined {
|
|
return <IPaneComposite>this.getActiveComposite();
|
|
}
|
|
|
|
getLastActivePaneCompositeId(): string {
|
|
return this.getLastActiveCompositeId();
|
|
}
|
|
|
|
hideActivePaneComposite(): void {
|
|
if (this.layoutService.isVisible(this.partId)) {
|
|
this.layoutService.setPartHidden(true, this.partId);
|
|
}
|
|
|
|
this.hideActiveComposite();
|
|
}
|
|
|
|
protected focusCompositeBar(): void {
|
|
this.paneCompositeBar.value?.focus();
|
|
}
|
|
|
|
override layout(width: number, height: number, top: number, left: number): void {
|
|
if (!this.layoutService.isVisible(this.partId)) {
|
|
return;
|
|
}
|
|
|
|
this.contentDimension = new Dimension(width, height);
|
|
|
|
// Layout contents
|
|
super.layout(this.contentDimension.width, this.contentDimension.height, top, left);
|
|
|
|
// Layout composite bar
|
|
this.layoutCompositeBar();
|
|
|
|
// Add empty pane message
|
|
this.layoutEmptyMessage();
|
|
}
|
|
|
|
private layoutCompositeBar(): void {
|
|
if (this.contentDimension && this.dimension && this.paneCompositeBar.value) {
|
|
const padding = this.compositeBarPosition === CompositeBarPosition.TITLE ? 16 : 8;
|
|
const borderWidth = this.partId === Parts.PANEL_PART ? 0 : 1;
|
|
let availableWidth = this.contentDimension.width - padding - borderWidth;
|
|
availableWidth = Math.max(AbstractPaneCompositePart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.getToolbarWidth());
|
|
this.paneCompositeBar.value.layout(availableWidth, this.dimension.height);
|
|
}
|
|
}
|
|
|
|
private layoutEmptyMessage(): void {
|
|
const visible = !this.getActiveComposite();
|
|
this.element.classList.toggle('empty', visible);
|
|
if (visible) {
|
|
this.titleLabel?.updateTitle('', '');
|
|
}
|
|
}
|
|
|
|
protected getToolbarWidth(): number {
|
|
if (!this.toolBar || this.compositeBarPosition !== CompositeBarPosition.TITLE) {
|
|
return 0;
|
|
}
|
|
|
|
const activePane = this.getActivePaneComposite();
|
|
if (!activePane) {
|
|
return 0;
|
|
}
|
|
|
|
// Each toolbar item has 4px margin
|
|
const toolBarWidth = this.toolBar.getItemsWidth() + this.toolBar.getItemsLength() * 4;
|
|
const globalToolBarWidth = this.globalToolBar ? this.globalToolBar.getItemsWidth() + this.globalToolBar.getItemsLength() * 4 : 0;
|
|
const globalLeftToolBarWidth = this.globalLeftToolBar ? this.globalLeftToolBar.getItemsWidth() + this.globalLeftToolBar.getItemsLength() * 4 : 0;
|
|
return toolBarWidth + globalToolBarWidth + globalLeftToolBarWidth + 8; // 8px padding left
|
|
}
|
|
|
|
private onTitleAreaContextMenu(event: StandardMouseEvent): void {
|
|
if (this.shouldShowCompositeBar() && this.getCompositeBarPosition() === CompositeBarPosition.TITLE) {
|
|
return this.onCompositeBarContextMenu(event);
|
|
} else {
|
|
const activePaneComposite = this.getActivePaneComposite() as PaneComposite;
|
|
const activePaneCompositeActions = activePaneComposite ? activePaneComposite.getContextMenuActions() : [];
|
|
if (activePaneCompositeActions.length) {
|
|
this.contextMenuService.showContextMenu({
|
|
getAnchor: () => event,
|
|
getActions: () => activePaneCompositeActions,
|
|
getActionViewItem: (action, options) => this.actionViewItemProvider(action, options),
|
|
actionRunner: activePaneComposite.getActionRunner(),
|
|
skipTelemetry: true
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private onCompositeBarAreaContextMenu(event: StandardMouseEvent): void {
|
|
return this.onCompositeBarContextMenu(event);
|
|
}
|
|
|
|
private onCompositeBarContextMenu(event: StandardMouseEvent): void {
|
|
if (this.paneCompositeBar.value) {
|
|
const actions: IAction[] = [...this.paneCompositeBar.value.getContextMenuActions()];
|
|
if (actions.length) {
|
|
this.contextMenuService.showContextMenu({
|
|
getAnchor: () => event,
|
|
getActions: () => actions,
|
|
skipTelemetry: true
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
protected getViewsSubmenuAction(): SubmenuAction | undefined {
|
|
const viewPaneContainer = (this.getActivePaneComposite() as PaneComposite)?.getViewPaneContainer();
|
|
if (viewPaneContainer) {
|
|
const disposables = new DisposableStore();
|
|
const scopedContextKeyService = disposables.add(this.contextKeyService.createScoped(this.element));
|
|
scopedContextKeyService.createKey('viewContainer', viewPaneContainer.viewContainer.id);
|
|
const menu = this.menuService.getMenuActions(ViewsSubMenu, scopedContextKeyService, { shouldForwardArgs: true, renderShortTitle: true });
|
|
const viewsActions = getActionBarActions(menu, () => true).primary;
|
|
disposables.dispose();
|
|
return viewsActions.length > 1 && viewsActions.some(a => a.enabled) ? new SubmenuAction('views', localize('views', "Views"), viewsActions) : undefined;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Override in subclasses to provide a menu ID for a global toolbar on the left side
|
|
* of the composite bar / title area. Returns `undefined` by default (no left toolbar).
|
|
*/
|
|
protected getGlobalLeftActionsMenuId(): MenuId | undefined {
|
|
return undefined;
|
|
}
|
|
|
|
protected abstract shouldShowCompositeBar(): boolean;
|
|
protected abstract getCompositeBarOptions(): IPaneCompositeBarOptions;
|
|
protected abstract getCompositeBarPosition(): CompositeBarPosition;
|
|
}
|