Skip to content

Commit

Permalink
CHE-3896 add the needed methods for Webview API
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksii Orel <oorel@redhat.com>
  • Loading branch information
olexii4 committed Apr 12, 2019
1 parent 26b1a35 commit a004b00
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { DebugService } from '@theia/debug/lib/common/debug-service';
import { PluginSharedStyle } from './plugin-shared-style';
import { FSResourceResolver } from './file-system-main';
import { SelectionProviderCommandContribution } from './selection-provider-command';
import { ViewColumnService } from './view-column-service';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bindHostedPluginPreferences(bind);
Expand Down Expand Up @@ -139,4 +140,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(DebugService).toService(PluginDebugService);
bind(PluginDebugSessionContributionRegistry).toSelf().inSingletonScope();
rebind(DebugSessionContributionRegistry).toService(PluginDebugSessionContributionRegistry);

bind(ViewColumnService).toSelf().inSingletonScope();
});
82 changes: 82 additions & 0 deletions packages/plugin-ext/src/main/browser/view-column-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/********************************************************************************
* Copyright (C) 2019 Red Hat, Inc. 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 { injectable, inject } from 'inversify';
import { Emitter, Event } from '@theia/core';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';

@injectable()
export class ViewColumnService {
private panelNode: HTMLElement;
private viewColumns = new Map<string, number>();
private viewColumnIds = new Map<number, string[]>();

constructor(
@inject(ApplicationShell) private readonly shell: ApplicationShell,
) {
this.panelNode = this.shell.mainPanel.node;
// this.updateViewColumnIds();

this.panelNode.addEventListener('p-drop', async () => {
await new Promise((resolve => setTimeout(() => resolve())));
this.updateViewColumnIds();
const viewColumns = new Map<string, number>();
this.viewColumnIds.forEach((ids: string[], viewColumn: number) => {
ids.forEach((id: string) => {
viewColumns.set(id, viewColumn);
if (this.viewColumns.has(id) && this.viewColumns.get(id) !== viewColumn) {
this.onViewColumnChangedEmitter.fire({ id, viewColumn });
}
});
});
this.viewColumns = viewColumns;
}, true);
}

updateViewColumnIds(): void {
const dockPanelElements = this.panelNode.getElementsByClassName('p-DockPanel-widget');
const positionIds = new Map<number, string[]>();
for (let i = 0; i < dockPanelElements.length; i++) {
// tslint:disable-next-line:no-any
const dockPanel = <any>dockPanelElements[i];
if (dockPanel && dockPanel.style && dockPanel.style.left) {
const pos = parseInt(dockPanel.style.left);
if (!positionIds.has(pos)) {
positionIds.set(pos, []);
}
positionIds.get(pos)!.push(dockPanelElements[i].id);
}
}
this.viewColumnIds.clear();
[...positionIds.keys()].sort().forEach((key: number, viewColumn: number) => {
positionIds.get(key)!.forEach((id: string) => {
if (!this.viewColumnIds.has(viewColumn)) {
this.viewColumnIds.set(viewColumn, []);
}
this.viewColumnIds.get(viewColumn)!.push(id);
});
});
}

getViewColumnIds(viewColumn: number): string[] | undefined {
return this.viewColumnIds.get(viewColumn);
}

protected readonly onViewColumnChangedEmitter = new Emitter<{ id: string, viewColumn: number }>();
get onViewColumnChanged(): Event<{ id: string, viewColumn: number }> {
return this.onViewColumnChangedEmitter.event;
}
}
37 changes: 34 additions & 3 deletions packages/plugin-ext/src/main/browser/webview/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,18 @@ export class WebviewWidget extends BaseWidget {
private static readonly ID = new IdGenerator('webview-widget-');
protected readonly toDispose = new DisposableCollection();
private iframe: HTMLIFrameElement;
private state: string | undefined = undefined;
private name: string;
private group: number | undefined;
private state: { [key: string]: any } | undefined = undefined;
private loadTimeout: number | undefined;

constructor(title: string, private options: WebviewWidgetOptions, private eventDelegate: WebviewEvents) {
constructor(name: string, private options: WebviewWidgetOptions, private eventDelegate: WebviewEvents) {
super();
this.node.tabIndex = 0;
this.id = WebviewWidget.ID.nextId();
this.name = name;
this.title.closable = true;
this.title.label = title;
this.title.label = this.name;
this.addClass(WebviewWidget.Styles.WEBVIEW);
}

Expand Down Expand Up @@ -72,6 +75,34 @@ export class WebviewWidget extends BaseWidget {
this.title.iconClass = iconClass;
}

getOptions(): WebviewWidgetOptions {
return this.options;
}

getName(): string {
return this.name;
}

getTitle() {
return this.getName();
}

getState(): { [key: string]: any } | undefined {
return this.state;
}

setState(state: { [key: string]: any } | undefined) {
this.state = state;
}

getGroup(): number | undefined {
return this.group;
}

public updateGroup(value: number | undefined): void {
this.group = value;
}

setHTML(html: string) {
html = html.replace(/theia-resource:/g, '/webview/');
const newDocument = new DOMParser().parseFromString(html, 'text/html');
Expand Down
100 changes: 97 additions & 3 deletions packages/plugin-ext/src/main/browser/webviews-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,49 @@ import { WebviewWidget } from './webview/webview';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { ThemeRulesService } from './webview/theme-rules-service';
import { DisposableCollection } from '@theia/core';
import { ViewColumnService } from './view-column-service';

export class WebviewsMainImpl implements WebviewsMain {
export interface WebviewReviver {
canRevive(webview: WebviewWidget): boolean;
reviveWebview(webview: WebviewWidget): Thenable<void>;
}

export class WebviewsMainImpl implements WebviewsMain, WebviewReviver {
private static revivalPool = 0;

private readonly revivers = new Set<string>();
private readonly proxy: WebviewsExt;
protected readonly shell: ApplicationShell;
protected readonly viewColumnService: ViewColumnService;
protected readonly keybindingRegistry: KeybindingRegistry;
protected readonly themeService = ThemeService.get();
protected readonly themeRulesService = ThemeRulesService.get();

private readonly views = new Map<string, WebviewWidget>();
private readonly viewsPanelOptions = new Map<string, { panelOptions: WebviewPanelShowOptions; panelId: string; active: boolean; visible: boolean }>();

constructor(rpc: RPCProtocol, container: interfaces.Container) {
this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.WEBVIEWS_EXT);
this.shell = container.get(ApplicationShell);
this.keybindingRegistry = container.get(KeybindingRegistry);
this.viewColumnService = container.get(ViewColumnService);

this.viewColumnService.onViewColumnChanged(e => {
this.updatePanelViewState(e.id, e.viewColumn);
});
let timeoutHandle: NodeJS.Timer | undefined;
const updateOptions = () => {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
timeoutHandle = setTimeout(() => {
for (const key of this.viewsPanelOptions.keys()) {
this.updatePanelViewState(key);
}
}, 100);
};
this.shell.activeChanged.connect(() => updateOptions());
this.shell.currentChanged.connect(() => updateOptions());
}

$createWebviewPanel(
Expand Down Expand Up @@ -85,13 +114,26 @@ export class WebviewsMainImpl implements WebviewsMain {
this.onCloseView(viewId);
});
this.views.set(viewId, view);
this.viewsPanelOptions.set(view.node.id, { panelOptions: showOptions, panelId: viewId, active: true, visible: true });
const widgetOptions: ApplicationShell.WidgetOptions = { area: showOptions.area ? showOptions.area : 'main' };
// const activeId = this.shell.currentWidget;
// FIXME translate all view columns properly
if (showOptions.viewColumn === -2) {
const ref = this.shell.currentWidget;
if (ref && this.shell.getAreaFor(ref) === widgetOptions.area) {
Object.assign(widgetOptions, { ref, mode: 'open-to-right' });
}
} else if (widgetOptions.area === 'main' && showOptions.viewColumn !== undefined) {
this.viewColumnService.updateViewColumnIds();
const columnIds = this.viewColumnService.getViewColumnIds(showOptions.viewColumn);
if (columnIds && columnIds[0]) {
const id = columnIds[0];
const widgets = this.shell.getWidgets(widgetOptions.area);
const targetWidget = widgets.find(widget => widget.node.id === id);
if (targetWidget) {
Object.assign(widgetOptions, { targetWidget });
}
}
}
this.shell.addWidget(view, widgetOptions);
if (showOptions.preserveFocus) {
Expand Down Expand Up @@ -144,10 +186,61 @@ export class WebviewsMainImpl implements WebviewsMain {
return Promise.resolve(webview !== undefined);
}
$registerSerializer(viewType: string): void {
throw new Error('Method not implemented.');
this.revivers.add(viewType);
}
$unregisterSerializer(viewType: string): void {
throw new Error('Method not implemented.');
this.revivers.delete(viewType);
}

canRevive(webview: WebviewWidget): boolean {
if (webview.isDisposed || !webview.getState() || !webview.getState()!.viewType) {
return false;
}

return !this.revivers.has(webview.getState()!.viewType);
}

reviveWebview(webview: WebviewWidget): Thenable<void> {
const viewType = webview.getState() ? webview.getState()!.viewType : undefined;
return Promise.resolve().then(() => {
const handle = 'revival-' + WebviewsMainImpl.revivalPool++;
this.views.set(handle, webview);

let state = undefined;
if (webview.getState() && webview.getState()!.state) {
try {
state = JSON.parse(webview.getState()!.state);
} catch {
// noop
}
}
const group = webview.getGroup() ? <number>webview.getGroup() : -2;
const options = <WebviewPanelOptions & WebviewOptions>webview.getOptions();
return this.proxy.$deserializeWebviewPanel(handle, viewType, webview.getTitle(), state, group, options).then(undefined, () => {
webview.setHTML(`<!DOCTYPE html><html><head><meta http-equiv="Content-type" content="text/html;charset=UTF-8"></head>
<body>An error occurred while restoring view:${viewType}</body></html>`);
});
});
}

private updatePanelViewState(handler: string, position?: number): void {
const option = this.viewsPanelOptions.get(handler);
if (!option || !option.panelOptions) {
return;
}
const view = this.views.get(option.panelId);
const active: boolean = !!this.shell.activeWidget && !!view && (this.shell.activeWidget.id === view.id);
const visible = !!view && view.isVisible;
if ((position === undefined || option.panelOptions.viewColumn === position) && option.visible === visible && option.active === active) {
return;
}
if (position !== undefined) {
option.panelOptions.viewColumn = position;
}
option.active = active;
option.visible = visible;
this.viewsPanelOptions.set(handler, option);
this.proxy.$onDidChangeWebviewPanelViewState(option.panelId, { active, visible, position: <number>option.panelOptions.viewColumn });
}

private getWebview(viewId: string): WebviewWidget {
Expand All @@ -165,6 +258,7 @@ export class WebviewsMainImpl implements WebviewsMain {
}
const cleanUp = () => {
this.views.delete(viewId);
this.viewsPanelOptions.delete(viewId);
};
this.proxy.$onDidDisposeWebviewPanel(viewId).then(cleanUp, cleanUp);
}
Expand Down

0 comments on commit a004b00

Please sign in to comment.