Skip to content

Commit

Permalink
[webview] fix #5786: unify the icon path resolution
Browse files Browse the repository at this point in the history
Also:
- preserve the icon path between user sessions and reconnections
- add missing icon-path getter api

Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Nov 7, 2019
1 parent 81eb6e5 commit f0a78d8
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 67 deletions.
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1245,7 +1245,7 @@ export interface WebviewsMain {
$disposeWebview(handle: string): void;
$reveal(handle: string, showOptions: theia.WebviewPanelShowOptions): void;
$setTitle(handle: string, value: string): void;
$setIconPath(handle: string, value: { light: string, dark: string } | string | undefined): void;
$setIconPath(handle: string, value: IconUrl | undefined): void;
$setHtml(handle: string, value: string): void;
$setOptions(handle: string, options: theia.WebviewOptions): void;
$postMessage(handle: string, value: any): Thenable<boolean>;
Expand Down
3 changes: 1 addition & 2 deletions packages/plugin-ext/src/main/browser/plugin-shared-style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,8 @@ export class PluginSharedStyle {
}): void {
const sheet = (<CSSStyleSheet>this.style.sheet);
const cssBody = body(ThemeService.get().getCurrentTheme());
sheet.insertRule(selector + ' { ' + cssBody + ' }', 0);
sheet.insertRule(selector + ' {\n' + cssBody + '\n}', 0);
}

deleteRule(selector: string): void {
const sheet = (<CSSStyleSheet>this.style.sheet);
const rules = sheet.rules || sheet.cssRules || [];
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-ext/src/main/browser/style/webview.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
border: none; margin: 0; padding: 0;
}

