diff --git a/samples/sqlservices/package.json b/samples/sqlservices/package.json index cbff34d018..c41815e28c 100644 --- a/samples/sqlservices/package.json +++ b/samples/sqlservices/package.json @@ -22,12 +22,12 @@ "title": "sqlservices.openDialog" }, { - "command": "sqlservices.openEditor1", - "title": "sqlservices.openEditor1" + "command": "sqlservices.openEditor", + "title": "sqlservices.openEditor" }, { - "command": "sqlservices.openEditor2", - "title": "sqlservices.openEditor2" + "command": "sqlservices.openEditorWithWebView", + "title": "sqlservices.openEditorWithWebView" } ], "dashboard.tabs": [ diff --git a/samples/sqlservices/src/controllers/mainController.ts b/samples/sqlservices/src/controllers/mainController.ts index b6d7db7e5d..3cb17ed902 100644 --- a/samples/sqlservices/src/controllers/mainController.ts +++ b/samples/sqlservices/src/controllers/mainController.ts @@ -35,7 +35,6 @@ export default class MainController implements vscode.Disposable { } public activate(): Promise { - const webviewExampleHtml = fs.readFileSync(path.join(__dirname, 'webviewExample.html')).toString(); const buttonHtml = fs.readFileSync(path.join(__dirname, 'button.html')).toString(); const counterHtml = fs.readFileSync(path.join(__dirname, 'counter.html')).toString(); this.registerSqlServicesModelView(); @@ -45,16 +44,16 @@ 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.openEditor1', () => { - this.openEditor1(buttonHtml, counterHtml); + vscode.commands.registerCommand('sqlservices.openEditor', () => { + this.openEditor(); }); - vscode.commands.registerCommand('sqlservices.openEditor2', () => { - this.openEditor2(webviewExampleHtml); + vscode.commands.registerCommand('sqlservices.openEditorWithWebView', () => { + this.openEditorWithWebview(buttonHtml, counterHtml); }); return Promise.resolve(true); @@ -78,51 +77,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(); }); 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({ @@ -150,38 +149,57 @@ export default class MainController implements vscode.Disposable { 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' + 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 openEditor1(html1: string, html2: string): void { - let editor = sqlops.workspace.createModelViewEditor('Editor view1'); + private openEditor(): void { + let editor = sqlops.workspace.createModelViewEditor('Test Model View'); + editor.registerContent(async view => { + let inputBox = view.modelBuilder.inputBox() + .withValidation(component => component.value !== 'valid') + .component(); + let formModel = view.modelBuilder.formContainer() + .withFormItems([{ + component: inputBox, + title: 'Enter anything but "valid"' + }]).component(); + view.onClosed((params) => { + vscode.window.showInformationMessage('The model view editor is closed.'); + }); + await view.initializeModel(formModel); + }); + editor.openEditor(); + } + + private openEditorWithWebview(html1: string, html2: string): void { + let editor = sqlops.workspace.createModelViewEditor('Editor view1', { retainContextWhenHidden: true }); editor.registerContent(async view => { let count = 0; let webview1 = view.modelBuilder.webView() @@ -213,29 +231,6 @@ export default class MainController implements vscode.Disposable { editor.openEditor(); } - private openEditor2(html: string): void { - let editor = sqlops.workspace.createModelViewEditor('Editor view2'); - editor.registerContent(async view => { - let webview1 = view.modelBuilder.webView() - .withProperties({ - html: html - }) - .component(); - - let flexModel = view.modelBuilder.flexContainer() - .withLayout({ - flexFlow: 'column', - alignItems: 'stretch', - height: '100%' - }).withItems([ - webview1 - ], { flex: '1' }) - .component(); - await view.initializeModel(flexModel); - }); - editor.openEditor(); - } - private registerSqlServicesModelView(): void { sqlops.ui.registerModelViewProvider('sqlservices', async (view) => { let flexModel = view.modelBuilder.flexContainer() diff --git a/samples/sqlservices/src/controllers/webviewExample.html b/samples/sqlservices/src/controllers/webviewExample.html deleted file mode 100644 index c57c83cbf9..0000000000 --- a/samples/sqlservices/src/controllers/webviewExample.html +++ /dev/null @@ -1,25 +0,0 @@ - - -
- -
- - Hello :) - - diff --git a/src/sql/parts/modelComponents/modelEditor/modelViewEditor.css b/src/sql/parts/modelComponents/modelEditor/modelViewEditor.css deleted file mode 100644 index bf012515dc..0000000000 --- a/src/sql/parts/modelComponents/modelEditor/modelViewEditor.css +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.model-view-container { - height: 100%; - width : 100%; -} diff --git a/src/sql/parts/modelComponents/modelEditor/modelViewEditor.ts b/src/sql/parts/modelComponents/modelEditor/modelViewEditor.ts index 61daf0ab3d..a2a58cf398 100644 --- a/src/sql/parts/modelComponents/modelEditor/modelViewEditor.ts +++ b/src/sql/parts/modelComponents/modelEditor/modelViewEditor.ts @@ -2,8 +2,6 @@ * 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!./modelViewEditor'; - import { Builder, $ } from 'vs/base/browser/builder'; import { TPromise } from 'vs/base/common/winjs.base'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -15,19 +13,16 @@ import * as DOM from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ModelViewInput } from 'sql/parts/modelComponents/modelEditor/modelViewInput'; -import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService'; -import { Dialog } from 'sql/platform/dialog/dialogTypes'; -import { DialogPane } from 'sql/platform/dialog/dialogPane'; export class ModelViewEditor extends BaseEditor { public static ID: string = 'workbench.editor.modelViewEditor'; - private _modelViewMap = new Map(); + + private _editorFrame: HTMLElement; constructor( @ITelemetryService telemetryService: ITelemetryService, - @IThemeService themeService: IThemeService, - @IInstantiationService private _instantiationService: IInstantiationService + @IThemeService themeService: IThemeService ) { super(ModelViewEditor.ID, telemetryService, themeService); } @@ -36,6 +31,7 @@ export class ModelViewEditor extends BaseEditor { * Called to create the editor in the parent builder. */ public createEditor(parent: Builder): void { + this._editorFrame = parent.getHTMLElement(); } /** @@ -44,23 +40,43 @@ export class ModelViewEditor extends BaseEditor { public focus(): void { } - public setInput(input: ModelViewInput, options?: EditorOptions): TPromise { + async setInput(input: ModelViewInput, options?: EditorOptions): TPromise { if (this.input && this.input.matches(input)) { return TPromise.as(undefined); } + const parentElement = this.getContainer().getHTMLElement(); - $(parentElement).clearChildren(); - - if (!this._modelViewMap.get(input.modelViewId)) { - let modelViewContainer = DOM.$('div.model-view-container'); - let dialogPane = new DialogPane(input.title, input.modelViewId, () => undefined, this._instantiationService); - dialogPane.createBody(modelViewContainer); - this._modelViewMap.set(input.modelViewId, modelViewContainer); + if (this.input instanceof ModelViewInput) { + if (this.input.container) { + if (this.input.options && this.input.options.retainContextWhenHidden) { + this.input.container.style.visibility = 'hidden'; + } else { + parentElement.removeChild(this.input.container); + } + } } - let element = this._modelViewMap.get(input.modelViewId); - DOM.append(parentElement, element); - return super.setInput(input, options); + if (!parentElement.contains(input.container)) { + parentElement.appendChild(input.container); + } + input.container.style.visibility = 'visible'; + + await super.setInput(input, options); + this.doUpdateContainer(); + } + + private doUpdateContainer() { + const modelViewContainer = this.input && (this.input as ModelViewInput).container; + if (modelViewContainer) { + const frameRect = this._editorFrame.getBoundingClientRect(); + const containerRect = modelViewContainer.parentElement.getBoundingClientRect(); + + modelViewContainer.style.position = 'absolute'; + modelViewContainer.style.top = `${frameRect.top}px`; + modelViewContainer.style.left = `${frameRect.left - containerRect.left}px`; + modelViewContainer.style.width = `${frameRect.width}px`; + modelViewContainer.style.height = `${frameRect.height}px`; + } } /** @@ -68,7 +84,12 @@ export class ModelViewEditor extends BaseEditor { * To be called when the container of this editor changes size. */ public layout(dimension: Dimension): void { - + if (this.input instanceof ModelViewInput) { + if (this.input.container && this.input.dialogPane) { + this.doUpdateContainer(); + // todo: layout this.input.dialogPane (Github issue: #1484) + } + } } } diff --git a/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts b/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts index 4700e97215..e16f110472 100644 --- a/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts +++ b/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts @@ -6,12 +6,22 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { EditorInput } from 'vs/workbench/common/editor'; +import * as DOM from 'vs/base/browser/dom'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { DialogPane } from 'sql/platform/dialog/dialogPane'; + +import * as sqlops from 'sqlops'; export class ModelViewInput extends EditorInput { public static ID: string = 'workbench.editorinputs.ModelViewEditorInput'; + private _container: HTMLElement; + private _dialogPane: DialogPane; - constructor(private _title: string, private _modelViewId: string) { + constructor(private _title: string, private _modelViewId: string, + private _options: sqlops.ModelViewEditorOptions, + @IInstantiationService private _instantiationService: IInstantiationService, + ) { super(); } @@ -34,4 +44,32 @@ export class ModelViewInput extends EditorInput { public getName(): string { return this._title; } + + public get container(): HTMLElement { + if (!this._container && !this._dialogPane) { + this._container = DOM.$('div.model-view-container'); + this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService); + this._dialogPane.createBody(this._container); + } + return this._container; + } + + public get dialogPane(): DialogPane { + return this._dialogPane; + } + + public get options(): sqlops.ModelViewEditorOptions { + return this._options; + } + + public dispose(): void { + if (this._dialogPane) { + this._dialogPane.dispose(); + } + if (this._container) { + this._container.remove(); + this._container = undefined; + } + super.dispose(); + } } diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 3a8645342f..00c16e82ba 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -666,7 +666,7 @@ declare module 'sqlops' { /** * Create a new model view editor */ - export function createModelViewEditor(title: string): ModelViewEditor; + export function createModelViewEditor(title: string, options?: ModelViewEditorOptions): ModelViewEditor; export interface ModelViewEditor extends window.modelviewdialog.ModelViewPanel { @@ -676,4 +676,11 @@ declare module 'sqlops' { openEditor(position?: vscode.ViewColumn): Thenable; } } + + export interface ModelViewEditorOptions { + /** + * Should the model view editor's context be kept around even when the editor is no longer visible? It is false by default + */ + readonly retainContextWhenHidden?: boolean; + } } diff --git a/src/sql/workbench/api/node/extHostModelViewDialog.ts b/src/sql/workbench/api/node/extHostModelViewDialog.ts index 199aad5fdf..9ecfa71895 100644 --- a/src/sql/workbench/api/node/extHostModelViewDialog.ts +++ b/src/sql/workbench/api/node/extHostModelViewDialog.ts @@ -76,13 +76,14 @@ class ModelViewEditorImpl extends ModelViewPanelImpl implements sqlops.workspace extHostModelViewDialog: ExtHostModelViewDialog, extHostModelView: ExtHostModelViewShape, private _proxy: MainThreadModelViewDialogShape, - private _title: string + private _title: string, + private _options: sqlops.ModelViewEditorOptions ) { super('modelViewEditor', extHostModelViewDialog, extHostModelView); } public openEditor(position?: vscode.ViewColumn): Thenable { - return this._proxy.$openEditor(this._modelViewId, this._title, position); + return this._proxy.$openEditor(this._modelViewId, this._title, this._options, position); } } @@ -345,8 +346,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { this._proxy.$closeDialog(handle); } - public createModelViewEditor(title: string): sqlops.workspace.ModelViewEditor { - let editor = new ModelViewEditorImpl(this, this._extHostModelView, this._proxy, title); + public createModelViewEditor(title: string, options?: sqlops.ModelViewEditorOptions): sqlops.workspace.ModelViewEditor { + let editor = new ModelViewEditorImpl(this, this._extHostModelView, this._proxy, title, options); editor.handle = this.getHandle(editor); return editor; } diff --git a/src/sql/workbench/api/node/mainThreadModelViewDialog.ts b/src/sql/workbench/api/node/mainThreadModelViewDialog.ts index 04dd3ddc5a..ae14350571 100644 --- a/src/sql/workbench/api/node/mainThreadModelViewDialog.ts +++ b/src/sql/workbench/api/node/mainThreadModelViewDialog.ts @@ -17,6 +17,7 @@ import { IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails, import { ModelViewInput } from 'sql/parts/modelComponents/modelEditor/modelViewInput'; import * as vscode from 'vscode'; +import * as sqlops from 'sqlops'; @extHostNamedCustomer(SqlMainContext.MainThreadModelViewDialog) export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape { @@ -31,20 +32,20 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape constructor( context: IExtHostContext, - @IInstantiationService instatiationService: IInstantiationService, + @IInstantiationService private _instatiationService: IInstantiationService, @IWorkbenchEditorService private _editorService: IWorkbenchEditorService ) { this._proxy = context.getProxy(SqlExtHostContext.ExtHostModelViewDialog); - this._dialogService = new CustomDialogService(instatiationService); + this._dialogService = new CustomDialogService(_instatiationService); } public dispose(): void { throw new Error('Method not implemented.'); } - public $openEditor(modelViewId: string, title: string, position?: vscode.ViewColumn): Thenable { + public $openEditor(modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable { return new Promise((resolve, reject) => { - let input = new ModelViewInput(title, modelViewId); + let input = this._instatiationService.createInstance(ModelViewInput, title, modelViewId, options); let editorOptions = { preserveFocus: true, pinned: true diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts index 810fdf4138..23565e4585 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts @@ -325,8 +325,8 @@ export function createApiFactory( const workspace: typeof sqlops.workspace = { onDidOpenDashboard: extHostDashboard.onDidOpenDashboard, onDidChangeToDashboard: extHostDashboard.onDidChangeToDashboard, - createModelViewEditor(title: string): sqlops.workspace.ModelViewEditor { - return extHostModelViewDialog.createModelViewEditor(title); + createModelViewEditor(title: string, options?: sqlops.ModelViewEditorOptions): sqlops.workspace.ModelViewEditor { + return extHostModelViewDialog.createModelViewEditor(title, options); } }; diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts index 038b43160d..81b8f13738 100644 --- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts @@ -559,7 +559,7 @@ export interface ExtHostModelViewDialogShape { } export interface MainThreadModelViewDialogShape extends IDisposable { - $openEditor(modelViewId: string, title: string, position?: vscode.ViewColumn): Thenable; + $openEditor(modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable; $openDialog(handle: number): Thenable; $closeDialog(handle: number): Thenable; $setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable;