Skip to content

Commit

Permalink
Implemented Toolbar support for sidepanels and changed sidepanel tabs.
Browse files Browse the repository at this point in the history
Every sidepanel has an header now where the title and a toolbar
is shown (if tools are registered for that widget).
As an example the control buttons of search in workspace extension are
moved to the toolbar.

Additionally the tabs of sidebars  don't show labels anymore but icons.

Fixes #3864

Signed-off-by: Jan Bicker <[email protected]>
  • Loading branch information
jbicker authored and akosyakov committed Mar 20, 2019
1 parent d03bddc commit 3340832
Show file tree
Hide file tree
Showing 30 changed files with 438 additions and 125 deletions.
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
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

0 comments on commit 3340832

Please sign in to comment.