dialog assisted notebooks (#6564)

This commit is contained in:
Alan Ren
2019-08-05 16:01:34 -07:00
committed by GitHub
parent 2431bb8e37
commit 2bb8806da6
13 changed files with 1074 additions and 225 deletions

View File

@@ -27,11 +27,51 @@ export interface ResourceTypeOptionValue {
}
export interface DeploymentProvider {
title: string;
dialog: DialogInfo;
notebook: string | NotebookInfo;
requiredTools: ToolRequirementInfo[];
when: string;
}
export interface DialogInfo {
title: string;
name: string;
tabs: DialogTabInfo[];
}
export interface DialogTabInfo {
title: string;
sections: DialogSectionInfo[];
}
export interface DialogSectionInfo {
title: string;
fields: DialogFieldInfo[];
}
export interface DialogFieldInfo {
label: string;
variableName: string;
type: FieldType;
defaultValue: string;
confirmationRequired: boolean;
confirmationLabel: string;
min?: number;
max?: number;
required: boolean;
options: string[];
placeHolder: string;
}
export enum FieldType {
Text = 'text',
Number = 'number',
DateTimeText = 'datetime_text',
Password = 'password',
Options = 'options'
}
export interface NotebookInfo {
win32: string;
darwin: string;
@@ -55,4 +95,4 @@ export interface ITool {
readonly displayName: string;
readonly description: string;
readonly type: ToolType;
}
}

View File

@@ -5,7 +5,7 @@
'use strict';
import vscode = require('vscode');
import { ResourceDeploymentDialog } from './ui/resourceDeploymentDialog';
import { ResourceTypePickerDialog } from './ui/resourceDeploymentDialog';
import { ToolsService } from './services/toolsService';
import { NotebookService } from './services/notebookService';
import { ResourceTypeService } from './services/resourceTypeService';
@@ -33,7 +33,7 @@ export function activate(context: vscode.ExtensionContext) {
if (filtered.length !== 1) {
vscode.window.showErrorMessage(localize('resourceDeployment.UnknownResourceType', 'The resource type: {0} is not defined', resourceTypeName));
} else {
const dialog = new ResourceDeploymentDialog(context, notebookService, toolsService, resourceTypeService, filtered[0]);
const dialog = new ResourceTypePickerDialog(context, notebookService, toolsService, resourceTypeService, filtered[0]);
dialog.open();
}
};
@@ -52,4 +52,4 @@ export function activate(context: vscode.ExtensionContext) {
// this method is called when your extension is deactivated
export function deactivate(): void {
}
}

View File

@@ -84,4 +84,4 @@ export class NotebookService implements INotebookService {
});
});
}
}
}

View File

