diff --git a/samples/sqlservices/src/controllers/button.html b/samples/sqlservices/src/controllers/button.html
new file mode 100644
index 0000000000..f7185f4d9a
--- /dev/null
+++ b/samples/sqlservices/src/controllers/button.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/samples/sqlservices/src/controllers/counter.html b/samples/sqlservices/src/controllers/counter.html
new file mode 100644
index 0000000000..9dc092f6cf
--- /dev/null
+++ b/samples/sqlservices/src/controllers/counter.html
@@ -0,0 +1,21 @@
+
+
+
+
+ Total Counts: 0
+
+
+
diff --git a/samples/sqlservices/src/controllers/mainController.ts b/samples/sqlservices/src/controllers/mainController.ts
index be8c2dcb25..e2dc1ab0f5 100644
--- a/samples/sqlservices/src/controllers/mainController.ts
+++ b/samples/sqlservices/src/controllers/mainController.ts
@@ -9,6 +9,8 @@ import * as sqlops from 'sqlops';
import * as Utils from '../utils';
import * as vscode from 'vscode';
import SplitPropertiesPanel from './splitPropertiesPanel';
+import * as fs from 'fs';
+import * as path from 'path';
/**
* The main controller class that initializes the extension
@@ -33,6 +35,8 @@ export default class MainController implements vscode.Disposable {
}
public activate(): Promise {
+ const buttonHtml = fs.readFileSync(path.join(__dirname, 'button.html')).toString();
+ const counterHtml = fs.readFileSync(path.join(__dirname, 'counter.html')).toString();
this.registerSqlServicesModelView();
this.registerSplitPanelModelView();
@@ -40,12 +44,12 @@ export default class MainController implements vscode.Disposable {
vscode.window.showInformationMessage(`Clicked from profile ${profile.serverName}.${profile.databaseName}`);
});
- vscode.commands.registerCommand('sqlservices.openDialog', () => {
+ vscode.commands.registerCommand('sqlservices.openDialog', () => {
this.openDialog();
});
- vscode.commands.registerCommand('sqlservices.openEditor', () => {
- this.openEditor();
+ vscode.commands.registerCommand('sqlservices.openEditor', () => {
+ this.openEditor(buttonHtml, counterHtml);
});
return Promise.resolve(true);
@@ -69,51 +73,51 @@ export default class MainController implements vscode.Disposable {
dialog.customButtons = [customButton1, customButton2];
tab1.registerContent(async (view) => {
let inputBox = view.modelBuilder.inputBox()
- .withProperties({
- //width: 300
- })
- .component();
+ .withProperties({
+ //width: 300
+ })
+ .component();
let inputBox2 = view.modelBuilder.inputBox()
- .component();
+ .component();
let checkbox = view.modelBuilder.checkBox()
- .withProperties({
- label: 'Copy-only backup'
- })
- .component();
+ .withProperties({
+ label: 'Copy-only backup'
+ })
+ .component();
checkbox.onChanged(e => {
- console.info("inputBox.enabled " + inputBox.enabled);
- inputBox.enabled = !inputBox.enabled;
+ console.info("inputBox.enabled " + inputBox.enabled);
+ inputBox.enabled = !inputBox.enabled;
});
let button = view.modelBuilder.button()
- .withProperties({
- label: '+'
- }).component();
+ .withProperties({
+ label: '+'
+ }).component();
let button3 = view.modelBuilder.button()
- .withProperties({
- label: '-'
+ .withProperties({
+ label: '-'
- }).component();
+ }).component();
let button2 = view.modelBuilder.button()
- .component();
+ .component();
button.onDidClick(e => {
- inputBox2.value = 'Button clicked';
+ inputBox2.value = 'Button clicked';
});
let dropdown = view.modelBuilder.dropDown()
- .withProperties({
- value: 'Full',
- values: ['Full', 'Differential', 'Transaction Log']
- })
- .component();
+ .withProperties({
+ value: 'Full',
+ values: ['Full', 'Differential', 'Transaction Log']
+ })
+ .component();
let f = 0;
inputBox.onTextChanged((params) => {
- vscode.window.showInformationMessage(inputBox.value);
+ vscode.window.showInformationMessage(inputBox.value);
f = f + 1;
- inputBox2.value=f.toString();
+ inputBox2.value = f.toString();
});
dropdown.onValueChanged((params) => {
- vscode.window.showInformationMessage(inputBox2.value);
- inputBox.value = dropdown.value;
+ vscode.window.showInformationMessage(inputBox2.value);
+ inputBox.value = dropdown.value;
});
let radioButton = view.modelBuilder.radioButton()
.withProperties({
@@ -121,68 +125,84 @@ export default class MainController implements vscode.Disposable {
name: 'radioButtonOptions',
label: 'Option 1',
checked: true
- //width: 300
- }).component();
+ //width: 300
+ }).component();
let radioButton2 = view.modelBuilder.radioButton()
.withProperties({
value: 'option2',
name: 'radioButtonOptions',
label: 'Option 2'
- //width: 300
- }).component();
+ //width: 300
+ }).component();
let flexRadioButtonsModel = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'column',
alignItems: 'left',
justifyContent: 'space-evenly',
height: 50
- }).withItems([
- radioButton, radioButton2]
+ }).withItems([
+ radioButton, radioButton2]
, { flex: '1 1 50%' }).component();
let formModel = view.modelBuilder.formContainer()
- .withFormItems([{
- component: inputBox,
- title: 'Backup name'
+ .withFormItems([{
+ component: inputBox,
+ title: 'Backup name'
}, {
- component: inputBox2,
- title: 'Recovery model'
+ component: inputBox2,
+ title: 'Recovery model'
}, {
- component:dropdown,
- title: 'Backup type'
+ component: dropdown,
+ title: 'Backup type'
}, {
- component: checkbox,
- title: ''
+ component: checkbox,
+ title: ''
}, {
- component: inputBox2,
- title: 'Backup files',
- actions: [button, button3]
+ component: inputBox2,
+ title: 'Backup files',
+ actions: [button, button3]
}, {
component: flexRadioButtonsModel,
title: 'Options'
}], {
- horizontal:false,
- width: 500,
- componentWidth: 400
- }).component();
+ horizontal: false,
+ width: 500,
+ componentWidth: 400
+ }).component();
await view.initializeModel(formModel);
});
sqlops.window.modelviewdialog.openDialog(dialog);
}
- private openEditor(): void {
+ private openEditor(html1: string, html2: string): void {
let editor = sqlops.workspace.createModelViewEditor('Test Editor view');
editor.registerContent(async view => {
- let inputBox = view.modelBuilder.inputBox()
- .withValidation(component => component.value !== 'valid')
+ let count = 0;
+ let webview1 = view.modelBuilder.webView()
+ .withProperties({
+ html: html1
+ })
.component();
- let formModel = view.modelBuilder.formContainer()
- .withFormItems([{
- component: inputBox,
- title: 'Enter anything but "valid"'
- }]).component();
- await view.initializeModel(formModel);
+ let webview2 = view.modelBuilder.webView()
+ .withProperties({
+ html: html2
+ })
+ .component();
+ webview1.onMessage((params) => {
+ count++;
+ webview2.message = count;
+ });
+
+ let flexModel = view.modelBuilder.flexContainer()
+ .withLayout({
+ flexFlow: 'column',
+ alignItems: 'left'
+ }).withItems([
+ webview1, webview2
+ ], { flex: '1 1 50%' })
+ .component();
+ await view.initializeModel(flexModel);
});
editor.openEditor();
}
diff --git a/src/sql/parts/modelComponents/components.contribution.ts b/src/sql/parts/modelComponents/components.contribution.ts
index 85f19d4bdf..d25de4266b 100644
--- a/src/sql/parts/modelComponents/components.contribution.ts
+++ b/src/sql/parts/modelComponents/components.contribution.ts
@@ -11,6 +11,7 @@ import DropDownComponent from './dropdown.component';
import ButtonComponent from './button.component';
import CheckBoxComponent from './checkbox.component';
import RadioButtonComponent from './radioButton.component';
+import WebViewComponent from './webview.component';
import TextComponent from './text.component';
import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
@@ -40,5 +41,8 @@ registerComponentType(CHECKBOX_COMPONENT, ModelComponentTypes.CheckBox, CheckBox
export const RADIOBUTTON_COMPONENT = 'radiobutton-component';
registerComponentType(RADIOBUTTON_COMPONENT, ModelComponentTypes.RadioButton, RadioButtonComponent);
+export const WEBVIEW_COMPONENT = 'webview-component';
+registerComponentType(WEBVIEW_COMPONENT, ModelComponentTypes.WebView, WebViewComponent);
+
export const TEXT_COMPONENT = 'text-component';
registerComponentType(TEXT_COMPONENT, ModelComponentTypes.Text, TextComponent);
diff --git a/src/sql/parts/modelComponents/interfaces.ts b/src/sql/parts/modelComponents/interfaces.ts
index a735e49933..a96750305c 100644
--- a/src/sql/parts/modelComponents/interfaces.ts
+++ b/src/sql/parts/modelComponents/interfaces.ts
@@ -63,7 +63,8 @@ export enum ComponentEventType {
PropertiesChanged,
onDidChange,
onDidClick,
- validityChanged
+ validityChanged,
+ onMessage
}
export interface IModelStore {
diff --git a/src/sql/parts/modelComponents/webview.component.ts b/src/sql/parts/modelComponents/webview.component.ts
new file mode 100644
index 0000000000..9d9980df0e
--- /dev/null
+++ b/src/sql/parts/modelComponents/webview.component.ts
@@ -0,0 +1,130 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import 'vs/css!./webview';
+import {
+ Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
+ ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList
+} from '@angular/core';
+
+import * as sqlops from 'sqlops';
+import Event, { Emitter } from 'vs/base/common/event';
+import { Webview } from 'vs/workbench/parts/html/browser/webview';
+import { addDisposableListener, EventType } from 'vs/base/browser/dom';
+import { Parts } from 'vs/workbench/services/part/common/partService';
+
+import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
+import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
+import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
+
+@Component({
+ template: '',
+ selector: 'webview-component'
+})
+export default class WebViewComponent extends ComponentBase implements IComponent, OnDestroy {
+ @Input() descriptor: IComponentDescriptor;
+ @Input() modelStore: IModelStore;
+
+ private _webview: Webview;
+ private _onMessage = new Emitter();
+ private _renderedHtml: string;
+
+ constructor(
+ @Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
+ @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
+ @Inject(forwardRef(() => ElementRef)) private _el: ElementRef) {
+ super(changeRef);
+ }
+
+ ngOnInit(): void {
+ this.baseInit();
+ this._createWebview();
+ this._register(addDisposableListener(window, EventType.RESIZE, e => {
+ this.layout();
+ }));
+ }
+
+ private _createWebview(): void {
+ this._webview = this._register(new Webview(this._el.nativeElement,
+ this._commonService.partService.getContainer(Parts.EDITOR_PART),
+ this._commonService.themeService,
+ this._commonService.environmentService,
+ this._commonService.contextViewService,
+ undefined,
+ undefined,
+ {
+ allowScripts: true,
+ enableWrappedPostMessage: true
+ }
+ ));
+
+
+ this._register(this._webview.onMessage(e => {
+ this._onEventEmitter.fire({
+ eventType: ComponentEventType.onMessage,
+ args: e
+ });
+ }));
+
+ this._webview.style(this._commonService.themeService.getTheme());
+ this.setHtml();
+ }
+
+ ngOnDestroy(): void {
+ this.baseDestroy();
+ }
+
+ /// Webview Functions
+
+ private setHtml(): void {
+ if (this._webview && this.html) {
+ this._renderedHtml = this.html;
+ this._webview.contents = this._renderedHtml;
+ this._webview.layout();
+ }
+ }
+
+ private sendMessage(): void {
+ if (this._webview && this.message) {
+ this._webview.sendMessage(this.message);
+ }
+ }
+
+ /// IComponent implementation
+
+ public layout(): void {
+ this._webview.layout();
+ }
+
+ public setLayout(layout: any): void {
+ // TODO allow configuring the look and feel
+ this.layout();
+ }
+
+ public setProperties(properties: { [key: string]: any; }): void {
+ super.setProperties(properties);
+ if (this.html !== this._renderedHtml) {
+ this.setHtml();
+ }
+ this.sendMessage();
+ }
+
+ // CSS-bound properties
+
+ public get message(): any {
+ return this.getPropertyOrDefault((props) => props.message, undefined);
+ }
+
+ public set message(newValue: any) {
+ this.setPropertyFromUI((properties, message) => { properties.message = message; }, newValue);
+ }
+
+ public get html(): string {
+ return this.getPropertyOrDefault((props) => props.html, undefined);
+ }
+
+ public set html(newValue: string) {
+ this.setPropertyFromUI((properties, html) => { properties.html = html; }, newValue);
+ }
+}
diff --git a/src/sql/parts/modelComponents/webview.css b/src/sql/parts/modelComponents/webview.css
new file mode 100644
index 0000000000..33bf076256
--- /dev/null
+++ b/src/sql/parts/modelComponents/webview.css
@@ -0,0 +1,10 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+webview-component {
+ height: 100%;
+ width : 100%;
+ display: block;
+}
\ No newline at end of file
diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts
index ba5fbaee39..8d8bf3ac73 100644
--- a/src/sql/sqlops.proposed.d.ts
+++ b/src/sql/sqlops.proposed.d.ts
@@ -22,11 +22,12 @@ declare module 'sqlops' {
inputBox(): ComponentBuilder;
checkBox(): ComponentBuilder;
radioButton(): ComponentBuilder;
+ webView(): ComponentBuilder;
text(): ComponentBuilder;
button(): ComponentBuilder;
dropDown(): ComponentBuilder;
- dashboardWidget(widgetId: string): ComponentBuilder;
- dashboardWebview(webviewId: string): ComponentBuilder;
+ dashboardWidget(widgetId: string): ComponentBuilder;
+ dashboardWebview(webviewId: string): ComponentBuilder;
formContainer(): FormBuilder;
}
@@ -273,6 +274,11 @@ declare module 'sqlops' {
editable?: boolean;
}
+ export interface WebViewProperties {
+ message?: any;
+ html?: string;
+ }
+
export interface ButtonProperties {
label?: string;
}
@@ -308,16 +314,22 @@ declare module 'sqlops' {
onValueChanged: vscode.Event;
}
+ export interface WebViewComponent extends Component {
+ html: string;
+ message: any;
+ onMessage: vscode.Event;
+ }
+
export interface ButtonComponent extends Component {
label: string;
onDidClick: vscode.Event;
}
- export interface WidgetComponent extends Component {
+ export interface DashboardWidgetComponent extends Component {
widgetId: string;
}
- export interface WebviewComponent extends Component {
+ export interface DashboardWebviewComponent extends Component {
webviewId: string;
}
diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts
index aa302f4966..d486cacf2d 100644
--- a/src/sql/workbench/api/common/sqlExtHostTypes.ts
+++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts
@@ -72,6 +72,7 @@ export enum ModelComponentTypes {
Button,
CheckBox,
RadioButton,
+ WebView,
Text,
DashboardWidget,
DashboardWebview,
@@ -95,7 +96,8 @@ export enum ComponentEventType {
PropertiesChanged,
onDidChange,
onDidClick,
- validityChanged
+ validityChanged,
+ onMessage
}
export interface IComponentEventArgs {
diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts
index 05d629825b..afc3bfa8f8 100644
--- a/src/sql/workbench/api/node/extHostModelView.ts
+++ b/src/sql/workbench/api/node/extHostModelView.ts
@@ -80,6 +80,13 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
return builder;
}
+ webView(): sqlops.ComponentBuilder {
+ let id = this.getNextComponentId();
+ let builder: ComponentBuilderImpl = this.getComponentBuilder(new WebViewWrapper(this._proxy, this._handle, id), id);
+ this._componentBuilders.set(id, builder);
+ return builder;
+ }
+
button(): sqlops.ComponentBuilder {
let id = this.getNextComponentId();
let builder: ComponentBuilderImpl = this.getComponentBuilder(new ButtonWrapper(this._proxy, this._handle, id), id);
@@ -94,16 +101,16 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
return builder;
}
- dashboardWidget(widgetId: string): sqlops.ComponentBuilder {
+ dashboardWidget(widgetId: string): sqlops.ComponentBuilder {
let id = this.getNextComponentId();
- let builder = this.getComponentBuilder(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id);
+ let builder = this.getComponentBuilder(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
- dashboardWebview(webviewId: string): sqlops.ComponentBuilder {
+ dashboardWebview(webviewId: string): sqlops.ComponentBuilder {
let id = this.getNextComponentId();
- let builder: ComponentBuilderImpl = this.getComponentBuilder(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWebview, id), id);
+ let builder: ComponentBuilderImpl = this.getComponentBuilder(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWebview, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
@@ -523,6 +530,34 @@ class CheckBoxWrapper extends ComponentWrapper implements sqlops.CheckBoxCompone
}
}
+class WebViewWrapper extends ComponentWrapper implements sqlops.WebViewComponent {
+
+ constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
+ super(proxy, handle, ModelComponentTypes.WebView, id);
+ this.properties = {};
+ this._emitterMap.set(ComponentEventType.onMessage, new Emitter());
+ }
+
+ public get message(): any {
+ return this.properties['message'];
+ }
+ public set message(v: any) {
+ this.setProperty('message', v);
+ }
+
+ public get html(): string {
+ return this.properties['html'];
+ }
+ public set html(v: string) {
+ this.setProperty('html', v);
+ }
+
+ public get onMessage(): vscode.Event {
+ let emitter = this._emitterMap.get(ComponentEventType.onMessage);
+ return emitter && emitter.event;
+ }
+}
+
class RadioButtonWrapper extends ComponentWrapper implements sqlops.RadioButtonComponent {
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {