/******************************************************************************** * Copyright (C) 2018 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 WITH Classpath-exception-2.0 ********************************************************************************/ import { ArrayExt, find, toArray } from '@phosphor/algorithm'; import { DockLayout, DockPanel, TabBar, Title, Widget } from '@phosphor/widgets'; import { Signal } from '@phosphor/signaling'; import { Disposable, DisposableCollection } from '@gedit/utils'; import { MessageLoop } from '../widgets'; const MAXIMIZED_CLASS = 'theia-maximized'; export const MAIN_AREA_ID = 'theia-main-content-panel'; export const BOTTOM_AREA_ID = 'theia-bottom-content-panel'; /** * This specialization of DockPanel adds various events that are used for implementing the * side panels of the application shell. */ export class TheiaDockPanel extends DockPanel { /** * Emitted when a widget is added to the panel. */ readonly widgetAdded = new Signal(this); /** * Emitted when a widget is activated by calling `activateWidget`. */ readonly widgetActivated = new Signal(this); /** * Emitted when a widget is removed from the panel. */ readonly widgetRemoved = new Signal(this); protected readonly toDisposeOnMarkAsCurrent = new DisposableCollection(); protected readonly toDisposeOnToggleMaximized = new DisposableCollection(); protected maximizedElement: HTMLElement | undefined; constructor(options?: DockPanel.IOptions) { super(options); this['_onCurrentChanged'] = (sender: TabBar, args: TabBar.ICurrentChangedArgs) => { this.markAsCurrent(args.currentTitle || undefined); super['_onCurrentChanged'](sender, args); }; this['_onTabActivateRequested'] = (sender: TabBar, args: TabBar.ITabActivateRequestedArgs) => { this.markAsCurrent(args.title); super['_onTabActivateRequested'](sender, args); }; } protected _currentTitle: Title | undefined; get currentTitle(): Title | undefined { return this._currentTitle; } get currentTabBar(): TabBar | undefined { return this._currentTitle && this.findTabBar(this._currentTitle); } findTabBar(title: Title): TabBar | undefined { return find(this.tabBars(), bar => ArrayExt.firstIndexOf(bar.titles, title) > -1); } markAsCurrent(title: Title | undefined): void { this.toDisposeOnMarkAsCurrent.dispose(); this._currentTitle = title; if (title) { const resetCurrent = () => this.markAsCurrent(undefined); title.owner.disposed.connect(resetCurrent); this.toDisposeOnMarkAsCurrent.push(Disposable.create(() => title.owner.disposed.disconnect(resetCurrent) )); } } addWidget(widget: Widget, options?: DockPanel.IAddOptions): void { if (this.mode === 'single-document' && widget.parent === this) { return; } super.addWidget(widget, options); this.widgetAdded.emit(widget); } activateWidget(widget: Widget): void { super.activateWidget(widget); this.widgetActivated.emit(widget); } nextTabBarWidget(widget: Widget): Widget | undefined { const current = this.findTabBar(widget.title); const next = current && this.nextTabBarInPanel(current); return next && next.currentTitle && next.currentTitle.owner || undefined; } nextTabBarInPanel(tabBar: TabBar): TabBar | undefined { const tabBars = toArray(this.tabBars()); const index = tabBars.indexOf(tabBar); if (index !== -1) { return tabBars[index + 1]; } return undefined; } previousTabBarWidget(widget: Widget): Widget | undefined { const current = this.findTabBar(widget.title); const previous = current && this.previousTabBarInPanel(current); return previous && previous.currentTitle && previous.currentTitle.owner || undefined; } previousTabBarInPanel(tabBar: TabBar): TabBar | undefined { const tabBars = toArray(this.tabBars()); const index = tabBars.indexOf(tabBar); if (index !== -1) { return tabBars[index - 1]; } return undefined; } toggleMaximized(): void { const areaContainer = this.node.parentElement; if (!areaContainer) { return; } const maximizedElement = this.getMaximizedElement(); if (areaContainer === maximizedElement) { this.toDisposeOnToggleMaximized.dispose(); return; } if (this.isAttached) { MessageLoop.sendMessage(this, Widget.Msg.BeforeDetach); this.node.remove(); MessageLoop.sendMessage(this, Widget.Msg.AfterDetach); } maximizedElement.style.display = 'block'; this.addClass(MAXIMIZED_CLASS); MessageLoop.sendMessage(this, Widget.Msg.BeforeAttach); maximizedElement.appendChild(this.node); MessageLoop.sendMessage(this, Widget.Msg.AfterAttach); this.fit(); this.toDisposeOnToggleMaximized.push(Disposable.create(() => { maximizedElement.style.display = 'none'; this.removeClass(MAXIMIZED_CLASS); if (this.isAttached) { MessageLoop.sendMessage(this, Widget.Msg.BeforeDetach); this.node.remove(); MessageLoop.sendMessage(this, Widget.Msg.AfterDetach); } MessageLoop.sendMessage(this, Widget.Msg.BeforeAttach); areaContainer.appendChild(this.node); MessageLoop.sendMessage(this, Widget.Msg.AfterAttach); this.fit(); })); const layout = this.layout; if (layout instanceof DockLayout) { const onResize = layout['onResize']; layout['onResize'] = () => onResize.bind(layout)(Widget.ResizeMessage.UnknownSize); this.toDisposeOnToggleMaximized.push(Disposable.create(() => layout['onResize'] = onResize)); } const removedListener = () => { if (!this.widgets().next()) { this.toDisposeOnToggleMaximized.dispose(); } }; this.widgetRemoved.connect(removedListener); this.toDisposeOnToggleMaximized.push(Disposable.create(() => this.widgetRemoved.disconnect(removedListener))); } protected onChildRemoved(msg: Widget.ChildMessage): void { super.onChildRemoved(msg); this.widgetRemoved.emit(msg.child); } protected getMaximizedElement(): HTMLElement { if (!this.maximizedElement) { this.maximizedElement = document.createElement('div'); this.maximizedElement.style.display = 'none'; document.body.appendChild(this.maximizedElement); } return this.maximizedElement; } }