.webview-icon {
.theia-webview-icon {
background: none !important;
min-height: 20px;
}

.webview-icon::before {
.theia-webview-icon::before {
background-size: 13px;
background-repeat: no-repeat;
vertical-align: middle;
Expand All @@ -41,7 +41,7 @@
content: "";
}

.p-TabBar.theia-app-sides .webview-icon::before {
.p-TabBar.theia-app-sides .theia-webview-icon::before {
width: var(--theia-private-sidebar-icon-size);
height: var(--theia-private-sidebar-icon-size);
background-size: contain;
Expand Down
39 changes: 33 additions & 6 deletions packages/plugin-ext/src/main/browser/webview/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@
********************************************************************************/

import * as mime from 'mime';
import { JSONExt } from '@phosphor/coreutils/lib/json';
import { injectable, inject, postConstruct } from 'inversify';
import { WebviewPanelOptions, WebviewPortMapping } from '@theia/plugin';
import { BaseWidget, Message } from '@theia/core/lib/browser/widgets/widget';
import { Disposable } from '@theia/core/lib/common/disposable';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
// TODO: get rid of dependencies to the mini browser
import { MiniBrowserContentStyle } from '@theia/mini-browser/lib/browser/mini-browser-content-style';
import { ApplicationShellMouseTracker } from '@theia/core/lib/browser/shell/application-shell-mouse-tracker';
import { StatefulWidget } from '@theia/core/lib/browser/shell/shell-layout-restorer';
import { WebviewPanelViewState } from '../../../common/plugin-api-rpc';
import { IconUrl } from '../../../common/plugin-protocol';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { WebviewEnvironment } from './webview-environment';
import URI from '@theia/core/lib/common/uri';
Expand All @@ -32,7 +34,8 @@ import { Emitter } from '@theia/core/lib/common/event';
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
import { Schemes } from '../../../common/uri-components';
import { JSONExt } from '@phosphor/coreutils';
import { PluginSharedStyle } from '../plugin-shared-style';
import { BuiltinThemeProvider } from '@theia/core/lib/browser/theming';

// tslint:disable:no-any

Expand Down Expand Up @@ -97,6 +100,9 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
@inject(KeybindingRegistry)
protected readonly keybindings: KeybindingRegistry;

@inject(PluginSharedStyle)
protected readonly sharedStyle: PluginSharedStyle;

viewState: WebviewPanelViewState = {
visible: false,
active: false,
Expand Down Expand Up @@ -212,8 +218,27 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
this.doUpdateContent();
}

setIconClass(iconClass: string): void {
this.title.iconClass = iconClass;
protected iconUrl: IconUrl | undefined;
protected readonly toDisposeOnIcon = new DisposableCollection();
setIconUrl(iconUrl: IconUrl | undefined): void {
if ((this.iconUrl && iconUrl && JSONExt.deepEqual(this.iconUrl, iconUrl)) || (this.iconUrl === iconUrl)) {
return;
}
this.toDisposeOnIcon.dispose();
this.toDispose.push(this.toDisposeOnIcon);
this.iconUrl = iconUrl;
if (iconUrl) {
const darkIconUrl = typeof iconUrl === 'object' ? iconUrl.dark : iconUrl;
const lightIconUrl = typeof iconUrl === 'object' ? iconUrl.light : iconUrl;
const iconClass = `${this.identifier.id}-file-icon`;
this.toDisposeOnIcon.push(this.sharedStyle.insertRule(
`.theia-webview-icon.${iconClass}::before`,
theme => `background-image: url(${theme.id === BuiltinThemeProvider.lightTheme.id ? lightIconUrl : darkIconUrl});`
));
this.title.iconClass = `theia-webview-icon ${iconClass}`;
} else {
this.title.iconClass = '';
}
}

setHTML(value: string): void {
Expand Down Expand Up @@ -330,16 +355,18 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
return {
viewType: this.viewType,
title: this.title.label,
iconUrl: this.iconUrl,
options: this.options,
contentOptions: this.contentOptions,
state: this.state
};
}

restoreState(oldState: WebviewWidget.State): void {
const { viewType, title, options, contentOptions, state } = oldState;
const { viewType, title, iconUrl, options, contentOptions, state } = oldState;
this.viewType = viewType;
this.title.label = title;
this.setIconUrl(iconUrl);
this.options = options;
this._contentOptions = contentOptions;
this._state = state;
Expand Down Expand Up @@ -383,9 +410,9 @@ export namespace WebviewWidget {
export interface State {
viewType: string
title: string
iconUrl?: IconUrl
options: WebviewPanelOptions
contentOptions: WebviewContentOptions
state: any
// TODO: preserve icon class
}
}
5 changes: 3 additions & 2 deletions packages/plugin-ext/src/main/browser/webviews-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { WidgetManager } from '@theia/core/lib/browser/widget-manager';
import { JSONExt } from '@phosphor/coreutils/lib/json';
import { Mutable } from '@theia/core/lib/common/types';
import { HostedPluginSupport } from '../../hosted/browser/hosted-plugin';
import { IconUrl } from '../../common/plugin-protocol';

export class WebviewsMainImpl implements WebviewsMain, Disposable {

Expand Down Expand Up @@ -159,9 +160,9 @@ export class WebviewsMainImpl implements WebviewsMain, Disposable {
webview.title.label = value;
}

async $setIconPath(handle: string, iconPath: { light: string; dark: string; } | string | undefined): Promise<void> {
async $setIconPath(handle: string, iconUrl: IconUrl | undefined): Promise<void> {
const webview = await this.getWebview(handle);
webview.setIconClass(iconPath ? `webview-icon ${handle}-file-icon` : '');
webview.setIconUrl(iconUrl);
}

async $setHtml(handle: string, value: string): Promise<void> {
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,10 @@ export function createAPIFactory(
title: string,
showOptions: theia.ViewColumn | theia.WebviewPanelShowOptions,
options: theia.WebviewPanelOptions & theia.WebviewOptions = {}): theia.WebviewPanel {
return webviewExt.createWebview(viewType, title, showOptions, options, Uri.file(plugin.pluginPath));
return webviewExt.createWebview(viewType, title, showOptions, options, plugin);
},
registerWebviewPanelSerializer(viewType: string, serializer: theia.WebviewPanelSerializer): theia.Disposable {
return webviewExt.registerWebviewPanelSerializer(viewType, serializer, Uri.file(plugin.pluginPath));
return webviewExt.registerWebviewPanelSerializer(viewType, serializer, plugin);
},
get state(): theia.WindowState {
return windowStateExt.getWindowState();
Expand Down
50 changes: 50 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-icon-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/********************************************************************************
* 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 * as path from 'path';
import Uri from 'vscode-uri';
import { IconUrl, PluginPackage } from '../common/plugin-protocol';
import { Plugin } from '../common/plugin-api-rpc';

export type PluginIconPath = string | Uri | {
light: string | Uri,
dark: string | Uri
};
export namespace PluginIconPath {
export function toUrl(iconPath: PluginIconPath | undefined, plugin: Plugin): IconUrl | undefined {
if (!iconPath) {
return undefined;
}
if (typeof iconPath === 'object' && 'light' in iconPath) {
return {
light: asString(iconPath.light, plugin),
dark: asString(iconPath.dark, plugin)
};
}
return asString(iconPath, plugin);
}
export function asString(arg: string | Uri, plugin: Plugin): string {
arg = arg instanceof Uri && arg.scheme === 'file' ? arg.fsPath : arg;
if (typeof arg !== 'string') {
return arg.toString(true);
}
const { packagePath } = plugin.rawModel;
const absolutePath = path.isAbsolute(arg) ? arg : path.join(packagePath, arg);
const normalizedPath = path.normalize(absolutePath);
const relativePath = path.relative(packagePath, normalizedPath);
return PluginPackage.toPluginUrl(plugin.rawModel, relativePath);
}
}
35 changes: 7 additions & 28 deletions packages/plugin-ext/src/plugin/tree/tree-views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

// tslint:disable:no-any

import * as path from 'path';
import URI from 'vscode-uri';
import {
TreeDataProvider, TreeView, TreeViewExpansionEvent, TreeItem2, TreeItemLabel,
TreeViewSelectionChangeEvent, TreeViewVisibilityChangeEvent
Expand All @@ -31,7 +29,7 @@ import { Plugin, PLUGIN_RPC_CONTEXT, TreeViewsExt, TreeViewsMain, TreeViewItem }
import { RPCProtocol } from '../../common/rpc-protocol';
import { CommandRegistryImpl, CommandsConverter } from '../command-registry';
import { TreeViewSelection } from '../../common';
import { PluginPackage } from '../../common/plugin-protocol';
import { PluginIconPath } from '../plugin-icon-path';

export class TreeViewsExtImpl implements TreeViewsExt {

Expand Down Expand Up @@ -279,31 +277,12 @@ class TreeViewExtImpl<T> implements Disposable {
let iconUrl;
let themeIconId;
const { iconPath } = treeItem;
if (iconPath) {
const toUrl = (arg: string | URI) => {
arg = arg instanceof URI && arg.scheme === 'file' ? arg.fsPath : arg;
if (typeof arg !== 'string') {
return arg.toString(true);
}
const { packagePath } = this.plugin.rawModel;
const absolutePath = path.isAbsolute(arg) ? arg : path.join(packagePath, arg);
const normalizedPath = path.normalize(absolutePath);
const relativePath = path.relative(packagePath, normalizedPath);
return PluginPackage.toPluginUrl(this.plugin.rawModel, relativePath);
};
if (typeof iconPath === 'string' && iconPath.indexOf('fa-') !== -1) {
icon = iconPath;
} else if (iconPath instanceof ThemeIcon) {
themeIconId = iconPath.id;
} else if (typeof iconPath === 'string' || iconPath instanceof URI) {
iconUrl = toUrl(iconPath);
} else {
const { light, dark } = iconPath as { light: string | URI, dark: string | URI };
iconUrl = {
light: toUrl(light),
dark: toUrl(dark)
};
}
if (typeof iconPath === 'string' && iconPath.indexOf('fa-') !== -1) {
icon = iconPath;
} else if (iconPath instanceof ThemeIcon) {
themeIconId = iconPath.id;
} else {
iconUrl = PluginIconPath.toUrl(<PluginIconPath | undefined>iconPath, this.plugin);
}

const treeViewItem = {
Expand Down
Loading

0 comments on commit f0a78d8

Please sign in to comment.