@@ -0,0 +1,135 @@
/*---------------------------------------------------------------------------------------------
* 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 * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import { DialogBase } from './dialogBase';
import { INotebookService } from '../services/notebookService';
import { DeploymentProvider, DialogFieldInfo, FieldType } from '../interfaces';
const localize = nls.loadMessageBundle();
export class DeploymentDialog extends DialogBase {
private variables: { [s: string]: string | undefined; } = {};
constructor(context: vscode.ExtensionContext,
private notebookService: INotebookService,
private deploymentProvider: DeploymentProvider) {
super(context, deploymentProvider.dialog.title, deploymentProvider.dialog.name, false);
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', 'Open Notebook');
this._dialogObject.okButton.onClick(() => this.onComplete());
}
protected initializeDialog() {
const tabs: azdata.window.DialogTab[] = [];
this.deploymentProvider.dialog.tabs.forEach(tabInfo => {
const tab = azdata.window.createTab(tabInfo.title);
tab.registerContent((view: azdata.ModelView) => {
const sections: azdata.FormComponentGroup[] = [];
tabInfo.sections.forEach(sectionInfo => {
const fields: azdata.FormComponent[] = [];
sectionInfo.fields.forEach(fieldInfo => {
this.addField(view, fields, fieldInfo);
});
sections.push({ title: sectionInfo.title, components: fields });
});
const formBuilder = view.modelBuilder.formContainer().withFormItems(
sections,
{
horizontal: false
}
);
const form = formBuilder.withLayout({ width: '100%' }).component();
return view.initializeModel(form);
});
tabs.push(tab);
});
this._dialogObject.content = tabs;
}
private addField(view: azdata.ModelView, fields: azdata.FormComponent[], fieldInfo: DialogFieldInfo): void {
switch (fieldInfo.type) {
case FieldType.Options:
this.addOptionsTypeField(view, fields, fieldInfo);
break;
case FieldType.DateTimeText:
case FieldType.Number:
case FieldType.Password:
case FieldType.Text:
this.addInputTypeField(view, fields, fieldInfo);
break;
}
}
private addOptionsTypeField(view: azdata.ModelView, fields: azdata.FormComponent[], fieldInfo: DialogFieldInfo): void {
const component = view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({ values: fieldInfo.options, value: fieldInfo.defaultValue }).component();
this.variables[fieldInfo.variableName] = fieldInfo.defaultValue;
this._toDispose.push(component.onValueChanged(() => { this.variables[fieldInfo.variableName] = <string>component.value; }));
fields.push({ title: fieldInfo.label, component: component });
}
private addInputTypeField(view: azdata.ModelView, fields: azdata.FormComponent[], fieldInfo: DialogFieldInfo): void {
let inputType: azdata.InputBoxInputType = 'text';
let defaultValue: string | undefined = fieldInfo.defaultValue;
switch (fieldInfo.type) {
case FieldType.Number:
inputType = 'number';
break;
case FieldType.Password:
inputType = 'password';
break;
case FieldType.DateTimeText:
defaultValue = fieldInfo.defaultValue + new Date().toISOString().slice(0, 19).replace(/[^0-9]/g, '');
break;
}
const component = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
value: defaultValue, ariaLabel: fieldInfo.label, inputType: inputType, min: fieldInfo.min, max: fieldInfo.max, required: fieldInfo.required, placeHolder: fieldInfo.placeHolder
}).component();
this.variables[fieldInfo.variableName] = defaultValue;
this._toDispose.push(component.onTextChanged(() => { this.variables[fieldInfo.variableName] = component.value; }));
fields.push({ title: fieldInfo.label, component: component });
if (fieldInfo.type === FieldType.Password && fieldInfo.confirmationRequired) {
const confirmPasswordComponent = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ ariaLabel: fieldInfo.confirmationLabel, inputType: inputType, required: true }).component();
fields.push({ title: fieldInfo.confirmationLabel, component: confirmPasswordComponent });
this._dialogObject.registerCloseValidator((): boolean => {
const passwordMatches = component.value === confirmPasswordComponent.value;
if (!passwordMatches) {
this._dialogObject.message = { level: azdata.window.MessageLevel.Error, text: localize('passwordNotMatch', "{0} doesn't match the confirmation password", fieldInfo.label) };
}
return passwordMatches;
});
const checkPassword = (): void => {
const passwordMatches = component.value === confirmPasswordComponent.value;
if (passwordMatches) {
this._dialogObject.message = { text: '' };
}
};
this._toDispose.push(component.onTextChanged(() => {
checkPassword();
}));
this._toDispose.push(confirmPasswordComponent.onTextChanged(() => {
checkPassword();
}));
}
}
private onComplete(): void {
Object.keys(this.variables).forEach(key => {
process.env[key] = this.variables[key];
});
this.notebookService.launchNotebook(this.deploymentProvider.notebook);
this.dispose();
}
}

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* 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 * as azdata from 'azdata';
import * as vscode from 'vscode';
export abstract class DialogBase {
protected _toDispose: vscode.Disposable[] = [];
protected _dialogObject: azdata.window.Dialog;
constructor(protected extensionContext: vscode.ExtensionContext, dialogTitle: string, dialogName: string, isWide: boolean = false) {
this._dialogObject = azdata.window.createModelViewDialog(dialogTitle, dialogName, isWide);
this._dialogObject.cancelButton.onClick(() => this.onCancel());
}
protected abstract initializeDialog(): void;
public open(): void {
this.initializeDialog();
azdata.window.openDialog(this._dialogObject);
}
protected onCancel(): void {
this.dispose();
}
protected dispose(): void {
this._toDispose.forEach(disposable => disposable.dispose());
}
}

View File

@@ -11,13 +11,13 @@ import * as vscode from 'vscode';
import { ResourceType, DeploymentProvider } from '../interfaces';
import { IToolsService } from '../services/toolsService';
import { INotebookService } from '../services/notebookService';
import { DialogBase } from './dialogBase';
import { DeploymentDialog } from './deploymentDialog';
const localize = nls.loadMessageBundle();
export class ResourceDeploymentDialog {
export class ResourceTypePickerDialog extends DialogBase {
private _selectedResourceType: ResourceType;
private _toDispose: vscode.Disposable[] = [];
private _dialogObject: azdata.window.Dialog;
private _resourceTypeCards: azdata.CardComponent[] = [];
private _view!: azdata.ModelView;
private _resourceDescriptionLabel!: azdata.TextComponent;
@@ -26,19 +26,18 @@ export class ResourceDeploymentDialog {
private _cardResourceTypeMap: Map<string, azdata.CardComponent> = new Map();
private _optionDropDownMap: Map<string, azdata.DropDownComponent> = new Map();
constructor(private context: vscode.ExtensionContext,
constructor(context: vscode.ExtensionContext,
private notebookService: INotebookService,
private toolsService: IToolsService,
private resourceTypeService: IResourceTypeService,
resourceType: ResourceType) {
super(context, localize('resourceTypePickerDialog.title', "Select the deployment options"), 'ResourceTypePickerDialog', true);
this._selectedResourceType = resourceType;
this._dialogObject = azdata.window.createModelViewDialog(localize('deploymentDialog.title', 'Select the deployment options'), 'resourceDeploymentDialog', true);
this._dialogObject.cancelButton.onClick(() => this.onCancel());
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', 'Open Notebook');
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', 'Select');
this._dialogObject.okButton.onClick(() => this.onComplete());
}
private initializeDialog() {
initializeDialog() {
let tab = azdata.window.createTab('');
tab.registerContent((view: azdata.ModelView) => {
const tableWidth = 1126;
@@ -98,17 +97,12 @@ export class ResourceDeploymentDialog {
this._dialogObject.content = [tab];
}
public open(): void {
this.initializeDialog();
azdata.window.openDialog(this._dialogObject);
}
private addCard(resourceType: ResourceType): void {
const card = this._view.modelBuilder.card().withProperties<azdata.CardProperties>({
cardType: azdata.CardType.VerticalButton,
iconPath: {
dark: this.context.asAbsolutePath(resourceType.icon.dark),
light: this.context.asAbsolutePath(resourceType.icon.light)
dark: this.extensionContext.asAbsolutePath(resourceType.icon.dark),
light: this.extensionContext.asAbsolutePath(resourceType.icon.light)
},
label: resourceType.displayName,
selected: (this._selectedResourceType && this._selectedResourceType.name === resourceType.name)
@@ -180,17 +174,14 @@ export class ResourceDeploymentDialog {
return this._selectedResourceType.getProvider(options)!;
}
private onCancel(): void {
this.dispose();
}
private onComplete(): void {
const provider = this.getCurrentProvider();
this.notebookService.launchNotebook(provider.notebook);
if (provider.dialog) {
const dialog = new DeploymentDialog(this.extensionContext, this.notebookService, provider);
dialog.open();
} else {
this.notebookService.launchNotebook(provider.notebook);
}
this.dispose();
}
private dispose(): void {
this._toDispose.forEach(disposable => disposable.dispose());
}
}