Skip to content

Commit 3d4501a

Browse files
evidolobolexii4
authored andcommitted
initial webview implementation
Signed-off-by: Yevhen Vydolob <[email protected]> add theme styles into webview Signed-off-by: Oleksii Orel <[email protected]> add keyboard events into webview Signed-off-by: Oleksii Orel <[email protected]> implement setOptions for webview Signed-off-by: Oleksii Orel <[email protected]> implement setIconPath for webview Signed-off-by: Oleksii Orel <[email protected]> add an implementation for acquireTheiaApi Signed-off-by: Oleksii Orel <[email protected]>
1 parent 9fe8aa6 commit 3d4501a

File tree

16 files changed

+1245
-3
lines changed

16 files changed

+1245
-3
lines changed

dev-packages/application-manager/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"circular-dependency-plugin": "^5.0.0",
3636
"copy-webpack-plugin": "^4.5.0",
3737
"css-loader": "^0.28.1",
38+
"style-loader": "^0.23.1",
3839
"electron": "1.8.2-beta.5",
3940
"electron-rebuild": "^1.5.11",
4041
"file-loader": "^1.1.11",

dev-packages/application-manager/src/generator/webpack-generator.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,16 @@ module.exports = {
8383
},
8484
{
8585
test: /\\.useable\\.css$/,
86-
loader: 'style-loader/useable!css-loader'
86+
use: [
87+
{
88+
loader: 'style-loader/useable',
89+
options: {
90+
singleton: true,
91+
attrs: { id: 'theia-theme' },
92+
}
93+
},
94+
'css-loader'
95+
]
8796
},
8897
{
8998
test: /\\.(ttf|eot|svg)(\\?v=\\d+\\.\\d+\\.\\d+)?$/,

packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,32 @@ export const doInitialization: BackendInitializationFn = (apiFactory: PluginAPIF
4343
}
4444

4545
// replace command API as it will send only the ID as a string parameter
46-
vscode.commands.registerCommand = function registerCommand(command: any, handler?: <T>(...args: any[]) => T | Thenable<T>): any {
46+
const registerCommand = vscode.commands.registerCommand;
47+
vscode.commands.registerCommand = function (command: any, handler?: <T>(...args: any[]) => T | Thenable<T>): any {
4748
// use of the ID when registering commands
4849
if (typeof command === 'string' && handler) {
4950
return vscode.commands.registerHandler(command, handler);
5051
}
52+
return registerCommand(command, handler);
53+
};
54+
55+
// replace createWebviewPanel API for override html setter
56+
const createWebviewPanel = vscode.window.createWebviewPanel;
57+
vscode.window.createWebviewPanel = function (viewType: string, title: string, showOptions: any, options: any | undefined): any {
58+
const panel = createWebviewPanel(viewType, title, showOptions, options);
59+
// redefine property
60+
Object.defineProperty(panel.webview, 'html', {
61+
set: function (html: string) {
62+
const newHtml = html.replace('vscode-resource:/', '/webview/');
63+
this.checkIsDisposed();
64+
if (this._html !== newHtml) {
65+
this._html = newHtml;
66+
this.proxy.$setHtml(this.viewId, newHtml);
67+
}
68+
}
69+
});
70+
71+
return panel;
5172
};
5273

5374
// use Theia plugin api instead vscode extensions

packages/plugin-ext/src/api/plugin-api.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,48 @@ export interface LanguagesMain {
820820
$registerOutlineSupport(handle: number, selector: SerializedDocumentFilter[]): void;
821821
}
822822

823+
export interface WebviewPanelViewState {
824+
readonly active: boolean;
825+
readonly visible: boolean;
826+
readonly position: number;
827+
}
828+
829+
export interface WebviewPanelShowOptions {
830+
readonly viewColumn?: number;
831+
readonly preserveFocus?: boolean;
832+
}
833+
834+
export interface WebviewsExt {
835+
$onMessage(handle: string, message: any): void;
836+
$onDidChangeWebviewPanelViewState(handle: string, newState: WebviewPanelViewState): void;
837+
$onDidDisposeWebviewPanel(handle: string): PromiseLike<void>;
838+
$deserializeWebviewPanel(newWebviewHandle: string,
839+
viewType: string,
840+
title: string,
841+
state: any,
842+
position: number,
843+
options: theia.WebviewOptions & theia.WebviewPanelOptions): PromiseLike<void>;
844+
}
845+
846+
export interface WebviewsMain {
847+
$createWebviewPanel(handle: string,
848+
viewType: string,
849+
title: string,
850+
showOptions: WebviewPanelShowOptions,
851+
options: theia.WebviewPanelOptions & theia.WebviewOptions | undefined,
852+
pluginLocation: UriComponents): void;
853+
$disposeWebview(handle: string): void;
854+
$reveal(handle: string, showOptions: WebviewPanelShowOptions): void;
855+
$setTitle(handle: string, value: string): void;
856+
$setIconPath(handle: string, value: { light: string, dark: string } | string | undefined): void;
857+
$setHtml(handle: string, value: string): void;
858+
$setOptions(handle: string, options: theia.WebviewOptions): void;
859+
$postMessage(handle: string, value: any): Thenable<boolean>;
860+
861+
$registerSerializer(viewType: string): void;
862+
$unregisterSerializer(viewType: string): void;
863+
}
864+
823865
export const PLUGIN_RPC_CONTEXT = {
824866
COMMAND_REGISTRY_MAIN: <ProxyIdentifier<CommandRegistryMain>>createProxyIdentifier<CommandRegistryMain>('CommandRegistryMain'),
825867
QUICK_OPEN_MAIN: createProxyIdentifier<QuickOpenMain>('QuickOpenMain'),
@@ -837,6 +879,7 @@ export const PLUGIN_RPC_CONTEXT = {
837879
OUTPUT_CHANNEL_REGISTRY_MAIN: <ProxyIdentifier<OutputChannelRegistryMain>>createProxyIdentifier<OutputChannelRegistryMain>('OutputChannelRegistryMain'),
838880
LANGUAGES_MAIN: createProxyIdentifier<LanguagesMain>('LanguagesMain'),
839881
CONNECTION_MAIN: createProxyIdentifier<ConnectionMain>('ConnectionMain'),
882+
WEBVIEWS_MAIN: createProxyIdentifier<WebviewsMain>('WebviewsMain'),
840883
};
841884

842885
export const MAIN_RPC_CONTEXT = {
@@ -854,4 +897,5 @@ export const MAIN_RPC_CONTEXT = {
854897
PREFERENCE_REGISTRY_EXT: createProxyIdentifier<PreferenceRegistryExt>('PreferenceRegistryExt'),
855898
LANGUAGES_EXT: createProxyIdentifier<LanguagesExt>('LanguagesExt'),
856899
CONNECTION_EXT: createProxyIdentifier<ConnectionExt>('ConnectionExt'),
900+
WEBVIEWS_EXT: createProxyIdentifier<WebviewsExt>('WebviewsExt'),
857901
};

packages/plugin-ext/src/main/browser/main-context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { DialogsMainImpl } from './dialogs-main';
3232
import { TreeViewsMainImpl } from './view/tree-views-main';
3333
import { NotificationMainImpl } from './notification-main';
3434
import { ConnectionMainImpl } from './connection-main';
35+
import { WebviewsMainImpl } from './webviews-main';
3536

3637
export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void {
3738
const commandRegistryMain = new CommandRegistryMainImpl(rpc, container);
@@ -78,6 +79,9 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container
7879
const languagesMain = new LanguagesMainImpl(rpc);
7980
rpc.set(PLUGIN_RPC_CONTEXT.LANGUAGES_MAIN, languagesMain);
8081

82+
const webviewsMain = new WebviewsMainImpl(rpc, container);
83+
rpc.set(PLUGIN_RPC_CONTEXT.WEBVIEWS_MAIN, webviewsMain);
84+
8185
const pluginConnection = new ConnectionMainImpl(rpc);
8286
rpc.set(PLUGIN_RPC_CONTEXT.CONNECTION_MAIN, pluginConnection);
8387
}

packages/plugin-ext/src/main/browser/style/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,5 @@
5252

5353
@import './plugin-sidebar.css';
5454
@import './view-registry.css';
55+
@import './webview.css';
5556
@import './tree.css';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/********************************************************************************
2+
* Copyright (C) 2018 Red Hat, Inc. and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
17+
.theia-webview {
18+
display: flex;
19+
flex-direction: column;
20+
height: 100%;
21+
}
22+
23+
.theia-webview iframe {
24+
flex-grow: 1;
25+
border: none; margin: 0; padding: 0;
26+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/********************************************************************************
2+
* Copyright (C) 2018 Red Hat, Inc. and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
17+
import { ThemeService } from '@theia/core/lib/browser/theming';
18+
19+
export const ThemeRulesServiceSymbol = Symbol('ThemeRulesService');
20+
21+
interface IconPath {
22+
light: string,
23+
dark: string
24+
}
25+
26+
export class ThemeRulesService {
27+
private styleElement?: HTMLStyleElement;
28+
private icons = new Map<string, IconPath | string>();
29+
protected readonly themeService = ThemeService.get();
30+
protected readonly themeRules = new Map<string, string[]>();
31+
32+
static get(): ThemeRulesService {
33+
const global = window as any; // tslint:disable-line
34+
return global[ThemeRulesServiceSymbol] || new ThemeRulesService();
35+
}
36+
37+
protected constructor() {
38+
const global = window as any; // tslint:disable-line
39+
global[ThemeRulesServiceSymbol] = this;
40+
41+
this.themeService.onThemeChange(() => {
42+
this.updateIconStyleElement();
43+
});
44+
}
45+
46+
createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLStyleElement {
47+
const style = document.createElement('style');
48+
style.type = 'text/css';
49+
style.media = 'screen';
50+
container.appendChild(style);
51+
return style;
52+
}
53+
54+
getCurrentThemeRules(): string[] {
55+
const cssText: string[] = [];
56+
const themeId = this.themeService.getCurrentTheme().id;
57+
if (this.themeRules.has(themeId)) {
58+
return <string[]>this.themeRules.get(themeId);
59+
}
60+
const styleElement = document.getElementById('theia-theme') as any;
61+
if (!styleElement) {
62+
return cssText;
63+
}
64+
65+
const sheet: {
66+
insertRule: (rule: string, index: number) => void,
67+
removeRule: (index: number) => void,
68+
rules: CSSRuleList
69+
} | undefined = (<any>styleElement).sheet;
70+
if (!sheet || !sheet.rules || !sheet.rules.length) {
71+
return cssText;
72+
}
73+
74+
const ruleList = sheet.rules;
75+
for (let index = 0; index < ruleList.length; index++) {
76+
if (ruleList[index] && ruleList[index].cssText) {
77+
cssText.push(ruleList[index].cssText.toString());
78+
}
79+
}
80+
81+
return cssText;
82+
}
83+
84+
setRules(styleSheet: HTMLElement, newRules: string[]): boolean {
85+
const sheet: {
86+
insertRule: (rule: string, index: number) => void;
87+
removeRule: (index: number) => void;
88+
rules: CSSRuleList;
89+
} | undefined = (<any>styleSheet).sheet;
90+
91+
if (!sheet) {
92+
return false;
93+
}
94+
for (let index = sheet.rules!.length; index > 0; index--) {
95+
sheet.removeRule(0);
96+
}
97+
newRules.forEach((rule: string, index: number) => {
98+
sheet.insertRule(rule, index);
99+
});
100+
return true;
101+
}
102+
103+
setIconPath(webviewId: string, iconPath: IconPath | string | undefined) {
104+
if (!iconPath) {
105+
this.icons.delete(webviewId);
106+
} else {
107+
this.icons.set(webviewId, <IconPath | string>iconPath);
108+
}
109+
if (!this.styleElement) {
110+
this.styleElement = this.createStyleSheet();
111+
this.styleElement.id = 'webview-icons';
112+
}
113+
this.updateIconStyleElement();
114+
}
115+
116+
private updateIconStyleElement() {
117+
if (!this.styleElement) {
118+
return;
119+
}
120+
const cssRules: string[] = [`.webview-icon::before {
121+
background-repeat: no-repeat;
122+
vertical-align: middle;
123+
display: inline-block;
124+
text-align: center;
125+
height: 11px;
126+
width: 11px;
127+
content: "";
128+
}`];
129+
this.icons.forEach((value, key) => {
130+
let path: string;
131+
if (typeof value === 'string') {
132+
path = value;
133+
} else {
134+
path = this.isDark() ? value.dark : value.light;
135+
}
136+
if (path.startsWith('/')) {
137+
path = `/webview${path}`;
138+
}
139+
cssRules.push(`.webview-icon.${key}-file-icon::before { background-image: url(${path}); }`);
140+
});
141+
this.setRules(this.styleElement, cssRules);
142+
}
143+
144+
private isDark(): boolean {
145+
const currentThemeId: string = this.themeService.getCurrentTheme().id;
146+
return !currentThemeId.includes('light');
147+
}
148+
}

0 commit comments

Comments
 (0)