Dialog with custom UI API (#561)

* initial checkin for dialog with custom UI API
This commit is contained in:
Leila Lali
2018-01-29 14:31:12 -08:00
committed by GitHub
parent 495c2e62e6
commit 1b2e264c7d
12 changed files with 499 additions and 4 deletions

View File

@@ -410,6 +410,10 @@ export abstract class Modal extends Disposable implements IThemable {
}
}
protected get title(): string {
return this._title;
}
/**
* Set the icon title class name
* @param iconClassName

View File

@@ -0,0 +1,173 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!sql/media/icons/common-icons';
import { Button } from 'sql/base/browser/ui/button/button';
import { Modal } from 'sql/base/browser/ui/modal/modal';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
import { attachButtonStyler, attachModalDialogStyler } from 'sql/common/theme/styler';
import { Builder } from 'vs/base/browser/builder';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
import Event, { Emitter } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { localize } from 'vs/nls';
import WebView from 'vs/workbench/parts/html/browser/webview';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import data = require('data');
export class WebViewDialog extends Modal {
private _body: HTMLElement;
private _okButton: Button;
private _okLabel: string;
private _closeLabel: string;
private _webview: WebView;
private _html: string;
private _headerTitle: string;
private _onOk = new Emitter<void>();
public onOk: Event<void> = this._onOk.event;
private _onClosed = new Emitter<void>();
public onClosed: Event<void> = this._onClosed.event;
private contentDisposables: IDisposable[] = [];
private _onMessage = new Emitter<any>();
constructor(
@IThemeService private _themeService: IThemeService,
@IClipboardService private _clipboardService: IClipboardService,
@IPartService private _webViewPartService: IPartService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextViewService private _contextViewService: IContextViewService,
@IEnvironmentService private _environmentService: IEnvironmentService,
) {
super('', TelemetryKeys.WebView, _webViewPartService, telemetryService, contextKeyService, { isFlyout: false, hasTitleIcon: true });
this._okLabel = localize('OK', 'OK');
this._closeLabel = localize('close', 'Close');
}
public set html(value: string) {
this._html = value;
}
public get html(): string {
return this._html;
}
public set okTitle(value: string) {
this._okLabel = value;
}
public get okTitle(): string {
return this._okLabel;
}
public set closeTitle(value: string) {
this._closeLabel = value;
}
public get closeTitle(): string {
return this._closeLabel;
}
public set headerTitle(value: string) {
this._headerTitle = value
}
public get headerTitle(): string {
return this._headerTitle;
}
protected renderBody(container: HTMLElement) {
new Builder(container).div({ 'class': 'webview-dialog' }, (bodyBuilder) => {
this._body = bodyBuilder.getHTMLElement();
this._webview = new WebView(this._body, this._webViewPartService.getContainer(Parts.EDITOR_PART),
this._contextViewService,
undefined,
undefined,
{
allowScripts: true,
enableWrappedPostMessage: true,
hideFind: true
}
);
this._webview.style(this._themeService.getTheme());
this._webview.onMessage(message => {
this._onMessage.fire(message);
}, null, this.contentDisposables);
this._themeService.onThemeChange(theme => this._webview.style(theme), null, this.contentDisposables);
this.contentDisposables.push(this._webview);
this.contentDisposables.push(toDisposable(() => this._webview = null));
});
}
get onMessage(): Event<any> {
return this._onMessage.event;
}
public render() {
super.render();
this._register(attachModalDialogStyler(this, this._themeService));
this._okButton = this.addFooterButton(this._okLabel, () => this.ok());
this._register(attachButtonStyler(this._okButton, this._themeService));
}
protected layout(height?: number): void {
// Nothing to re-layout
}
private updateDialogBody(): void {
this._webview.contents = [this.html];
}
/* espace key */
protected onClose() {
this.ok();
}
/* enter key */
protected onAccept() {
this.ok();
}
public ok(): void {
this._onOk.fire();
this.close();
}
public close() {
this.hide();
this._onClosed.fire();
}
public sendMessage(message: any): void {
this._webview.sendMessage(message);
}
public open() {
this.title = this.headerTitle;
this.updateDialogBody();
this.show();
this._okButton.focus();
}
public dispose(): void {
this.contentDisposables.forEach(element => {
element.dispose();
});
}
}

View File

@@ -29,6 +29,7 @@ export const FirewallRuleRequested = 'FirewallRuleCreated';
// Modal Dialogs:
export const ErrorMessage = 'ErrorMessage';
export const WebView = 'WebView';
export const ConnectionAdvancedProperties = 'ConnectionAdvancedProperties';
export const Connection = 'Connection';
export const Backup = 'Backup';

59
src/sql/data.d.ts vendored
View File

@@ -1360,4 +1360,63 @@ declare module 'data' {
result: boolean;
ipAddress: string;
}
export interface ModalDialog {
/**
* Title of the webview.
*/
title: string;
/**
* Contents of the dialog body.
*/
html: string;
/**
* The caption of the OK button.
*/
okTitle: string;
/**
* The caption of the Close button.
*/
closeTitle: string;
/**
* Opens the dialog.
*/
open(): void;
/**
* Closes the dialog.
*/
close(): void;
/**
* Raised when the webview posts a message.
*/
readonly onMessage: vscode.Event<any>;
/**
* Raised when dialog closed.
*/
readonly onClosed: vscode.Event<any>;
/**
* Post a message to the dialog.
*
* @param message Body of the message.
*/
postMessage(message: any): Thenable<any>;
}
export namespace window {
/**
* creates a dialog
* @param title
*/
export function createDialog(
title: string
): ModalDialog;
}
}

View File

@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { WebViewDialog } from 'sql/base/browser/ui/modal/webViewDialog';
import { MainThreadModalDialogShape, SqlMainContext, SqlExtHostContext, ExtHostModalDialogsShape } from 'sql/workbench/api/node/sqlextHost.protocol';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import 'vs/css!sql/media/icons/common-icons';
@extHostNamedCustomer(SqlMainContext.MainThreadModalDialog)
export class MainThreadModalDialog implements MainThreadModalDialogShape {
private readonly _proxy: ExtHostModalDialogsShape;
private readonly _dialogs = new Map<number, WebViewDialog>();
constructor(
context: IExtHostContext,
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
this._proxy = context.get(SqlExtHostContext.ExtHostModalDialogs);
}
dispose(): void {
throw new Error('Method not implemented.');
}
$createDialog(handle: number): void {
const dialog = this._instantiationService.createInstance(WebViewDialog);
this._dialogs.set(handle, dialog);
dialog.onMessage(args => {
this._proxy.$onMessage(handle, args);
});
dialog.onClosed(args => {
this._proxy.$onClosed(handle);
});
}
$disposeDialog(handle: number): void {
const dialog = this._dialogs.get(handle);
dialog.close();
}
$setTitle(handle: number, value: string): void {
const dialog = this._dialogs.get(handle);
dialog.headerTitle = value;
}
$setHtml(handle: number, value: string): void {
const dialog = this._dialogs.get(handle);
dialog.html = value;
}
$show(handle: number): void {
const dialog = this._dialogs.get(handle);
dialog.render();
dialog.open();
}
async $sendMessage(handle: number, message: any): Promise<boolean> {
const dialog = this._dialogs.get(handle);
dialog.sendMessage(message);
return Promise.resolve(true);
}
}

View File

@@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'sql/workbench/api/electron-browser/mainThreadModalDialog';

View File

@@ -0,0 +1,122 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { SqlMainContext, MainThreadModalDialogShape, ExtHostModalDialogsShape } from 'sql/workbench/api/node/sqlextHost.protocol';
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import * as vscode from 'vscode';
import * as data from 'data';
import { Emitter } from 'vs/base/common/event';
class ExtHostDialog implements data.ModalDialog {
private _title: string;
private _html: string;
private _okTitle: string;
private _closeTitle: string;
public onMessageEmitter = new Emitter<any>();
public onClosedEmitter = new Emitter<any>();
constructor(
private readonly _proxy: MainThreadModalDialogShape,
private readonly _handle: number,
) { }
get title(): string {
return this._title;
}
set title(value: string) {
if (this._title !== value) {
this._title = value;
this._proxy.$setTitle(this._handle, value);
}
}
get html(): string {
return this._html;
}
set html(value: string) {
if (this._html !== value) {
this._html = value;
this._proxy.$setHtml(this._handle, value);
}
}
public set okTitle(value: string) {
this._okTitle = value;
}
public get okTitle(): string {
return this._okTitle;
}
public set closeTitle(value: string) {
this._closeTitle = value;
}
public get closeTitle(): string {
return this._closeTitle;
}
public open(): void {
this._proxy.$show(this._handle);
}
public close(): void {
this._proxy.$disposeDialog(this._handle);
}
public postMessage(message: any): Thenable<any> {
return this._proxy.$sendMessage(this._handle, message);
}
public get onMessage(): vscode.Event<any> {
return this.onMessageEmitter.event;
}
public get onClosed(): vscode.Event<any> {
return this.onClosedEmitter.event;
}
}
export class ExtHostModalDialogs implements ExtHostModalDialogsShape {
private static _handlePool = 0;
private readonly _proxy: MainThreadModalDialogShape;
private readonly _webviews = new Map<number, ExtHostDialog>();
constructor(
mainContext: IMainContext
) {
this._proxy = mainContext.get(SqlMainContext.MainThreadModalDialog);
}
createDialog(
title: string
): data.ModalDialog {
console.log(title);
const handle = ExtHostModalDialogs._handlePool++;
this._proxy.$createDialog(handle);
const webview = new ExtHostDialog(this._proxy, handle);
this._webviews.set(handle, webview);
webview.title = title;
//webview.options = options;
//this._proxy.$show(handle);
return webview;
}
$onMessage(handle: number, message: any): void {
const webview = this._webviews.get(handle);
webview.onMessageEmitter.fire(message);
}
$onClosed(handle: number): void {
const webview = this._webviews.get(handle);
webview.onClosedEmitter.fire();
}
}

