UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

1,465 lines 62.4 kB
"use strict";
// *****************************************************************************
// Copyright (C) 2018-2019 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
var ViewContainer_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ViewContainerLayout = exports.ViewContainerPart = exports.ViewContainer = exports.DynamicToolbarWidget = exports.BadgeWidget = exports.DescriptionWidget = exports.ViewContainerIdentifier = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("inversify");
const algorithm_1 = require("@lumino/algorithm");
const widgets_1 = require("./widgets");
const event_1 = require("../common/event");
const disposable_1 = require("../common/disposable");
const command_1 = require("../common/command");
const menu_1 = require("../common/menu");
const shell_1 = require("./shell");
const theia_dock_panel_1 = require("./shell/theia-dock-panel");
const frontend_application_state_1 = require("./frontend-application-state");
const context_menu_renderer_1 = require("./context-menu-renderer");
const browser_1 = require("./browser");
const tab_bar_toolbar_1 = require("./shell/tab-bar-toolbar");
const common_1 = require("../common");
const widget_manager_1 = require("./widget-manager");
const keys_1 = require("./keys");
const progress_bar_factory_1 = require("./progress-bar-factory");
const dragdrop_1 = require("@lumino/dragdrop");
const coreutils_1 = require("@lumino/coreutils");
const domutils_1 = require("@lumino/domutils");
const tab_bar_decorator_1 = require("./shell/tab-bar-decorator");
const context_key_service_1 = require("./context-key-service");
const keybinding_1 = require("./keybinding");
const tab_bar_toolbar_menu_adapters_1 = require("./shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters");
const theia_split_panel_1 = require("./shell/theia-split-panel");
let ViewContainerIdentifier = class ViewContainerIdentifier {
};
exports.ViewContainerIdentifier = ViewContainerIdentifier;
exports.ViewContainerIdentifier = ViewContainerIdentifier = tslib_1.__decorate([
    (0, inversify_1.injectable)()
], ViewContainerIdentifier);
var DescriptionWidget;
(function (DescriptionWidget) {
    function is(arg) {
        return (0, common_1.isObject)(arg) && 'onDidChangeDescription' in arg;
    }
    DescriptionWidget.is = is;
})(DescriptionWidget || (exports.DescriptionWidget = DescriptionWidget = {}));
var BadgeWidget;
(function (BadgeWidget) {
    function is(arg) {
        return (0, common_1.isObject)(arg) && 'onDidChangeBadge' in arg && 'onDidChangeBadgeTooltip' in arg;
    }
    BadgeWidget.is = is;
})(BadgeWidget || (exports.BadgeWidget = BadgeWidget = {}));
var DynamicToolbarWidget;
(function (DynamicToolbarWidget) {
    function is(arg) {
        return (0, common_1.isObject)(arg) && 'onDidChangeToolbarItems' in arg;
    }
    DynamicToolbarWidget.is = is;
})(DynamicToolbarWidget || (exports.DynamicToolbarWidget = DynamicToolbarWidget = {}));
class PartsMenuToolbarItem extends tab_bar_toolbar_menu_adapters_1.SubmenuAsToolbarItemWrapper {
    constructor(target, effectiveMenuPath, commandRegistry, menuRegistry, contextKeyService, contextMenuRenderer, menuNode, group, menuPath) {
        super(effectiveMenuPath, commandRegistry, menuRegistry, contextKeyService, contextMenuRenderer, menuNode, group);
        this.target = target;
    }
    isVisible(widget) {
        return widget === this.target() && super.isVisible(widget);
    }
}
/**
 * A view container holds an arbitrary number of widgets inside a split panel.
 * Each widget is wrapped in a _part_ that displays the widget title and toolbar
 * and allows to collapse / expand the widget content.
 */
