Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented Toolbar support for sidepanels and changed sidepanel tabs. #4600

Merged
merged 3 commits into from
Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Breaking changes:
- [plugin] support multiple windows per a backend [#4509](https://github.com/theia-ide/theia/issues/4509)
- Some plugin bindings are scoped per a connection now. Clients, who contribute/rebind these bindings, will need to scope them per a connection as well.
- [quick-open] disable separate fuzzy matching by default [#4549](https://github.com/theia-ide/theia/pull/4549)
- [shell] support toolbars in side bars [#4600](https://github.com/theia-ide/theia/pull/4600)
- In side bars a widget title is rendered as an icon.

## v0.4.0
- [application-manager] added support for pre-load HTML templates
Expand Down
2 changes: 1 addition & 1 deletion examples/browser/test/left-panel/left-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class LeftPanel {
}

openCloseTab(tabName: string) {
this.driver.element('.p-TabBar.theia-app-left .p-TabBar-content').click(`div=${tabName}`);
this.driver.element('.p-TabBar.theia-app-left .p-TabBar-content').element(`div=${tabName}`).click('..');
// Wait for animations to finish
this.driver.pause(300);
}
Expand Down
12 changes: 6 additions & 6 deletions examples/browser/test/left-panel/left-panel.ui-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,20 @@ before(() => {
});

describe('theia left panel', () => {
it("should show 'Files' and 'Git'", () => {
expect(leftPanel.doesTabExist('Files')).to.be.true;
it("should show 'Explorer' and 'Git'", () => {
expect(leftPanel.doesTabExist('Explorer')).to.be.true;
expect(leftPanel.doesTabExist('Git')).to.be.true;
});

describe('files tab', () => {
it('should open/close the files tab', () => {
leftPanel.openCloseTab('Files');
leftPanel.openCloseTab('Explorer');
expect(leftPanel.isFileTreeVisible()).to.be.true;
expect(leftPanel.isTabActive('Files')).to.be.true;
expect(leftPanel.isTabActive('Explorer')).to.be.true;

leftPanel.openCloseTab('Files');
leftPanel.openCloseTab('Explorer');
expect(leftPanel.isFileTreeVisible()).to.be.false;
expect(leftPanel.isTabActive('Files')).to.be.false;
expect(leftPanel.isTabActive('Explorer')).to.be.false;
});
});

Expand Down
2 changes: 1 addition & 1 deletion examples/browser/test/right-panel/right-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class RightPanel {
}

openCloseTab(tabName: string) {
this.driver.element('.p-TabBar.theia-app-right .p-TabBar-content').click(`div=${tabName}`);
this.driver.element('.p-TabBar.theia-app-right .p-TabBar-content').element(`div=${tabName}`).click('..');
// Wait for animations to finish
this.driver.pause(300);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/browser/test/top-panel/top-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class TopPanel {

toggleFilesView() {
this.clickMenuTab('View');
this.clickSubMenu('Files');
this.clickSubMenu('Explorer');
}

toggleGitView() {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ import { LocalStorageService, StorageService } from './storage-service';
import { WidgetFactory, WidgetManager } from './widget-manager';
import {
ApplicationShell, ApplicationShellOptions, DockPanelRenderer, TabBarRenderer,
TabBarRendererFactory, ShellLayoutRestorer, SidePanelHandler, SidePanelHandlerFactory, SplitPositionHandler, DockPanelRendererFactory
TabBarRendererFactory, ShellLayoutRestorer,
SidePanelHandler, SidePanelHandlerFactory,
SplitPositionHandler, DockPanelRendererFactory
} from './shell';
import { StatusBar, StatusBarImpl } from './status-bar/status-bar';
import { LabelParser } from './label-parser';
Expand Down
33 changes: 28 additions & 5 deletions packages/core/src/browser/shell/side-panel-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { TabBarRendererFactory, TabBarRenderer, SHELL_TABBAR_CONTEXT_MENU, SideT
import { SplitPositionHandler, SplitPositionOptions } from './split-panels';
import { FrontendApplicationStateService } from '../frontend-application-state';
import { TheiaDockPanel } from './theia-dock-panel';
import { SidePanelToolbar } from './side-panel-toolbar';
import { TabBarToolbarRegistry, TabBarToolbarFactory, TabBarToolbar } from './tab-bar-toolbar';

/** The class name added to the left and right area panels. */
export const LEFT_RIGHT_AREA_CLASS = 'theia-app-sides';
Expand Down Expand Up @@ -56,6 +58,10 @@ export class SidePanelHandler {
* tab bar itself remains visible as long as there is at least one widget.
*/
tabBar: SideTabBar;
/**
* A tool bar, which displays a title and widget specific command buttons.
*/
toolBar: SidePanelToolbar;
/**
* The widget container is a dock panel in `single-document` mode, which means that the panel
* cannot be split.
Expand Down Expand Up @@ -85,6 +91,8 @@ export class SidePanelHandler {
*/
protected options: SidePanel.Options;

@inject(TabBarToolbarRegistry) protected tabBarToolBarRegistry: TabBarToolbarRegistry;
@inject(TabBarToolbarFactory) protected tabBarToolBarFactory: () => TabBarToolbar;
@inject(TabBarRendererFactory) protected tabBarRendererFactory: () => TabBarRenderer;
@inject(SplitPositionHandler) protected splitPositionHandler: SplitPositionHandler;
@inject(FrontendApplicationStateService) protected readonly applicationStateService: FrontendApplicationStateService;
Expand All @@ -96,6 +104,7 @@ export class SidePanelHandler {
this.side = side;
this.options = options;
this.tabBar = this.createSideBar();
this.toolBar = this.createToolbar();
this.dockPanel = this.createSidePanel();
this.container = this.createContainer();

Expand Down Expand Up @@ -152,7 +161,19 @@ export class SidePanelHandler {
return sidePanel;
}

protected createToolbar(): SidePanelToolbar {
const toolbar = new SidePanelToolbar(this.tabBarToolBarRegistry, this.tabBarToolBarFactory, this.side);
return toolbar;
}

protected createContainer(): Panel {
const contentBox = new BoxLayout({ direction: 'top-to-bottom', spacing: 0 });
BoxPanel.setStretch(this.toolBar, 0);
contentBox.addWidget(this.toolBar);
BoxPanel.setStretch(this.dockPanel, 1);
contentBox.addWidget(this.dockPanel);
const contentPanel = new BoxPanel({layout: contentBox});

const side = this.side;
let direction: BoxLayout.Direction;
switch (side) {
Expand All @@ -165,12 +186,12 @@ export class SidePanelHandler {
default:
throw new Error('Illegal argument: ' + side);
}
const boxLayout = new BoxLayout({ direction, spacing: 0 });
const containerLayout = new BoxLayout({ direction, spacing: 0 });
BoxPanel.setStretch(this.tabBar, 0);
boxLayout.addWidget(this.tabBar);
BoxPanel.setStretch(this.dockPanel, 1);
boxLayout.addWidget(this.dockPanel);
const boxPanel = new BoxPanel({ layout: boxLayout });
containerLayout.addWidget(this.tabBar);
BoxPanel.setStretch(contentPanel, 1);
containerLayout.addWidget(contentPanel);
const boxPanel = new BoxPanel({ layout: containerLayout });
boxPanel.id = 'theia-' + side + '-content-panel';
return boxPanel;
}
Expand Down Expand Up @@ -327,6 +348,8 @@ export class SidePanelHandler {
const hideDockPanel = currentTitle === null;
let relativeSizes: number[] | undefined;

this.toolBar.toolbarTitle = currentTitle || undefined;

if (hideDockPanel) {
container.addClass(COLLAPSED_CLASS);
if (this.state.expansion === SidePanel.ExpansionState.expanded && !this.state.empty) {
Expand Down
88 changes: 88 additions & 0 deletions packages/core/src/browser/shell/side-panel-toolbar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/********************************************************************************
* Copyright (C) 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 WITH Classpath-exception-2.0
********************************************************************************/

import { Widget, Title } from '@phosphor/widgets';
import { TabBarToolbar, TabBarToolbarRegistry } from './tab-bar-toolbar';
import { Message } from '@phosphor/messaging';

export class SidePanelToolbar extends Widget {

protected titleContainer: HTMLElement | undefined;
private _toolbarTitle: Title<Widget> | undefined;
protected toolbar: TabBarToolbar | undefined;

constructor(
protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry,
protected readonly tabBarToolbarFactory: () => TabBarToolbar,
protected readonly side: 'left' | 'right') {
super();
this.init();
this.tabBarToolbarRegistry.onDidChange(() => this.update());
}

protected onAfterAttach(msg: Message): void {
if (this.toolbar) {
if (this.toolbar.isAttached) {
Widget.detach(this.toolbar);
}
Widget.attach(this.toolbar, this.node);
}
super.onAfterAttach(msg);
}

protected onBeforeDetach(msg: Message): void {
if (this.titleContainer) {
this.node.removeChild(this.titleContainer);
}
if (this.toolbar && this.toolbar.isAttached) {
Widget.detach(this.toolbar);
}
super.onBeforeDetach(msg);
}

protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.updateToolbar();
}

protected updateToolbar(): void {
if (!this.toolbar) {
return;
}
const current = this._toolbarTitle;
const widget = current && current.owner || undefined;
const items = widget ? this.tabBarToolbarRegistry.visibleItems(widget) : [];
this.toolbar.updateItems(items, widget);
}

protected init(): void {
this.titleContainer = document.createElement('div');
this.titleContainer.classList.add('theia-sidepanel-title');
this.node.appendChild(this.titleContainer);
this.node.classList.add('theia-sidepanel-toolbar');
this.node.classList.add(`theia-${this.side}-side-panel`);
this.toolbar = this.tabBarToolbarFactory();
this.update();
}

set toolbarTitle(title: Title<Widget> | undefined) {
if (this.titleContainer && title) {
this._toolbarTitle = title;
this.titleContainer.innerHTML = this._toolbarTitle.label;
this.update();
}
}
}
28 changes: 24 additions & 4 deletions packages/core/src/browser/shell/tab-bar-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import { FrontendApplicationContribution } from '../frontend-application';
import { CommandRegistry, CommandService } from '../../common/command';
import { Disposable } from '../../common/disposable';
import { ContextKeyService } from '../context-key-service';
import { Event, Emitter } from '../../common/event';

import debounce = require('lodash.debounce');

/**
* Factory for instantiating tab-bar toolbars.
Expand Down Expand Up @@ -80,15 +83,21 @@ export class TabBarToolbar extends ReactWidget {
}
}
const command = this.commands.getCommand(item.command);
const iconClass = command && command.iconClass;
if (iconClass) {
classNames.push(iconClass);
if (command) {
const iconClass = command.iconClass;
if (iconClass) {
classNames.push(iconClass);
}
}
return <div key={item.id} className={TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM} >
return <div key={item.id} className={`${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM}${command && this.commandIsEnabled(command.id) ? ' enabled' : ''}`} >
<div id={item.id} className={classNames.join(' ')} onClick={this.executeCommand} title={item.tooltip}>{innerText}</div>
</div>;
}

protected commandIsEnabled(command: string): boolean {
return this.commands.isEnabled(command, this.current);
}

protected executeCommand = (e: React.MouseEvent<HTMLElement>) => {
const item = this.items.get(e.currentTarget.id);
if (item) {
Expand Down Expand Up @@ -174,6 +183,8 @@ export interface TabBarToolbarItem {
*/
readonly when?: string;

readonly onDidChange?: Event<void>;

}

export namespace TabBarToolbarItem {
Expand Down Expand Up @@ -227,6 +238,11 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
@named(TabBarToolbarContribution)
protected readonly contributionProvider: ContributionProvider<TabBarToolbarContribution>;

protected readonly onDidChangeEmitter = new Emitter<void>();
readonly onDidChange: Event<void> = this.onDidChangeEmitter.event;
// debounce in order to avoid to fire more than once in the same tick
protected fireOnDidChange = debounce(() => this.onDidChangeEmitter.fire(undefined), 0);

onStart(): void {
const contributions = this.contributionProvider.getContributions();
for (const contribution of contributions) {
Expand All @@ -245,6 +261,10 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
throw new Error(`A toolbar item is already registered with the '${id}' ID.`);
}
this.items.set(id, item);
this.fireOnDidChange();
if (item.onDidChange) {
item.onDidChange(() => this.fireOnDidChange());
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/browser/shell/tab-bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ export class ToolbarAwareTabBar extends ScrollableTabBar {

super(options);
this.rewireDOM();
this.tabBarToolbarRegistry.onDidChange(() => this.update());
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ body {
}

.theia-icon {
width: 18px;
width: 32px;
height: 18px;
margin: 5px;
margin-left: 8px;
Expand Down
Loading