View File

@@ -25,6 +25,7 @@ import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostT
import * as sqlExtHostTypes from 'sql/workbench/api/common/sqlExtHostTypes';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostModalDialogs } from 'sql/workbench/api/node/extHostModalDialog';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl';
@@ -52,6 +53,7 @@ export function createApiFactory(
const extHostDataProvider = threadService.set(SqlExtHostContext.ExtHostDataProtocol, new ExtHostDataProtocol(threadService));
const extHostSerializationProvider = threadService.set(SqlExtHostContext.ExtHostSerializationProvider, new ExtHostSerializationProvider(threadService));
const extHostResourceProvider = threadService.set(SqlExtHostContext.ExtHostResourceProvider, new ExtHostResourceProvider(threadService));
const extHostModalDialogs = threadService.set(SqlExtHostContext.ExtHostModalDialogs, new ExtHostModalDialogs(threadService));
return {
vsCodeFactory: vsCodeFactory,
@@ -236,6 +238,12 @@ export function createApiFactory(
}
};
const window = {
createDialog(name: string) {
return extHostModalDialogs.createDialog(name);
}
};
return {
accounts,
credentials,
@@ -248,7 +256,8 @@ export function createApiFactory(
MetadataType: sqlExtHostTypes.MetadataType,
TaskStatus: sqlExtHostTypes.TaskStatus,
TaskExecutionMode: sqlExtHostTypes.TaskExecutionMode,
ScriptOperation: sqlExtHostTypes.ScriptOperation
ScriptOperation: sqlExtHostTypes.ScriptOperation,
window
};
}
};

View File

@@ -409,7 +409,8 @@ export const SqlMainContext = {
MainThreadCredentialManagement: createMainId<MainThreadCredentialManagementShape>('MainThreadCredentialManagement'),
MainThreadDataProtocol: createMainId<MainThreadDataProtocolShape>('MainThreadDataProtocol'),
MainThreadSerializationProvider: createMainId<MainThreadSerializationProviderShape>('MainThreadSerializationProvider'),
MainThreadResourceProvider: createMainId<MainThreadResourceProviderShape>('MainThreadResourceProvider')
MainThreadResourceProvider: createMainId<MainThreadResourceProviderShape>('MainThreadResourceProvider'),
MainThreadModalDialog: createMainId<MainThreadModalDialogShape>('MainThreadModalDialog'),
};
export const SqlExtHostContext = {
@@ -417,5 +418,19 @@ export const SqlExtHostContext = {
ExtHostCredentialManagement: createExtId<ExtHostCredentialManagementShape>('ExtHostCredentialManagement'),
ExtHostDataProtocol: createExtId<ExtHostDataProtocolShape>('ExtHostDataProtocol'),
ExtHostSerializationProvider: createExtId<ExtHostSerializationProviderShape>('ExtHostSerializationProvider'),
ExtHostResourceProvider: createExtId<ExtHostResourceProviderShape>('ExtHostResourceProvider')
ExtHostResourceProvider: createExtId<ExtHostResourceProviderShape>('ExtHostResourceProvider'),
ExtHostModalDialogs: createExtId<ExtHostModalDialogsShape>('ExtHostModalDialogs')
};
export interface MainThreadModalDialogShape extends IDisposable {
$createDialog(handle: number): void;
$disposeDialog(handle: number): void;
$show(handle: number): void;
$setTitle(handle: number, value: string): void;
$setHtml(handle: number, value: string): void;
$sendMessage(handle: number, value: any): Thenable<boolean>;
}
export interface ExtHostModalDialogsShape {
$onMessage(handle: number, message: any): void;
$onClosed(handle: number): void;
}