let ViewContainer = ViewContainer_1 = class ViewContainer extends widgets_1.BaseWidget {
    constructor() {
        super(...arguments);
        /**
         * Disable dragging parts from/to this view container.
         */
        this.disableDNDBetweenContainers = false;
        this.onDidChangeTrackableWidgetsEmitter = new event_1.Emitter();
        this.onDidChangeTrackableWidgets = this.onDidChangeTrackableWidgetsEmitter.event;
        this.toDisposeOnUpdateTitle = new disposable_1.DisposableCollection();
        this._tabBarDelegate = this;
        this.toRemoveWidgets = new Map();
        this.toDisposeOnDragEnd = new disposable_1.DisposableCollection();
    }
    init() {
        this.toDispose.push(disposable_1.Disposable.create(() => {
            this.toDisposeOnUpdateTitle.dispose();
            this.toDisposeOnDragEnd.dispose();
        }));
        this.id = this.options.id;
        this.addClass('theia-view-container');
        const layout = new widgets_1.PanelLayout();
        this.layout = layout;
        this.panel = new theia_split_panel_1.TheiaSplitPanel({
            layout: new ViewContainerLayout({
                renderer: widgets_1.SplitPanel.defaultRenderer,
                orientation: this.orientation,
                spacing: 2,
                headerSize: ViewContainerPart.HEADER_HEIGHT,
                animationDuration: 200
            }, this.splitPositionHandler)
        });
        this.panel.node.tabIndex = -1;
        this.configureLayout(layout);
        const { commandRegistry, menuRegistry, contextMenuRenderer } = this;
        this.toDispose.pushAll([
            (0, widgets_1.addEventListener)(this.node, 'contextmenu', event => {
                if (event.button === 2 && (0, algorithm_1.every)(this.containerLayout.iter(), part => !!part.isHidden)) {
                    event.stopPropagation();
                    event.preventDefault();
                    contextMenuRenderer.render({
                        menuPath: this.contextMenuPath,
                        anchor: event,
                        context: event.currentTarget instanceof HTMLElement ? event.currentTarget : this.node
                    });
                }
            }),
            commandRegistry.registerCommand({ id: this.globalHideCommandId }, {
                execute: (anchor) => {
                    const toHide = this.findPartForAnchor(anchor);
                    if (toHide && toHide.canHide) {
                        toHide.hide();
                    }
                },
                isVisible: (anchor) => {
                    const toHide = this.findPartForAnchor(anchor);
                    if (toHide) {
                        return toHide.canHide && !toHide.isHidden;
                    }
                    else {
                        return (0, algorithm_1.some)(this.containerLayout.iter(), part => !part.isHidden);
                    }
                }
            }),
            menuRegistry.registerMenuAction([...this.contextMenuPath, '0_global'], {
                commandId: this.globalHideCommandId,
                label: common_1.nls.localizeByDefault('Hide')
            }),
            this.onDidChangeTrackableWidgetsEmitter,
            this.onDidChangeTrackableWidgets(() => this.decoratorService.fireDidChangeDecorations())
        ]);
        if (this.options.progressLocationId) {
            this.toDispose.push(this.progressBarFactory({ container: this.node, insertMode: 'prepend', locationId: this.options.progressLocationId }));
        }
    }
    configureLayout(layout) {
        layout.addWidget(this.panel);
    }
    updateCurrentPart(part) {
        if (part && this.getParts().indexOf(part) !== -1) {
            this.currentPart = part;
        }
        if (this.currentPart && !this.currentPart.isDisposed) {
            return;
        }
        const visibleParts = this.getParts().filter(p => !p.isHidden);
        const expandedParts = visibleParts.filter(p => !p.collapsed);
        this.currentPart = expandedParts[0] || visibleParts[0];
    }
    updateSplitterVisibility() {
        const className = 'lm-first-visible';
        let firstFound = false;
        for (const part of this.getParts()) {
            if (!part.isHidden && !firstFound) {
                part.addClass(className);
                firstFound = true;
            }
            else {
                part.removeClass(className);
            }
        }
    }
    setTitleOptions(titleOptions) {
        this.titleOptions = titleOptions;
        this.updateTitle();
    }
    updateTabBarDelegate() {
        const visibleParts = this.getParts().filter(part => !part.isHidden);
        if (visibleParts.length === 1) {
            this._tabBarDelegate = visibleParts[0].wrapped;
        }
        else {
            this._tabBarDelegate = this;
        }
    }
    getTabBarDelegate() {
        return this._tabBarDelegate;
    }
    updateTitle() {
        this.toDisposeOnUpdateTitle.dispose();
        this.toDisposeOnUpdateTitle = new disposable_1.DisposableCollection();
        this.updateTabBarDelegate();
        let title = Object.assign({}, this.titleOptions);
        if ((0, common_1.isEmpty)(title)) {
            return;
        }
        const allParts = this.getParts();
        const visibleParts = allParts.filter(part => !part.isHidden);
        this.title.label = title.label;
        // If there's only one visible part - inline it's title into the container title except in case the part
        // isn't originally belongs to this container but there are other **original** hidden parts.
        if (visibleParts.length === 1 && (visibleParts[0].originalContainerId === this.id || !this.findOriginalPart())) {
            const part = visibleParts[0];
            this.toDisposeOnUpdateTitle.push(part.onTitleChanged(() => this.updateTitle()));
            const partLabel = part.wrapped.title.label;
            // Change the container title if it contains only one part that originally belongs to another container.
            if (allParts.length === 1 && part.originalContainerId !== this.id && !this.isCurrentTitle(part.originalContainerTitle)) {
                title = Object.assign({}, part.originalContainerTitle);
                this.setTitleOptions(title);
                return;
            }
            if (partLabel) {
                if (this.title.label && this.title.label !== partLabel) {
                    this.title.label += ': ' + partLabel;
                }
                else {
                    this.title.label = partLabel;
                }
            }
            part.collapsed = false;
            part.hideTitle();
        }
        else {
            visibleParts.forEach(part => part.showTitle());
            // If at least one part originally belongs to this container the title should return to its original value.
            const originalPart = this.findOriginalPart();
            if (originalPart && !this.isCurrentTitle(originalPart.originalContainerTitle)) {
                title = Object.assign({}, originalPart.originalContainerTitle);
                this.setTitleOptions(title);
                return;
            }
        }
        this.updateToolbarItems(allParts);
        this.title.caption = title?.caption || title?.label;
        if (title.iconClass) {
            this.title.iconClass = title.iconClass;
        }
        if (this.title.className.includes(widgets_1.PINNED_CLASS)) {
            this.title.closable &&= false;
        }
        else if (title.closeable !== undefined) {
            this.title.closable = title.closeable;
        }
    }
    updateToolbarItems(allParts) {
        if (allParts.length > 1) {
            const group = new menu_1.SubmenuImpl(`toggleParts-${this.id}`, this.getToggleVisibilityGroupLabel(), undefined, '000');
            for (const part of allParts) {
                const existingId = this.toggleVisibilityCommandId(part);
                const { label } = part.wrapped.title;
                group.addNode(new menu_1.ActionMenuNode({
                    commandId: existingId,
                    label: label
                }, this.commandRegistry, this.keybindingRegistry, this.contextKeyService));
            }
            // widget === this.getTabBarDelegate()
            const toolbarItem = new PartsMenuToolbarItem(() => this.getTabBarDelegate(), [this.id], this.commandRegistry, this.menuRegistry, this.contextKeyService, this.contextMenuRenderer, group, '000_views', [this.id]);
            this.toDisposeOnUpdateTitle.push(this.toolbarRegistry.doRegisterItem(toolbarItem));
        }
    }
    getToggleVisibilityGroupLabel() {
        return 'Views';
    }
    findOriginalPart() {
        return this.getParts().find(part => part.originalContainerId === this.id);
    }
    isCurrentTitle(titleOptions) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return (!!titleOptions && !!this.titleOptions && Object.keys(titleOptions).every(key => titleOptions[key] === this.titleOptions[key]))
            || (!titleOptions && !this.titleOptions);
    }
    findPartForAnchor(anchor) {
        const element = document.elementFromPoint(anchor.x, anchor.y);
        if (element instanceof Element) {
            const closestPart = ViewContainerPart.closestPart(element);
            if (closestPart && closestPart.id) {
                return (0, algorithm_1.find)(this.containerLayout.iter(), part => part.id === closestPart.id);
            }
        }
        return undefined;
    }
    createPartId(widget) {
        const description = this.widgetManager.getDescription(widget);
        return widget.id || JSON.stringify(description);
    }
    addWidget(widget, options, originalContainerId, originalContainerTitle) {
        const existing = this.toRemoveWidgets.get(widget.id);
        if (existing) {
            return existing;
        }
        const partId = this.createPartId(widget);
        const newPart = this.createPart(widget, partId, originalContainerId || this.id, originalContainerTitle || this.titleOptions, options);
        return this.attachNewPart(newPart);
    }
    attachNewPart(newPart, insertIndex) {
        const toRemoveWidget = new disposable_1.DisposableCollection();
        this.toDispose.push(toRemoveWidget);
        this.toRemoveWidgets.set(newPart.wrapped.id, toRemoveWidget);
        toRemoveWidget.push(disposable_1.Disposable.create(() => this.toRemoveWidgets.delete(newPart.wrapped.id)));
        this.registerPart(newPart);
        if (insertIndex !== undefined || (newPart.options && newPart.options.order !== undefined)) {
            const index = insertIndex ?? this.getParts().findIndex(part => part.options.order === undefined || part.options.order > newPart.options.order);
            if (index >= 0) {
                this.containerLayout.insertWidget(index, newPart);
            }
            else {
                this.containerLayout.addWidget(newPart);
            }
        }
        else {
            this.containerLayout.addWidget(newPart);
        }
        this.refreshMenu(newPart);
        this.updateTitle();
        this.updateCurrentPart();
        this.updateSplitterVisibility();
        this.update();
        this.fireDidChangeTrackableWidgets();
        toRemoveWidget.pushAll([
            disposable_1.Disposable.create(() => {
                if (newPart.currentViewContainerId === this.id) {
                    newPart.dispose();
                }
                this.unregisterPart(newPart);
                if (!newPart.isDisposed && this.getPartIndex(newPart.id) > -1) {
                    this.containerLayout.removeWidget(newPart);
                }
                if (!this.isDisposed) {
                    this.update();
                    this.updateTitle();
                    this.updateCurrentPart();
                    this.updateSplitterVisibility();
                    this.fireDidChangeTrackableWidgets();
                }
            }),
            this.registerDND(newPart),
            newPart.onDidChangeVisibility(() => {
                this.updateTitle();
                this.updateCurrentPart();
                this.updateSplitterVisibility();
                this.containerLayout.updateSashes();
            }),
            newPart.onCollapsed(() => {
                this.containerLayout.updateCollapsed(newPart, this.enableAnimation);
                this.containerLayout.updateSashes();
                this.updateCurrentPart();
            }),
            newPart.onContextMenu(event => {
                if (event.button === 2) {
                    event.preventDefault();
                    event.stopPropagation();
                    this.contextMenuRenderer.render({ menuPath: this.contextMenuPath, anchor: event, context: this.node });
                }
            }),
            newPart.onTitleChanged(() => this.refreshMenu(newPart)),
            newPart.onDidFocus(() => this.updateCurrentPart(newPart))
        ]);
        newPart.disposed.connect(() => toRemoveWidget.dispose());
        return toRemoveWidget;
    }
    createPart(widget, partId, originalContainerId, originalContainerTitle, options) {
        return new ViewContainerPart(widget, partId, this.id, originalContainerId, originalContainerTitle, this.toolbarRegistry, this.toolbarFactory, options);
    }
    removeWidget(widget) {
        const disposable = this.toRemoveWidgets.get(widget.id);
        if (disposable) {
            disposable.dispose();
            return true;
        }
        return false;
    }
    getParts() {
        return this.containerLayout.widgets;
    }
    getPartIndex(partId) {
        if (partId) {
            return this.getParts().findIndex(part => part.id === partId);
        }
        return -1;
    }
    getPartFor(widget) {
        return this.getParts().find(p => p.wrapped.id === widget.id);
    }
    get containerLayout() {
        const layout = this.panel.layout;
        if (layout instanceof ViewContainerLayout) {
            return layout;
        }
        throw new Error('view container is disposed');
    }
    get orientation() {
        return ViewContainer_1.getOrientation(this.node);
    }
    get enableAnimation() {
        return this.applicationStateService.state === 'ready';
    }
    storeState() {
        if (!this.isVisible && this.lastVisibleState) {
            return this.lastVisibleState;
        }
        return this.doStoreState();
    }
    doStoreState() {
        const parts = this.getParts();
        const availableSize = this.containerLayout.getAvailableSize();
        const orientation = this.orientation;
        const partStates = parts.map(part => {
            let size = this.containerLayout.getPartSize(part);
            if (size && size > ViewContainerPart.HEADER_HEIGHT && orientation === 'vertical') {
                size -= ViewContainerPart.HEADER_HEIGHT;
            }
            return {
                widget: part.wrapped,
                partId: part.partId,
                collapsed: part.collapsed,
                hidden: part.isHidden,
                relativeSize: size && availableSize ? size / availableSize : undefined,
                originalContainerId: part.originalContainerId,
                originalContainerTitle: part.originalContainerTitle
            };
        });
        return { parts: partStates, title: this.titleOptions };
    }
    restoreState(state) {
        this.lastVisibleState = state;
        this.doRestoreState(state);
    }
    doRestoreState(state) {
        this.setTitleOptions(state.title);
        // restore widgets
        for (const part of state.parts) {
            if (part.widget) {
                this.addWidget(part.widget, undefined, part.originalContainerId, part.originalContainerTitle || {});
            }
        }
        const partStates = state.parts.filter(partState => (0, algorithm_1.some)(this.containerLayout.iter(), p => p.partId === partState.partId));
        // Reorder the parts according to the stored state
        for (let index = 0; index < partStates.length; index++) {
            const partState = partStates[index];
            const widget = this.getParts().find(part => part.partId === partState.partId);
            if (widget) {
                this.containerLayout.insertWidget(index, widget);
            }
        }
        // Restore visibility and collapsed state
        const parts = this.getParts();
        for (let index = 0; index < parts.length; index++) {
            const part = parts[index];
            const partState = partStates.find(s => part.partId === s.partId);
            if (partState) {
                part.setHidden(partState.hidden);
                part.collapsed = partState.collapsed || !partState.relativeSize;
            }
            else if (part.canHide) {
                part.hide();
            }
            this.refreshMenu(part);
        }
        // Restore part sizes
        (0, widgets_1.waitForRevealed)(this).then(() => {
            this.containerLayout.setPartSizes(partStates.map(partState => partState.relativeSize));
            this.updateSplitterVisibility();
        });
    }
    /**
     * Register a command to toggle the visibility of the new part.
     */
    registerPart(toRegister) {
        const commandId = this.toggleVisibilityCommandId(toRegister);
        this.commandRegistry.registerCommand({ id: commandId }, {
            execute: () => {
                const toHide = (0, algorithm_1.find)(this.containerLayout.iter(), part => part.id === toRegister.id);
                if (toHide) {
                    toHide.setHidden(!toHide.isHidden);
                }
            },
            isToggled: () => {
                if (!toRegister.canHide) {
                    return true;
                }
                const widgetToToggle = (0, algorithm_1.find)(this.containerLayout.iter(), part => part.id === toRegister.id);
                if (widgetToToggle) {
                    return !widgetToToggle.isHidden;
                }
                return false;
            },
            isEnabled: arg => toRegister.canHide && (!this.titleOptions || !(arg instanceof widgets_1.Widget) || arg === this.getTabBarDelegate()),
            isVisible: arg => !this.titleOptions || !(arg instanceof widgets_1.Widget) || arg === this.getTabBarDelegate()
        });
    }
    /**
     * Register a menu action to toggle the visibility of the new part.
     * The menu action is unregistered first to enable refreshing the order of menu actions.
     */
    refreshMenu(part) {
        const commandId = this.toggleVisibilityCommandId(part);
        this.menuRegistry.unregisterMenuAction(commandId);
        if (!part.wrapped.title.label) {
            return;
        }
        const { dataset: { visibilityCommandLabel }, caption, label } = part.wrapped.title;
        const action = {
            commandId: commandId,
            label: visibilityCommandLabel || caption || label,
            order: this.getParts().indexOf(part).toString()
        };
        this.menuRegistry.registerMenuAction([...this.contextMenuPath, '1_widgets'], action);
        if (this.titleOptions) {
            this.menuRegistry.registerMenuAction([...shell_1.SIDE_PANEL_TOOLBAR_CONTEXT_MENU, 'navigation'], action);
        }
    }
    unregisterPart(part) {
        const commandId = this.toggleVisibilityCommandId(part);
        this.commandRegistry.unregisterCommand(commandId);
        this.menuRegistry.unregisterMenuAction(commandId);
    }
    get contextMenuPath() {
        return [`${this.id}-context-menu`];
    }
    toggleVisibilityCommandId(part) {
        return `${this.id}:toggle-visibility-${part.id}`;
    }
    get globalHideCommandId() {
        return `${this.id}:toggle-visibility`;
    }
    moveBefore(toMovedId, moveBeforeThisId) {
        const parts = this.getParts();
        const indexToMove = parts.findIndex(part => part.id === toMovedId);
        const targetIndex = parts.findIndex(part => part.id === moveBeforeThisId);
        if (indexToMove >= 0 && targetIndex >= 0) {
            this.containerLayout.insertWidget(targetIndex, parts[indexToMove]);
            for (let index = Math.min(indexToMove, targetIndex); index < parts.length; index++) {
                this.refreshMenu(parts[index]);
                this.activate();
            }
        }
        this.updateSplitterVisibility();
    }
    getTrackableWidgets() {
        return this.getParts().map(w => w.wrapped);
    }
    fireDidChangeTrackableWidgets() {
        this.onDidChangeTrackableWidgetsEmitter.fire(this.getTrackableWidgets());
    }
    activateWidget(id) {
        const part = this.revealPart(id);
        if (!part) {
            return undefined;
        }
        this.updateCurrentPart(part);
        part.collapsed = false;
        return part.wrapped;
    }
    revealWidget(id) {
        const part = this.revealPart(id);
        return part && part.wrapped;
    }
    revealPart(id) {
        const part = this.getParts().find(p => p.wrapped.id === id);
        if (!part) {
            return undefined;
        }
        part.setHidden(false);
        return part;
    }
    onActivateRequest(msg) {
        super.onActivateRequest(msg);
        if (this.currentPart) {
            this.currentPart.activate();
        }
        else {
            this.panel.node.focus({ preventScroll: true });
        }
    }
    onAfterAttach(msg) {
        const orientation = this.orientation;
        this.containerLayout.orientation = orientation;
        if (orientation === 'horizontal') {
            for (const part of this.getParts()) {
                part.collapsed = false;
            }
        }
        super.onAfterAttach(msg);
    }
    onBeforeHide(msg) {
        super.onBeforeHide(msg);
        this.lastVisibleState = this.storeState();
    }
    onAfterShow(msg) {
        super.onAfterShow(msg);
        this.updateTitle();
        this.lastVisibleState = undefined;
    }
    onBeforeAttach(msg) {
        super.onBeforeAttach(msg);
        this.node.addEventListener('lm-dragenter', this, true);
        this.node.addEventListener('lm-dragover', this, true);
        this.node.addEventListener('lm-dragleave', this, true);
        this.node.addEventListener('lm-drop', this, true);
    }
    onAfterDetach(msg) {
        super.onAfterDetach(msg);
        this.node.removeEventListener('lm-dragenter', this, true);
        this.node.removeEventListener('lm-dragover', this, true);
        this.node.removeEventListener('lm-dragleave', this, true);
        this.node.removeEventListener('lm-drop', this, true);
    }
    handleEvent(event) {
        switch (event.type) {
            case 'lm-dragenter':
                this.handleDragEnter(event);
                break;
            case 'lm-dragover':
                this.handleDragOver(event);
                break;
            case 'lm-dragleave':
                this.handleDragLeave(event);
                break;
            case 'lm-drop':
                this.handleDrop(event);
                break;
        }
    }
    handleDragEnter(event) {
        if (event.mimeData.hasData('application/vnd.lumino.view-container-factory')) {
            event.preventDefault();
            event.stopPropagation();
        }
    }
    handleDragOver(event) {
        const factory = event.mimeData.getData('application/vnd.lumino.view-container-factory');
        const widget = factory && factory();
        if (!(widget instanceof ViewContainerPart)) {
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        const sameContainers = this.id === widget.currentViewContainerId;
        const targetPart = algorithm_1.ArrayExt.findFirstValue(this.getParts(), (p => domutils_1.ElementExt.hitTest(p.node, event.clientX, event.clientY)));
        if (!targetPart && sameContainers) {
            event.dropAction = 'none';
            return;
        }
        if (targetPart) {
            // add overlay class style to the `targetPart` node.
            targetPart.node.classList.add('drop-target');
            this.toDisposeOnDragEnd.push(disposable_1.Disposable.create(() => targetPart.node.classList.remove('drop-target')));
        }
        else {
            // show panel overlay.
            const dockPanel = this.getDockPanel();
            if (dockPanel) {
                dockPanel.overlay.show({ top: 0, bottom: 0, right: 0, left: 0 });
                this.toDisposeOnDragEnd.push(disposable_1.Disposable.create(() => dockPanel.overlay.hide(100)));
            }
        }
        const isDraggingOutsideDisabled = this.disableDNDBetweenContainers || widget.viewContainer?.disableDNDBetweenContainers
            || widget.options.disableDraggingToOtherContainers;
        if (isDraggingOutsideDisabled && !sameContainers) {
            const { target } = event;
            if (target instanceof HTMLElement) {
                target.classList.add('theia-cursor-no-drop');
                this.toDisposeOnDragEnd.push(disposable_1.Disposable.create(() => {
                    target.classList.remove('theia-cursor-no-drop');
                }));
            }
            event.dropAction = 'none';
            return;
        }
        ;
        event.dropAction = event.proposedAction;
    }
    ;
    handleDragLeave(event) {
        this.toDisposeOnDragEnd.dispose();
        if (event.mimeData.hasData('application/vnd.lumino.view-container-factory')) {
            event.preventDefault();
            event.stopPropagation();
        }
    }
    ;
    handleDrop(event) {
        this.toDisposeOnDragEnd.dispose();
        const factory = event.mimeData.getData('application/vnd.lumino.view-container-factory');
        const draggedPart = factory && factory();
        if (!(draggedPart instanceof ViewContainerPart)) {
            event.dropAction = 'none';
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        const parts = this.getParts();
        const toIndex = algorithm_1.ArrayExt.findFirstIndex(parts, part => domutils_1.ElementExt.hitTest(part.node, event.clientX, event.clientY));
        if (draggedPart.currentViewContainerId !== this.id) {
            this.attachNewPart(draggedPart, toIndex > -1 ? toIndex + 1 : toIndex);
            draggedPart.onPartMoved(this);
        }
        else {
            this.moveBefore(draggedPart.id, parts[toIndex].id);
        }
        event.dropAction = event.proposedAction;
    }
    registerDND(part) {
        part.headerElement.draggable = true;
        return new disposable_1.DisposableCollection((0, widgets_1.addEventListener)(part.headerElement, 'dragstart', event => {
            event.preventDefault();
            const mimeData = new coreutils_1.MimeData();
            mimeData.setData('application/vnd.lumino.view-container-factory', () => part);
            const clonedHeader = part.headerElement.cloneNode(true);
            clonedHeader.style.width = part.node.style.width;
            clonedHeader.style.opacity = '0.6';
            const drag = new dragdrop_1.Drag({
                mimeData,
                dragImage: clonedHeader,
                proposedAction: 'move',
                supportedActions: 'move'
            });
            part.node.classList.add('lm-mod-hidden');
            drag.start(event.clientX, event.clientY).then(dropAction => {
                // The promise is resolved when the drag has ended
                if (dropAction === 'move' && part.currentViewContainerId !== this.id) {
                    this.removeWidget(part.wrapped);
                    this.lastVisibleState = this.doStoreState();
                }
            });
            setTimeout(() => { part.node.classList.remove('lm-mod-hidden'); }, 0);
        }, false));
    }
    getDockPanel() {
        let panel;
        let parent = this.parent;
        while (!panel && parent) {
            if (this.isSideDockPanel(parent)) {
                panel = parent;
            }
            else {
                parent = parent.parent;
            }
        }
        return panel;
    }
    isSideDockPanel(widget) {
        const { leftPanelHandler, rightPanelHandler } = this.shell;
        if (widget instanceof widgets_1.DockPanel && (widget.id === rightPanelHandler.dockPanel.id || widget.id === leftPanelHandler.dockPanel.id)) {
            return true;
        }
        return false;
    }
};
exports.ViewContainer = ViewContainer;
tslib_1.__decorate([
    (0, inversify_1.inject)(frontend_application_state_1.FrontendApplicationStateService),
    tslib_1.__metadata("design:type", frontend_application_state_1.FrontendApplicationStateService)
], ViewContainer.prototype, "applicationStateService", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(context_menu_renderer_1.ContextMenuRenderer),
    tslib_1.__metadata("design:type", context_menu_renderer_1.ContextMenuRenderer)
], ViewContainer.prototype, "contextMenuRenderer", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(command_1.CommandRegistry),
    tslib_1.__metadata("design:type", command_1.CommandRegistry)
], ViewContainer.prototype, "commandRegistry", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(menu_1.MenuModelRegistry),
    tslib_1.__metadata("design:type", menu_1.MenuModelRegistry)
], ViewContainer.prototype, "menuRegistry", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(widget_manager_1.WidgetManager),
    tslib_1.__metadata("design:type", widget_manager_1.WidgetManager)
], ViewContainer.prototype, "widgetManager", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(shell_1.SplitPositionHandler),
    tslib_1.__metadata("design:type", shell_1.SplitPositionHandler)
], ViewContainer.prototype, "splitPositionHandler", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(ViewContainerIdentifier),
    tslib_1.__metadata("design:type", ViewContainerIdentifier)
], ViewContainer.prototype, "options", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(tab_bar_toolbar_1.TabBarToolbarRegistry),
    tslib_1.__metadata("design:type", tab_bar_toolbar_1.TabBarToolbarRegistry)
], ViewContainer.prototype, "toolbarRegistry", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(tab_bar_toolbar_1.TabBarToolbarFactory),
    tslib_1.__metadata("design:type", Function)
], ViewContainer.prototype, "toolbarFactory", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(progress_bar_factory_1.ProgressBarFactory),
    tslib_1.__metadata("design:type", Function)
], ViewContainer.prototype, "progressBarFactory", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(shell_1.ApplicationShell),
    tslib_1.__metadata("design:type", shell_1.ApplicationShell)
], ViewContainer.prototype, "shell", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(tab_bar_decorator_1.TabBarDecoratorService),
    tslib_1.__metadata("design:type", tab_bar_decorator_1.TabBarDecoratorService)
], ViewContainer.prototype, "decoratorService", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(context_key_service_1.ContextKeyService),
    tslib_1.__metadata("design:type", Object)
], ViewContainer.prototype, "contextKeyService", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(keybinding_1.KeybindingRegistry),
    tslib_1.__metadata("design:type", keybinding_1.KeybindingRegistry)
], ViewContainer.prototype, "keybindingRegistry", void 0);
tslib_1.__decorate([
    (0, inversify_1.postConstruct)(),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", []),
    tslib_1.__metadata("design:returntype", void 0)
], ViewContainer.prototype, "init", null);
exports.ViewContainer = ViewContainer = ViewContainer_1 = tslib_1.__decorate([
    (0, inversify_1.injectable)()
], ViewContainer);
(function (ViewContainer) {
    ViewContainer.Factory = Symbol('ViewContainerFactory');
    function getOrientation(node) {
        if (node.closest(`#${theia_dock_panel_1.MAIN_AREA_ID}`) || node.closest(`#${theia_dock_panel_1.BOTTOM_AREA_ID}`)) {
            return 'horizontal';
        }
        return 'vertical';
    }
    ViewContainer.getOrientation = getOrientation;
})(ViewContainer || (exports.ViewContainer = ViewContainer = {}));
/**
 * Wrapper around a widget held by a view container. Adds a header to display the
 * title, toolbar, and collapse / expand handle.
 */
class ViewContainerPart extends widgets_1.BaseWidget {
    constructor(wrapped, partId, currentContainerId, originalContainerId, originalContainerTitle, toolbarRegistry, toolbarFactory, options = {}) {
        super();
        this.wrapped = wrapped;
        this.partId = partId;
        this.currentContainerId = currentContainerId;
        this.originalContainerId = originalContainerId;
        this.originalContainerTitle = originalContainerTitle;
        this.toolbarRegistry = toolbarRegistry;
        this.toolbarFactory = toolbarFactory;
        this.options = options;
        this.collapsedEmitter = new event_1.Emitter();
        this.contextMenuEmitter = new event_1.Emitter();
        this.onTitleChangedEmitter = new event_1.Emitter();
        this.onTitleChanged = this.onTitleChangedEmitter.event;
        this.onDidFocusEmitter = new event_1.Emitter();
        this.onDidFocus = this.onDidFocusEmitter.event;
        this.onPartMovedEmitter = new event_1.Emitter();
        this.onDidMove = this.onPartMovedEmitter.event;
        this.onDidChangeDescriptionEmitter = new event_1.Emitter();
        this.onDidChangeDescription = this.onDidChangeDescriptionEmitter.event;
        this.onDidChangeBadgeEmitter = new event_1.Emitter();
        this.onDidChangeBadge = this.onDidChangeBadgeEmitter.event;
        this.onDidChangeBadgeTooltipEmitter = new event_1.Emitter();
        this.onDidChangeBadgeTooltip = this.onDidChangeBadgeTooltipEmitter.event;
        this.toShowHeader = new disposable_1.DisposableCollection();
        wrapped.parent = this;
        wrapped.disposed.connect(() => this.dispose());
        this.id = `${originalContainerId}--${wrapped.id}`;
        this.addClass('part');
        const fireTitleChanged = () => this.onTitleChangedEmitter.fire(undefined);
        this.wrapped.title.changed.connect(fireTitleChanged);
        this.toDispose.push(disposable_1.Disposable.create(() => this.wrapped.title.changed.disconnect(fireTitleChanged)));
        if (DescriptionWidget.is(this.wrapped)) {
            this.wrapped?.onDidChangeDescription(() => this.onDidChangeDescriptionEmitter.fire(), undefined, this.toDispose);
        }
        if (BadgeWidget.is(this.wrapped)) {
            this.wrapped.onDidChangeBadge(() => this.onDidChangeBadgeEmitter.fire(), undefined, this.toDispose);
            this.wrapped.onDidChangeBadgeTooltip(() => this.onDidChangeBadgeTooltipEmitter.fire(), undefined, this.toDispose);
        }
        if (DynamicToolbarWidget.is(this.wrapped)) {
            this.wrapped.onDidChangeToolbarItems(() => {
                this.toolbar.updateTarget(this.wrapped);
                this.viewContainer?.update();
            });
        }
        const { header, body, disposable } = this.createContent();
        this.header = header;
        this.body = body;
        this.toNoDisposeWrapped = this.toDispose.push(wrapped);
        this.toolbar = this.toolbarFactory();
        this.toolbar.addClass('theia-view-container-part-title');
        this.toDispose.pushAll([
            disposable,
            this.toolbar,
            this.toolbarRegistry.onDidChange(() => this.toolbar.updateTarget(this.wrapped)),
            this.collapsedEmitter,
            this.contextMenuEmitter,
            this.onTitleChangedEmitter,
            this.onDidChangeDescriptionEmitter,
            this.onDidChangeBadgeEmitter,
            this.onDidChangeBadgeTooltipEmitter,
            this.registerContextMenu(),
            this.onDidFocusEmitter,
            // focus event does not bubble, capture it
            (0, widgets_1.addEventListener)(this.node, 'focus', () => this.onDidFocusEmitter.fire(this), true)
        ]);
        this.scrollOptions = {
            suppressScrollX: true,
            minScrollbarLength: 35
        };
        this.collapsed = !!options.initiallyCollapsed;
        if (options.initiallyHidden && this.canHide) {
            this.hide();
        }
    }
    get viewContainer() {
        return this.parent ? this.parent.parent : undefined;
    }
    get currentViewContainerId() {
        return this.currentContainerId;
    }
    get headerElement() {
        return this.header;
    }
    get collapsed() {
        return this._collapsed;
    }
    set collapsed(collapsed) {
        // Cannot collapse/expand if the orientation of the container is `horizontal`.
        const orientation = ViewContainer.getOrientation(this.node);
        if (this._collapsed === collapsed || (orientation === 'horizontal' && collapsed)) {
            return;
        }
        this._collapsed = collapsed;
        this.node.classList.toggle('collapsed', collapsed);
        if (collapsed && this.wrapped.node.contains(document.activeElement)) {
            this.header.focus();
        }
        this.wrapped.setHidden(collapsed);
        const toggleIcon = this.header.querySelector(`span.${widgets_1.EXPANSION_TOGGLE_CLASS}`);
        if (toggleIcon) {
            if (collapsed) {
                toggleIcon.classList.add(widgets_1.COLLAPSED_CLASS);
            }
            else {
                toggleIcon.classList.remove(widgets_1.COLLAPSED_CLASS);
            }
        }
        this.update();
        this.collapsedEmitter.fire(collapsed);
    }
    onPartMoved(newContainer) {
        this.currentContainerId = newContainer.id;
        this.onPartMovedEmitter.fire(newContainer);
    }
    setHidden(hidden) {
        if (!this.canHide) {
            return;
        }
        super.setHidden(hidden);
    }
    get canHide() {
        return this.options.canHide === undefined || this.options.canHide;
    }
    get onCollapsed() {
        return this.collapsedEmitter.event;
    }
    get onContextMenu() {
        return this.contextMenuEmitter.event;
    }
    get minSize() {
        const style = getComputedStyle(this.body);
        if (ViewContainer.getOrientation(this.node) === 'horizontal') {
            return (0, browser_1.parseCssMagnitude)(style.minWidth, 0);
        }
        else {
            return (0, browser_1.parseCssMagnitude)(style.minHeight, 0);
        }
    }
    showTitle() {
        this.toShowHeader.dispose();
    }
    hideTitle() {
        if (this.titleHidden) {
            return;
        }
        const display = this.header.style.display;
        const height = this.body.style.height;
        this.body.style.height = '100%';
        this.header.style.display = 'none';
        this.toShowHeader.push(disposable_1.Disposable.create(() => {
            this.header.style.display = display;
            this.body.style.height = height;
        }));
    }
    get titleHidden() {
        return !this.toShowHeader.disposed || this.collapsed;
    }
    getScrollContainer() {
        return this.body;
    }
    registerContextMenu() {
        return new disposable_1.DisposableCollection((0, widgets_1.addEventListener)(this.header, 'contextmenu', event => {
            this.contextMenuEmitter.fire(event);
        }));
    }
    createContent() {
        const disposable = new disposable_1.DisposableCollection();
        const { header, disposable: headerDisposable } = this.createHeader();
        const body = document.createElement('div');
        body.classList.add('body');
        this.node.appendChild(header);
        this.node.appendChild(body);
        disposable.push(headerDisposable);
        return {
            header,
            body,
            disposable,
        };
    }
    createHeader() {
        const disposable = new disposable_1.DisposableCollection();
        const header = document.createElement('div');
        header.tabIndex = 0;
        header.classList.add('theia-header', 'header', 'theia-view-container-part-header');
        disposable.push((0, widgets_1.addEventListener)(header, 'click', event => {
            if (this.toolbar && this.toolbar.shouldHandleMouseEvent(event)) {
                return;
            }
            this.collapsed = !this.collapsed;
        }));
        disposable.push((0, widgets_1.addKeyListener)(header, keys_1.Key.ARROW_LEFT, () => this.collapsed = true));
        disposable.push((0, widgets_1.addKeyListener)(header, keys_1.Key.ARROW_RIGHT, () => this.collapsed = false));
        disposable.push((0, widgets_1.addKeyListener)(header, keys_1.Key.ENTER, () => this.collapsed = !this.collapsed));
        const toggleIcon = document.createElement('span');
        toggleIcon.classList.add(widgets_1.EXPANSION_TOGGLE_CLASS, ...widgets_1.CODICON_TREE_ITEM_CLASSES);
        if (this.collapsed) {
            toggleIcon.classList.add(widgets_1.COLLAPSED_CLASS);
        }
        header.appendChild(toggleIcon);
        const title = document.createElement('span');
        title.classList.add('label', 'noselect');
        const description = document.createElement('span');
        description.classList.add('description');
        const badgeSpan = document.createElement('span');
        badgeSpan.classList.add('notification-count');
        const badgeContainer = document.createElement('div');
        badgeContainer.classList.add('notification-count-container');
        badgeContainer.appendChild(badgeSpan);
        const badgeContainerDisplay = badgeContainer.style.display;
        const updateTitle = () => {
            if (this.currentContainerId !== this.originalContainerId && this.originalContainerTitle?.label) {
                // Creating a title in format: <original_container_title>: <part_title>.
                title.innerText = this.originalContainerTitle.label + ': ' + this.wrapped.title.label;
            }
            else {
                title.innerText = this.wrapped.title.label;
            }
        };
        const updateCaption = () => title.title = this.wrapped.title.caption || this.wrapped.title.label;
        const updateDescription = () => {
            description.innerText = DescriptionWidget.is(this.wrapped) && !this.collapsed && this.wrapped.description || '';
        };
        const updateBadge = () => {
            if (BadgeWidget.is(this.wrapped)) {
                const visibleToolBarItems = this.toolbarRegistry.visibleItems(this.wrapped).length > 0;
                const badge = this.wrapped.badge;
                if (badge && !visibleToolBarItems) {
                    badgeSpan.innerText = badge.toString();
                    badgeSpan.title = this.wrapped.badgeTooltip || '';
                    badgeContainer.style.display = badgeContainerDisplay;
                    return;
                }
            }
            badgeContainer.style.display = 'none';
        };
        updateTitle();
        updateCaption();
        updateDescription();
        updateBadge();
        disposable.pushAll([
            this.onTitleChanged(updateTitle),
            this.onTitleChanged(updateCaption),
            this.onDidMove(updateTitle),
            this.onDidChangeDescription(updateDescription),
            this.onDidChangeBadge(updateBadge),
            this.onDidChangeBadgeTooltip(updateBadge),
            this.onCollapsed(updateDescription)
        ]);
        header.appendChild(title);
        header.appendChild(description);
        header.appendChild(badgeContainer);
        return {
            header,
            disposable
        };
    }
    handleResize() {
        const handleMouseEnter = () => {
            this.node?.classList.add('no-pointer-events');
            setTimeout(() => {
                this.node?.classList.remove('no-pointer-events');
                this.node?.removeEventListener('mouseenter', handleMouseEnter);
            }, 100);
        };
        this.node?.addEventListener('mouseenter', handleMouseEnter);
    }
    onResize(msg) {
        this.handleResize();
        if (this.wrapped.isAttached && !this.collapsed) {
            widgets_1.MessageLoop.sendMessage(this.wrapped, widgets_1.Widget.ResizeMessage.UnknownSize);
        }
        super.onResize(msg);
    }
    onUpdateRequest(msg) {
        if (this.wrapped.isAttached && !this.collapsed) {
            widgets_1.MessageLoop.sendMessage(this.wrapped, msg);
        }
        super.onUpdateRequest(msg);
    }
    onAfterAttach(msg) {
        if (!this.wrapped.isAttached) {
            widgets_1.UnsafeWidgetUtilities.attach(this.wrapped, this.body);
        }
        widgets_1.UnsafeWidgetUtilities.attach(this.toolbar, this.header);
        super.onAfterAttach(msg);
    }
    onBeforeDetach(msg) {
        super.onBeforeDetach(msg);
        if (this.toolbar.isAttached) {
            widgets_1.Widget.detach(this.toolbar);
        }
        if (this.wrapped.isAttached) {
            widgets_1.UnsafeWidgetUtilities.detach(this.wrapped);
        }
    }
    onBeforeShow(msg) {
        if (this.wrapped.isAttached && !this.collapsed) {
            widgets_1.MessageLoop.sendMessage(this.wrapped, msg);
        }
        super.onBeforeShow(msg);
    }
    onAfterShow(msg) {
        super.onAfterShow(msg);
        if (this.wrapped.isAttached && !this.collapsed) {
            widgets_1.MessageLoop.sendMessage(this.wrapped, msg);
        }
    }
    onBeforeHide(msg) {
        if (this.wrapped.isAttached && !this.collapsed) {
            widgets_1.MessageLoop.sendMessage(this.wrapped, msg);
        }
        super.onBeforeShow(msg);
    }
    onAfterHide(msg) {
        super.onAfterHide(msg);
        if (this.wrapped.isAttached && !this.collapsed) {
            widgets_1.MessageLoop.sendMessage(this.wrapped, msg);
        }
    }
    onChildRemoved(msg) {
        super.onChildRemoved(msg);
        // if wrapped is not disposed, but detached then we should not dispose it, but only get rid of this part
        this.toNoDisposeWrapped.dispose();
        this.dispose();
    }
    onActivateRequest(msg) {
        super.onActivateRequest(msg);
        if (this.collapsed) {
            this.header.focus();
        }
        else {
            this.wrapped.activate();
        }
    }
}
exports.ViewContainerPart = ViewContainerPart;
(function (ViewContainerPart) {
    /**
     * Make sure to adjust the `line-height` of the `.theia-view-container .part > .header` CSS class when modifying this, and vice versa.
     */
    ViewContainerPart.HEADER_HEIGHT = 22;
    function closestPart(element, selector = 'div.part') {
        if (element instanceof Element) {
            const part = element.closest(selector);
            if (part instanceof Element) {
                return part;
            }
        }
        return undefined;
    }
    ViewContainerPart.closestPart = closestPart;
})(ViewContainerPart || (exports.ViewContainerPart = ViewContainerPart = {}));
class ViewContainerLayout extends widgets_1.SplitLayout {
    constructor(options, splitPositionHandler) {
        super(options);
        this.options = options;
        this.splitPositionHandler = splitPositionHandler;
    }
    get items() {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return this._items;
    }
    iter() {
        return (0, algorithm_1.map)(this.items, item => item.widget);
    }
    // @ts-expect-error TS2611 `SplitLayout.widgets` is declared as `readonly widgets` but is implemented as a getter.
    get widgets() {
        return Array.from(this.iter());
    }
    attachWidget(index, widget) {
        super.attachWidget(index, widget);
        if (index > -1 && this.parent && this.parent.node.contains(this.widgets[index + 1]?.node)) {
            // Set the correct attach index to the DOM elements.
            const ref = this.widgets[index + 1].node;
            this.parent.node.insertBefore(widget.node, ref);
            this.parent.node.insertBefore(this.handles[index], ref);
            this.parent.fit();
        }
    }
    getPartSize(part) {
        if (part.collapsed || part.isHidden) {
            return part.uncollapsedSize;
        }
        if (this.orientation === 'horizontal') {
            return part.node.offsetWidth;
        }
        else {
            return part.node.offsetHeight;
        }
    }
    /**
     * Set the sizes of the view container parts according to the given weights
     * by moving the split handles. This is similar to `setRelativeSizes` defined
     * in `SplitLayout`, but here we properly consider the collapsed / expanded state.
     */
    setPartSizes(weights) {
        const parts = this.widgets;
        const availableSize = this.getAvailableSize();
        // Sum up the weights of visible parts
        let totalWeight = 0;
        let weightCount = 0;
        for (let index = 0; index < weights.length && index < parts.length; index++) {
            const part = parts[index];
            const weight = weights[index];
            if (weight && !part.isHidden && !part.collapsed) {
                totalWeight += weight;
                weightCount++;
            }
        }
        if (weightCount === 0 || availableSize === 0) {
            return;
        }
        // Add the average weight for visible parts without weight
        const averageWeight = totalWeight / weightCount;
        for (let index = 0; index < weights.length && index < parts.length; index++) {
            const part = parts[index];
            const weight = weights[index];
            if (!weight && !part.isHidden && !part.collapsed) {
                totalWeight += averageWeight;
            }
        }
        // Apply the weights to compute actual sizes
        let position = 0;
        for (let index = 0; index < weights.length && index < parts.length - 1; index++) {
            const part = parts[index];
            if (!part.isHidden) {
                if (this.orientation === 'vertical') {
                    position += this.options.headerSize;
                }
                const weight = weights[index];
                if (part.collapsed) {
                    if (weight) {
                        part.uncollapsedSize = weight / totalWeight * availableSize;
                    }
                }
                else {
                    let contentSize = (weight || averageWeight) / totalWeight * availableSize;
                    const minSize = part.minSize;
                    if (contentSize < minSize) {
                        contentSize = minSize;
                    }
                    position += contentSize;
                }
                this.setHandlePosition(index, position);
                position += this.spacing;
            }
        }
    }
    /**
     * Determine the size of the split panel area that is available for widget content,
     * i.e. excluding part headers and split handles.
     */
    getAvailableSize() {
        if (!this.parent || !this.parent.isAttached) {
            return 0;
        }
        const parts = this.widgets;
        const visiblePartCount = parts.filter(part => !part.isHidden).length;
        let availableSize;
        if (this.orientation === 'horizontal') {
            availableSize = this.parent.node.offsetWidth;
        }
        else {
            availableSize = this.parent.node.offsetHeight;
            availableSize -= visiblePartCount * this.options.headerSize;
        }
        availableSize -= (visiblePartCount - 1) * this.spacing;
        if (availableSize < 0) {
            return 0;
        }
        return availableSize;
    }
    /**
     * Update a view container part that has been collapsed or expanded. The transition
     * to the new state is animated.
     */
    updateCollapsed(part, enableAnimation) {
        const index = this.items.findIndex(item => item.widget === part);
        if (index < 0 || !this.parent || part.isHidden) {
            return;
        }
        // Do not store the height of the "stretched item". Otherwise, we mess up the "hint height".
        // Store the height only if there are other expanded items.
        const currentSize = this.orientation === 'horizontal' ? part.node.offsetWidth : part.node.offsetHeight;
        if (part.collapsed && this.items.some(item => !item.widget.collapsed && !item.widget.isHidden)) {
            part.uncollapsedSize = currentSize;
        }
        if (!enableAnimation || this.options.animationDuration <= 0) {
            widgets_1.MessageLoop.postMessage(this.parent, widgets_1.Widget.Msg.FitRequest);
            return;
        }
        let startTime = undefined;
        const duration = this.options.animationDuration;
        const direction = part.collapsed ? 'collapse' : 'expand';
        let fullSize;
        if (direction === 'collapse') {
            fullSize = currentSize - this.options.headerSize;
        }
        else {
            fullSize = Math.max((part.uncollapsedSize || 0) - this.options.headerSize, part.minSize);
            if (this.items.filter(item => !item.widget.collapsed && !item.widget.isHidden).length === 1) {
                // Expand to full available size
                fullSize = Math.max(fullSize, this.getAvailableSize());
            }
        }
        // The update function is called on every animation frame until the predefined duration has elapsed.
        const updateFunc = (time) => {
            if (!this.parent) {
                part.animatedSize = undefined;
                return;
            }
            if (startTime === undefined) {
                startTime = time;
            }
            if (time - startTime < duration) {
                // Render an intermediate state for the animation
                const t = this.tween((time - startTime) / duration);
                if (direction === 'collapse') {
                    part.animatedSize = (1 - t) * fullSize;
                }
                else {
                    part.animatedSize = t * fullSize;
                }
                requestAnimationFrame(updateFunc);
            }
            else {
                // The animation is finished
                if (direction === 'collapse') {
                    part.animatedSize = undefined;
                }
                else {
                    part.animatedSize = fullSize;
                    // Request another frame to reset the part to variable size
                    requestAnimationFrame(() => {
                        part.animatedSize = undefined;
                        if (this.parent) {
                            widgets_1.MessageLoop.sendMessage(this.parent, widgets_1.Widget.Msg.FitRequest);
                        }
                    });
                }
            }
            widgets_1.MessageLoop.sendMessage(this.parent, widgets_1.Widget.Msg.FitRequest);
        };
        requestAnimationFrame(updateFunc);
    }
    updateSashes() {
        const { widgets, handles } = this;
        if (widgets.length !== handles.length) {
            console.warn('Unexpected mismatch between number of widgets and number of handles.');
            return;
        }
        const firstUncollapsed = this.getFirstUncollapsedWidgetIndex();
        const lastUncollapsed = firstUncollapsed === undefined ? undefined : this.getLastUncollapsedWidgetIndex();
        const allHidden = firstUncollapsed === lastUncollapsed;
        for (const [index, handle] of this.handles.entries()) {
            // The or clauses are added for type checking. If they're true, allHidden will also have been true.
            if (allHidden || firstUncollapsed === undefined || lastUncollapsed === undefined) {
                handle.classList.add('sash-hidden');
            }
            else if (index < lastUncollapsed && index >= firstUncollapsed) {
                handle.classList.remove('sash-hidden');
            }
            else {
                handle.classList.add('sash-hidden');
            }
        }
    }
    getFirstUncollapsedWidgetIndex() {
        const index = this.widgets.findIndex(widget => !widget.collapsed && !widget.isHidden);
        return index === -1 ? undefined : index;
    }
    getLastUncollapsedWidgetIndex() {
        for (let i = this.widgets.length - 1; i >= 0; i--) {
            if (!this.widgets[i].collapsed && !this.widgets[i].isHidden) {
                return i;
            }
        }
    }
    onFitRequest(msg) {
        for (const part of this.widgets) {
            const style = part.node.style;
            if (part.animatedSize !== undefined) {
                // The part size has been fixed for animating the transition to collapsed / expanded state
                const fixedSize = `${this.options.headerSize + part.animatedSize}px`;
                style.minHeight = fixedSize;
                style.maxHeight = fixedSize;
            }
            else if (part.collapsed) {
                // The part size is fixed to the header size
                const fixedSize = `${this.options.headerSize}px`;
                style.minHeight = fixedSize;
                style.maxHeight = fixedSize;
            }
            else {
                const minSize = `${this.options.headerSize + part.minSize}px`;
                style.minHeight = minSize;
                style.maxHeight = '';
            }
        }
        super.onFitRequest(msg);
    }
    /**
     * Sinusoidal tween function for smooth animation.
     */
    tween(t) {
        return 0.5 * (1 - Math.cos(Math.PI * t));
    }
    setHandlePosition(index, position) {
        const options = {
            referenceWidget: this.widgets[index],
            duration: 0
        };
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return this.splitPositionHandler.setSplitHandlePosition(this.parent, index, position, options);
    }
}
exports.ViewContainerLayout = ViewContainerLayout;
//# sourceMappingURL=view-container.js.map