Fix view model editor and webview component (#1483)

* destroy viewmodel when editor is closed and add example

* support retainContextWhenHidden option for webview component

* fix breaking change from master

* dispose html element during dispose

* add more comments
This commit is contained in:
Abbie Petchtes
2018-05-24 13:54:41 -07:00
committed by GitHub
parent 1359354387
commit c208abf0c5
11 changed files with 176 additions and 147 deletions

View File

@@ -22,12 +22,12 @@
"title": "sqlservices.openDialog" "title": "sqlservices.openDialog"
}, },
{ {
"command": "sqlservices.openEditor1", "command": "sqlservices.openEditor",
"title": "sqlservices.openEditor1" "title": "sqlservices.openEditor"
}, },
{ {
"command": "sqlservices.openEditor2", "command": "sqlservices.openEditorWithWebView",
"title": "sqlservices.openEditor2" "title": "sqlservices.openEditorWithWebView"
} }
], ],
"dashboard.tabs": [ "dashboard.tabs": [

View File

@@ -35,7 +35,6 @@ export default class MainController implements vscode.Disposable {
} }
public activate(): Promise<boolean> { public activate(): Promise<boolean> {
const webviewExampleHtml = fs.readFileSync(path.join(__dirname, 'webviewExample.html')).toString();
const buttonHtml = fs.readFileSync(path.join(__dirname, 'button.html')).toString(); const buttonHtml = fs.readFileSync(path.join(__dirname, 'button.html')).toString();
const counterHtml = fs.readFileSync(path.join(__dirname, 'counter.html')).toString(); const counterHtml = fs.readFileSync(path.join(__dirname, 'counter.html')).toString();
this.registerSqlServicesModelView(); this.registerSqlServicesModelView();
@@ -49,12 +48,12 @@ export default class MainController implements vscode.Disposable {
this.openDialog(); this.openDialog();
}); });
vscode.commands.registerCommand('sqlservices.openEditor1', () => { vscode.commands.registerCommand('sqlservices.openEditor', () => {
this.openEditor1(buttonHtml, counterHtml); this.openEditor();
}); });
vscode.commands.registerCommand('sqlservices.openEditor2', () => { vscode.commands.registerCommand('sqlservices.openEditorWithWebView', () => {
this.openEditor2(webviewExampleHtml); this.openEditorWithWebview(buttonHtml, counterHtml);
}); });
return Promise.resolve(true); return Promise.resolve(true);
@@ -171,7 +170,7 @@ export default class MainController implements vscode.Disposable {
title: 'Options' title: 'Options'
}], { }], {
horizontal: false, horizontal: false,
width: 500, //width: 500,
componentWidth: 400 componentWidth: 400
}).component(); }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
@@ -180,8 +179,27 @@ export default class MainController implements vscode.Disposable {
sqlops.window.modelviewdialog.openDialog(dialog); sqlops.window.modelviewdialog.openDialog(dialog);
} }
private openEditor1(html1: string, html2: string): void { private openEditor(): void {
let editor = sqlops.workspace.createModelViewEditor('Editor view1'); 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 => { editor.registerContent(async view => {
let count = 0; let count = 0;
let webview1 = view.modelBuilder.webView() let webview1 = view.modelBuilder.webView()
@@ -213,29 +231,6 @@ export default class MainController implements vscode.Disposable {
editor.openEditor(); 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 { private registerSqlServicesModelView(): void {
sqlops.ui.registerModelViewProvider('sqlservices', async (view) => { sqlops.ui.registerModelViewProvider('sqlservices', async (view) => {
let flexModel = view.modelBuilder.flexContainer() let flexModel = view.modelBuilder.flexContainer()

View File

@@ -1,25 +0,0 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<html>
<header>
<style>
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
background-color: skyblue;
}
</style>
</header>
<body>
Hello :)
</body>
</html>

View File

@@ -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%;
}

View File

@@ -2,8 +2,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { Builder, $ } from 'vs/base/browser/builder';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; 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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ModelViewInput } from 'sql/parts/modelComponents/modelEditor/modelViewInput'; 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 { export class ModelViewEditor extends BaseEditor {
public static ID: string = 'workbench.editor.modelViewEditor'; public static ID: string = 'workbench.editor.modelViewEditor';
private _modelViewMap = new Map<string, HTMLElement>();
private _editorFrame: HTMLElement;
constructor( constructor(
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService, @IThemeService themeService: IThemeService
@IInstantiationService private _instantiationService: IInstantiationService
) { ) {
super(ModelViewEditor.ID, telemetryService, themeService); super(ModelViewEditor.ID, telemetryService, themeService);
} }
@@ -36,6 +31,7 @@ export class ModelViewEditor extends BaseEditor {
* Called to create the editor in the parent builder. * Called to create the editor in the parent builder.
*/ */
public createEditor(parent: Builder): void { public createEditor(parent: Builder): void {
this._editorFrame = parent.getHTMLElement();
} }
/** /**
@@ -44,23 +40,43 @@ export class ModelViewEditor extends BaseEditor {
public focus(): void { public focus(): void {
} }
public setInput(input: ModelViewInput, options?: EditorOptions): TPromise<void, any> { async setInput(input: ModelViewInput, options?: EditorOptions): TPromise<void, any> {
if (this.input && this.input.matches(input)) { if (this.input && this.input.matches(input)) {
return TPromise.as(undefined); return TPromise.as(undefined);
} }
const parentElement = this.getContainer().getHTMLElement(); const parentElement = this.getContainer().getHTMLElement();
$(parentElement).clearChildren(); if (this.input instanceof ModelViewInput) {
if (this.input.container) {
if (!this._modelViewMap.get(input.modelViewId)) { if (this.input.options && this.input.options.retainContextWhenHidden) {
let modelViewContainer = DOM.$('div.model-view-container'); this.input.container.style.visibility = 'hidden';
let dialogPane = new DialogPane(input.title, input.modelViewId, () => undefined, this._instantiationService); } else {
dialogPane.createBody(modelViewContainer); parentElement.removeChild(this.input.container);
this._modelViewMap.set(input.modelViewId, modelViewContainer); }
}
} }
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. * To be called when the container of this editor changes size.
*/ */
public layout(dimension: Dimension): void { 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)
}
}
} }
} }

View File

@@ -6,12 +6,22 @@
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { IEditorModel } from 'vs/platform/editor/common/editor'; import { IEditorModel } from 'vs/platform/editor/common/editor';
import { EditorInput } from 'vs/workbench/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 { export class ModelViewInput extends EditorInput {
public static ID: string = 'workbench.editorinputs.ModelViewEditorInput'; 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(); super();
} }
@@ -34,4 +44,32 @@ export class ModelViewInput extends EditorInput {
public getName(): string { public getName(): string {
return this._title; 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();
}
} }

View File

@@ -666,7 +666,7 @@ declare module 'sqlops' {
/** /**
* Create a new model view editor * 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 { export interface ModelViewEditor extends window.modelviewdialog.ModelViewPanel {
@@ -676,4 +676,11 @@ declare module 'sqlops' {
openEditor(position?: vscode.ViewColumn): Thenable<void>; openEditor(position?: vscode.ViewColumn): Thenable<void>;
} }
} }
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;
}
} }

View File

@@ -76,13 +76,14 @@ class ModelViewEditorImpl extends ModelViewPanelImpl implements sqlops.workspace
extHostModelViewDialog: ExtHostModelViewDialog, extHostModelViewDialog: ExtHostModelViewDialog,
extHostModelView: ExtHostModelViewShape, extHostModelView: ExtHostModelViewShape,
private _proxy: MainThreadModelViewDialogShape, private _proxy: MainThreadModelViewDialogShape,
private _title: string private _title: string,
private _options: sqlops.ModelViewEditorOptions
) { ) {
super('modelViewEditor', extHostModelViewDialog, extHostModelView); super('modelViewEditor', extHostModelViewDialog, extHostModelView);
} }
public openEditor(position?: vscode.ViewColumn): Thenable<void> { public openEditor(position?: vscode.ViewColumn): Thenable<void> {
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); this._proxy.$closeDialog(handle);
} }
public createModelViewEditor(title: string): sqlops.workspace.ModelViewEditor { public createModelViewEditor(title: string, options?: sqlops.ModelViewEditorOptions): sqlops.workspace.ModelViewEditor {
let editor = new ModelViewEditorImpl(this, this._extHostModelView, this._proxy, title); let editor = new ModelViewEditorImpl(this, this._extHostModelView, this._proxy, title, options);
editor.handle = this.getHandle(editor); editor.handle = this.getHandle(editor);
return editor; return editor;
} }

View File

@@ -17,6 +17,7 @@ import { IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails,
import { ModelViewInput } from 'sql/parts/modelComponents/modelEditor/modelViewInput'; import { ModelViewInput } from 'sql/parts/modelComponents/modelEditor/modelViewInput';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as sqlops from 'sqlops';
@extHostNamedCustomer(SqlMainContext.MainThreadModelViewDialog) @extHostNamedCustomer(SqlMainContext.MainThreadModelViewDialog)
export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape { export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape {
@@ -31,20 +32,20 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
constructor( constructor(
context: IExtHostContext, context: IExtHostContext,
@IInstantiationService instatiationService: IInstantiationService, @IInstantiationService private _instatiationService: IInstantiationService,
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService @IWorkbenchEditorService private _editorService: IWorkbenchEditorService
) { ) {
this._proxy = context.getProxy(SqlExtHostContext.ExtHostModelViewDialog); this._proxy = context.getProxy(SqlExtHostContext.ExtHostModelViewDialog);
this._dialogService = new CustomDialogService(instatiationService); this._dialogService = new CustomDialogService(_instatiationService);
} }
public dispose(): void { public dispose(): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
public $openEditor(modelViewId: string, title: string, position?: vscode.ViewColumn): Thenable<void> { public $openEditor(modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
let input = new ModelViewInput(title, modelViewId); let input = this._instatiationService.createInstance(ModelViewInput, title, modelViewId, options);
let editorOptions = { let editorOptions = {
preserveFocus: true, preserveFocus: true,
pinned: true pinned: true

View File

@@ -325,8 +325,8 @@ export function createApiFactory(
const workspace: typeof sqlops.workspace = { const workspace: typeof sqlops.workspace = {
onDidOpenDashboard: extHostDashboard.onDidOpenDashboard, onDidOpenDashboard: extHostDashboard.onDidOpenDashboard,
onDidChangeToDashboard: extHostDashboard.onDidChangeToDashboard, onDidChangeToDashboard: extHostDashboard.onDidChangeToDashboard,
createModelViewEditor(title: string): sqlops.workspace.ModelViewEditor { createModelViewEditor(title: string, options?: sqlops.ModelViewEditorOptions): sqlops.workspace.ModelViewEditor {
return extHostModelViewDialog.createModelViewEditor(title); return extHostModelViewDialog.createModelViewEditor(title, options);
} }
}; };

View File

@@ -559,7 +559,7 @@ export interface ExtHostModelViewDialogShape {
} }
export interface MainThreadModelViewDialogShape extends IDisposable { export interface MainThreadModelViewDialogShape extends IDisposable {
$openEditor(modelViewId: string, title: string, position?: vscode.ViewColumn): Thenable<void>; $openEditor(modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void>;
$openDialog(handle: number): Thenable<void>; $openDialog(handle: number): Thenable<void>;
$closeDialog(handle: number): Thenable<void>; $closeDialog(handle: number): Thenable<void>;
$setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable<void>; $setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable<void>;