Skip to content

Commit

Permalink
Address KJM feedback: better pinning and dragging
Browse files Browse the repository at this point in the history
Signed-off-by: Colin Grant <[email protected]>
  • Loading branch information
colin-grant-work committed May 26, 2021
1 parent 2f13d25 commit 75c61e8
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 26 deletions.
11 changes: 11 additions & 0 deletions packages/core/src/browser/shell/application-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,17 @@ export class ApplicationShell extends Widget {
return this.currentTabBar;
}

/**
* @returns the widget whose title has been targeted by a DOM event on a tabbar, or undefined if none can be found.
*/
findTargetedWidget(event?: Event): Widget | undefined {
if (event) {
const tab = this.findTabBar(event);
const title = tab && this.findTitle(tab, event);
return title && title.owner;
}
}

/**
* The current widget in the application shell. The current widget is the last widget that
* was active and not yet closed. See the remarks to `activeWidget` on what _active_ means.
Expand Down
71 changes: 71 additions & 0 deletions packages/editor-preview/src/browser/editor-preview-contribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/********************************************************************************
* Copyright (C) 2021 Ericsson 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 { ApplicationShell, KeybindingContribution, KeybindingRegistry, SHELL_TABBAR_CONTEXT_MENU, Widget } from '@theia/core/lib/browser';
import { Command, CommandContribution, CommandRegistry, MenuContribution, MenuModelRegistry } from '@theia/core/lib/common';
import { inject, injectable } from '@theia/core/shared/inversify';
import { EditorPreviewWidget } from './editor-preview-widget';

export namespace EditorPreviewCommands {
export const PIN_PREVIEW_COMMAND: Command = {
id: 'editor-preview-pin-editor',
label: 'Keep Open'
};
}

@injectable()
export class EditorPreviewContribution implements CommandContribution, MenuContribution, KeybindingContribution {
@inject(ApplicationShell) protected readonly shell: ApplicationShell;

registerCommands(registry: CommandRegistry): void {
registry.registerCommand(EditorPreviewCommands.PIN_PREVIEW_COMMAND, {
execute: async (event?: Event) => {
const widget = this.getTargetWidget(event);
if (widget instanceof EditorPreviewWidget) {
widget.convertToNonPreview();
await this.shell.activateWidget(widget.id);
}
},
isEnabled: (event?: Event) => {
const widget = this.getTargetWidget(event);
return widget instanceof EditorPreviewWidget && widget.isPreview;
},
isVisible: (event?: Event) => {
const widget = this.getTargetWidget(event);
return widget instanceof EditorPreviewWidget;
}
});
}

registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({
command: EditorPreviewCommands.PIN_PREVIEW_COMMAND.id,
keybinding: 'ctrlcmd+k enter'
});
}

registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(SHELL_TABBAR_CONTEXT_MENU, {
commandId: EditorPreviewCommands.PIN_PREVIEW_COMMAND.id,
label: EditorPreviewCommands.PIN_PREVIEW_COMMAND.label,
order: '6',
});
}

protected getTargetWidget(event?: Event): Widget | undefined {
return event ? this.shell.findTargetedWidget(event) : this.shell.activeWidget;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
********************************************************************************/

import '../../src/browser/style/index.css';
import { OpenHandler, WidgetFactory } from '@theia/core/lib/browser';
import { KeybindingContribution, OpenHandler, WidgetFactory } from '@theia/core/lib/browser';
import { ContainerModule } from '@theia/core/shared/inversify';
import { bindEditorPreviewPreferences } from './editor-preview-preferences';
import { EditorPreviewManager } from './editor-preview-manager';
import { EditorManager } from '@theia/editor/lib/browser';
import { EditorPreviewWidgetFactory } from './editor-preview-widget-factory';
import { EditorPreviewContribution } from './editor-preview-contribution';
import { CommandContribution, MenuContribution } from '@theia/core/lib/common';

export default new ContainerModule((bind, unbind, isBound, rebind) => {

Expand All @@ -31,5 +33,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(EditorManager).toService(EditorPreviewManager);
bind(OpenHandler).toService(EditorPreviewManager);

bind(EditorPreviewContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(EditorPreviewContribution);
bind(KeybindingContribution).toService(EditorPreviewContribution);
bind(MenuContribution).toService(EditorPreviewContribution);

bindEditorPreviewPreferences(bind);
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { EditorManager, EditorOpenerOptions, EditorWidget } from '@theia/editor/lib/browser';
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import { EditorPreviewPreferences } from './editor-preview-preferences';
import { NavigatableWidgetOptions, WidgetManager } from '@theia/core/lib/browser';
import { NavigatableWidgetOptions } from '@theia/core/lib/browser';
import { DisposableCollection, MaybePromise } from '@theia/core/lib/common';
import URI from '@theia/core/lib/common/uri';
import { EditorPreviewWidgetFactory } from './editor-preview-widget-factory';
Expand All @@ -32,7 +32,6 @@ export interface EditorPreviewOpenerOptions extends NavigatableWidgetOptions {
export class EditorPreviewManager extends EditorManager {
readonly id = EditorPreviewWidgetFactory.ID;

@inject(WidgetManager) protected readonly widgetManager: WidgetManager;
@inject(EditorPreviewPreferences) protected readonly preferences: EditorPreviewPreferences;
@inject(FrontendApplicationStateService) protected readonly stateService: FrontendApplicationStateService;

Expand Down
40 changes: 34 additions & 6 deletions packages/editor-preview/src/browser/editor-preview-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,34 @@
********************************************************************************/

import { Message } from '@theia/core/shared/@phosphor/messaging';
import { Widget } from '@theia/core/lib/browser';
import { EditorWidget } from '@theia/editor/lib/browser';
import { Emitter } from '@theia/core/lib/common';
import { DockPanel, TabBar, Widget } from '@theia/core/lib/browser';
import { EditorWidget, TextEditor } from '@theia/editor/lib/browser';
import { Disposable, DisposableCollection, Emitter, SelectionService } from '@theia/core/lib/common';
import { find } from '@theia/core/shared/@phosphor/algorithm';

const PREVIEW_TITLE_CLASS = 'theia-editor-preview-title-unpinned';
export class EditorPreviewWidget extends EditorWidget {
protected _isPreview = false;
protected lastParent: Widget | undefined;
protected lastTabbar: TabBar<Widget> | undefined;

protected readonly onDidChangePreviewStateEmitter = new Emitter<void>();
readonly onDidChangePreviewState = this.onDidChangePreviewStateEmitter.event;

protected readonly toDisposeOnLocationChange = new DisposableCollection();

get isPreview(): boolean {
return this._isPreview;
}

constructor(
readonly editor: TextEditor,
protected readonly selectionService: SelectionService
) {
super(editor, selectionService);
this.toDispose.push(this.onDidChangePreviewStateEmitter);
this.toDispose.push(this.toDisposeOnLocationChange);
}

initializePreview(): void {
this._isPreview = true;
this.title.className += ` ${PREVIEW_TITLE_CLASS}`;
Expand All @@ -44,18 +56,34 @@ export class EditorPreviewWidget extends EditorWidget {
convertToNonPreview(): void {
if (this._isPreview) {
this._isPreview = false;
this.toDisposeOnLocationChange.dispose();
this.lastTabbar = undefined;
this.title.className = this.title.className.replace(PREVIEW_TITLE_CLASS, '');
this.onDidChangePreviewStateEmitter.fire();
this.onDidChangePreviewStateEmitter.dispose();
}
}

protected onAfterAttach(msg: Message): void {
super.onAfterAttach(msg);
if (this._isPreview) {
if (this.lastParent && this.parent !== this.lastParent) {
this.checkForTabbarChange();
}
}

protected checkForTabbarChange(): void {
const { parent } = this;
if (parent instanceof DockPanel) {
this.toDisposeOnLocationChange.dispose();
const newTabbar = find(parent.tabBars(), tabbar => !!tabbar.titles.find(title => title === this.title));
if (this.lastTabbar && this.lastTabbar !== newTabbar) {
this.convertToNonPreview();
} else {
this.lastTabbar = newTabbar;
const listener = () => this.checkForTabbarChange();
parent.layoutModified.connect(listener);
this.toDisposeOnLocationChange.push(Disposable.create(() => parent.layoutModified.disconnect(listener)));
}
this.lastParent = this.parent ?? undefined;
}
}

Expand Down
20 changes: 3 additions & 17 deletions packages/navigator/src/browser/navigator-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
SelectableTreeNode,
SHELL_TABBAR_CONTEXT_MENU,
Widget,
Title
} from '@theia/core/lib/browser';
import { FileDownloadCommands } from '@theia/filesystem/lib/browser/download/file-download-command-contribution';
import {
Expand Down Expand Up @@ -265,15 +264,15 @@ export class FileNavigatorContribution extends AbstractViewContribution<FileNavi
});
registry.registerCommand(FileNavigatorCommands.REVEAL_IN_NAVIGATOR, {
execute: (event?: Event) => {
const widget = this.findTargetedWidget(event);
const widget = this.shell.findTargetedWidget(event);
this.openView({ activate: true }).then(() => this.selectWidgetFileNode(widget || this.shell.currentWidget));
},
isEnabled: (event?: Event) => {
const widget = this.findTargetedWidget(event);
const widget = this.shell.findTargetedWidget(event);
return widget ? Navigatable.is(widget) : Navigatable.is(this.shell.currentWidget);
},
isVisible: (event?: Event) => {
const widget = this.findTargetedWidget(event);
const widget = this.shell.findTargetedWidget(event);
return widget ? Navigatable.is(widget) : Navigatable.is(this.shell.currentWidget);
}
});
Expand Down Expand Up @@ -558,19 +557,6 @@ export class FileNavigatorContribution extends AbstractViewContribution<FileNavi
this.tabbarToolbarRegistry.registerItem(item);
};

/**
* Find the selected widget.
* @returns `widget` of the respective `title` if it exists, else returns undefined.
*/
private findTargetedWidget(event?: Event): Widget | undefined {
let title: Title<Widget> | undefined;
if (event) {
const tab = this.shell.findTabBar(event);
title = tab && this.shell.findTitle(tab, event);
}
return title && title.owner;
}

/**
* Reveals and selects node in the file navigator to which given widget is related.
* Does nothing if given widget undefined or doesn't have related resource.
Expand Down

0 comments on commit 75c61e8

Please sign in to comment.