GenerateToNotebook and Deploy buttons for Notebook Wizards (#12656)

* enable userChooses how to run notebook

* arc ext changes

* nb fixes

* working version

* revert unneeded changes

* fix comments

* Update interfaces.ts

* fix comments

* fix comments

* fix comments

* runAllCells instead of background execute

* pr feedback

* PR feedback

* pr feedback

* arc ext changes for new WizardInfo syntax

* fix doc comments

* pr feedback
This commit is contained in:
Arvind Ranasaria
2020-09-29 18:12:30 -07:00
committed by GitHub
parent fd5acf7ab1
commit b8de69dfac
15 changed files with 203 additions and 75 deletions

View File

@@ -56,7 +56,7 @@
{
"cell_type": "markdown",
"source": [
"### **Setup and Check Prerequisites**"
"### **Setup**"
],
"metadata": {
"azdata_cell_guid": "e3dd8e75-e15f-44b4-81fc-1f54d6f0b1e2"

View File

@@ -144,9 +144,13 @@
"notebookWizard": {
"notebook": "./notebooks/arcDeployment/deploy.arc.data.controller.ipynb",
"type": "new-arc-control-plane",
"runNotebook": false,
"doneAction": {
"label": "%deploy.done.action%"
},
"scriptAction": {
"label": "%deploy.script.action%"
},
"codeCellInsertionPosition": 5,
"actionText": "%deploy.script.to.notebook.action%",
"title": "%arc.data.controller.new.wizard.title%",
"name": "arc.data.controller.new.wizard",
"labelPosition": "left",
@@ -521,9 +525,13 @@
{
"notebookWizard": {
"notebook": "./notebooks/arcDeployment/deploy.sql.existing.arc.ipynb",
"runNotebook": false,
"doneAction": {
"label": "%deploy.done.action%"
},
"scriptAction": {
"label": "%deploy.script.action%"
},
"codeCellInsertionPosition": 5,
"actionText": "%deploy.script.to.notebook.action%",
"title": "%arc.sql.wizard.title%",
"name": "arc.sql.wizard",
"labelPosition": "left",
@@ -682,9 +690,13 @@
{
"notebookWizard": {
"notebook": "./notebooks/arcDeployment/deploy.postgres.existing.arc.ipynb",
"runNotebook": false,
"doneAction": {
"label": "%deploy.done.action%"
},
"scriptAction": {
"label": "%deploy.script.action%"
},
"codeCellInsertionPosition": 5,
"actionText": "%deploy.script.to.notebook.action%",
"title": "%arc.postgres.wizard.title%",
"name": "arc.postgres.wizard",
"labelPosition": "left",

View File

@@ -58,8 +58,8 @@
"arc.data.controller.summary.location": "Location",
"arc.data.controller.arc.data.controller.agreement": "I accept {0} and {1}.",
"microsoft.agreement.privacy.statement":"Microsoft Privacy Statement",
"deploy.script.to.notebook.action":"Script to notebook",
"deploy.script.action":"Script to notebook",
"deploy.done.action":"Deploy",
"resource.type.arc.sql.display.name": "Azure SQL managed instance - Azure Arc (preview)",
"resource.type.arc.postgres.display.name": "PostgreSQL Hyperscale server groups - Azure Arc (preview)",

View File

@@ -114,21 +114,53 @@ export interface BdcWizardInfo {
notebook: string | NotebookPathInfo;
type: BdcDeploymentType;
}
/**
* An object that configures Script and Done buttons of the wizard.
*/
export interface WizardAction {
label?: string
}
/**
* This object defines the shape, form and behavior of a Notebook Wizard.
*/
export interface NotebookWizardInfo extends WizardInfoBase {
/**
* path to the template python notebook that is modified with variables collected in the wizard. A copy of this modified notebook is executed at the end of the wizard either from commonadline of from notebook editor in ADS.
*/
notebook: string | NotebookPathInfo;
runNotebook?: boolean;
/**
* 0 based position number where the variables values are inserted into the notebook as python statements.
*/
codeCellInsertionPosition?: number;
/**
* This array defines the json for the pages of this wizard.
*/
pages: NotebookWizardPageInfo[]
}
export interface WizardInfoBase extends FieldInfoBase {
taskName?: string;
type?: DeploymentType;
actionText?: string;
/**
* done button attributes.
*/
doneAction: WizardAction;
/**
* script button attributes.
*/
scriptAction?: WizardAction;
/**
* title displayed on every page of the wizard
*/
title: string;
name?: string;
/**
* This array defines the json for the pages of this wizard.
*/
pages: PageInfoBase[];
/**
* if true an auto generated summary page is inserted at the end of the wizard
*/
isSummaryPageAutoGenerated?: boolean
}
@@ -401,11 +433,7 @@ export const enum BdcDeploymentType {
ExistingOpenShift = 'existing-openshift'
}
export const enum ArcDeploymentType {
NewControlPlane = 'new-control-plane'
}
export type DeploymentType = ArcDeploymentType | BdcDeploymentType;
export type DeploymentType = BdcDeploymentType;
export interface Command {
command: string;

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
import { FieldType, OptionsType } from './interfaces';
import { OptionsSourceType } from './helpers/optionSources';
import { FieldType, OptionsType } from './interfaces';
const localize = nls.loadMessageBundle();
@@ -37,3 +37,5 @@ export const optionsTypeRadioOrDropdown = localize('optionsTypeRadioOrDropdown',
export const azdataEulaNotAccepted = localize('azdataEulaNotAccepted', "Deployment cannot continue. Azure Data CLI license terms have not yet been accepted. Please accept the EULA to enable the features that requires Azure Data CLI.");
export const azdataEulaDeclined = localize('azdataEulaDeclined', "Deployment cannot continue. Azure Data CLI license terms were declined.You can either Accept EULA to continue or Cancel this operation");
export const acceptEulaAndSelect = localize('deploymentDialog.RecheckEulaButton', "Accept EULA & Select");
export const scriptToNotebook = localize('ui.ScriptToNotebookButton', "Script");
export const deployNotebook = localize('ui.DeployButton', "Run");

View File

@@ -33,9 +33,9 @@ export interface NotebookExecutionResult {
}
export interface INotebookService {
launchNotebook(notebook: string | NotebookPathInfo): Promise<azdata.nb.NotebookEditor>;
launchNotebookWithEdits(notebook: string | NotebookPathInfo, cellStatements: string[], insertionPosition?: number): Promise<void>;
launchNotebookWithContent(title: string, content: string): Promise<azdata.nb.NotebookEditor>;
openNotebook(notebook: string | NotebookPathInfo): Promise<azdata.nb.NotebookEditor>;
openNotebookWithEdits(notebook: string | NotebookPathInfo, cellStatements: string[], insertionPosition?: number): Promise<azdata.nb.NotebookEditor>;
openNotebookWithContent(title: string, content: string): Promise<azdata.nb.NotebookEditor>;
getNotebook(notebook: string | NotebookPathInfo): Promise<Notebook>;
getNotebookPath(notebook: string | NotebookPathInfo): string;
executeNotebook(notebook: any, env?: NodeJS.ProcessEnv): Promise<NotebookExecutionResult>;
@@ -47,38 +47,39 @@ export class NotebookService implements INotebookService {
constructor(private platformService: IPlatformService, private extensionPath: string) { }
/**
* Launch notebook with file path
* Open notebook with file path
* @param notebook the path of the notebook
*/
async launchNotebook(notebook: string | NotebookPathInfo): Promise<azdata.nb.NotebookEditor> {
async openNotebook(notebook: string | NotebookPathInfo): Promise<azdata.nb.NotebookEditor> {
const notebookPath = await this.getNotebookFullPath(notebook);
return await this.showNotebookAsUntitled(notebookPath);
}
/**
* Inserts cell code given by {@param cellStatements} in an existing notebook given by {@param notebook} file path at the location
* {@param insertionPosition} and then launches the edited notebook.
* {@param insertionPosition} and then opens the edited notebook.
*
* @param notebook - the path to notebook that needs to be launched
* @param notebook - the path to notebook that needs to be opened
* @param cellStatements - array of statements to be inserted in a cell
* @param insertionPosition - the position at which cells are inserted. Default is a new cell at the beginning of the notebook.
*/
async launchNotebookWithEdits(notebook: string, cellStatements: string[], insertionPosition: number = 0): Promise<void> {
const openedNotebook = await this.launchNotebook(notebook);
async openNotebookWithEdits(notebook: string, cellStatements: string[], insertionPosition: number = 0): Promise<azdata.nb.NotebookEditor> {
const openedNotebook = await this.openNotebook(notebook);
await openedNotebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => {
editBuilder.insertCell({
cell_type: 'code',
source: cellStatements
}, insertionPosition);
});
return openedNotebook;
}
/**
* Launch notebook with file path
* Open notebook with given contents
* @param title the title of the notebook
* @param content the notebook content
*/
async launchNotebookWithContent(title: string, content: string): Promise<azdata.nb.NotebookEditor> {
async openNotebookWithContent(title: string, content: string): Promise<azdata.nb.NotebookEditor> {
const uri: vscode.Uri = vscode.Uri.parse(`untitled:${this.findNextUntitledEditorName(title)}`);
return await azdata.nb.showNotebookDocument(uri, {
connectionProfile: undefined,
@@ -150,11 +151,11 @@ export class NotebookService implements INotebookService {
platformService.logToOutputChannel(taskFailedMessage);
if (selectedOption === viewErrorDetail) {
try {
await this.launchNotebookWithContent(`${tempNotebookPrefix}-${getDateTimeString()}`, result.outputNotebook);
await this.openNotebookWithContent(`${tempNotebookPrefix}-${getDateTimeString()}`, result.outputNotebook);
} catch (error) {
const launchNotebookError = localize('resourceDeployment.FailedToOpenNotebook', "An error occurred launching the output notebook. {1}{2}.", EOL, getErrorMessage(error));
platformService.logToOutputChannel(launchNotebookError);
vscode.window.showErrorMessage(launchNotebookError);
const openNotebookError = localize('resourceDeployment.FailedToOpenNotebook', "An error occurred opening the output notebook. {1}{2}.", EOL, getErrorMessage(error));
platformService.logToOutputChannel(openNotebookError);
vscode.window.showErrorMessage(openNotebookError);
}
}
} else {

View File

@@ -10,17 +10,17 @@ import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { DeploymentProvider, instanceOfAzureSQLVMDeploymentProvider, instanceOfCommandDeploymentProvider, instanceOfDialogDeploymentProvider, instanceOfDownloadDeploymentProvider, instanceOfNotebookBasedDialogInfo, instanceOfNotebookDeploymentProvider, instanceOfNotebookWizardDeploymentProvider, instanceOfWebPageDeploymentProvider, instanceOfWizardDeploymentProvider, NotebookInfo, NotebookPathInfo, ResourceType, ResourceTypeOption } from '../interfaces';
import { DeployAzureSQLVMWizard } from '../ui/deployAzureSQLVMWizard/deployAzureSQLVMWizard';
import { DeployClusterWizard } from '../ui/deployClusterWizard/deployClusterWizard';
import { DeploymentInputDialog } from '../ui/deploymentInputDialog';
import { NotebookWizard } from '../ui/notebookWizard/notebookWizard';
import { AzdataService } from './azdataService';
import { KubeService } from './kubeService';
import { INotebookService } from './notebookService';
import { IPlatformService } from './platformService';
import { IToolsService } from './toolsService';
import { ResourceType, ResourceTypeOption, NotebookPathInfo, DeploymentProvider, instanceOfWizardDeploymentProvider, instanceOfDialogDeploymentProvider, instanceOfNotebookDeploymentProvider, instanceOfDownloadDeploymentProvider, instanceOfWebPageDeploymentProvider, instanceOfCommandDeploymentProvider, instanceOfNotebookBasedDialogInfo, instanceOfNotebookWizardDeploymentProvider, NotebookInfo, instanceOfAzureSQLVMDeploymentProvider } from '../interfaces';
import { DeployClusterWizard } from '../ui/deployClusterWizard/deployClusterWizard';
import { DeploymentInputDialog } from '../ui/deploymentInputDialog';
import { KubeService } from './kubeService';
import { AzdataService } from './azdataService';
import { NotebookWizard } from '../ui/notebookWizard/notebookWizard';
import { DeployAzureSQLVMWizard } from '../ui/deployAzureSQLVMWizard/deployAzureSQLVMWizard';
const localize = nls.loadMessageBundle();
export interface IResourceTypeService {
@@ -257,7 +257,7 @@ export class ResourceTypeService implements IResourceTypeService {
const dialog = new DeploymentInputDialog(this.notebookService, this.platformService, this.toolsService, provider.dialog);
dialog.open();
} else if (instanceOfNotebookDeploymentProvider(provider)) {
this.notebookService.launchNotebook(provider.notebook);
this.notebookService.openNotebook(provider.notebook);
} else if (instanceOfDownloadDeploymentProvider(provider)) {
const taskName = localize('resourceDeployment.DownloadAndLaunchTaskName', "Download and launch installer, URL: {0}", provider.downloadUrl);
azdata.tasks.startBackgroundOperation({

View File

@@ -73,7 +73,7 @@ export class DeployAzureSQLVMWizard extends WizardBase<DeployAzureSQLVMWizard, W
const variableValueStatements = this.model.getCodeCellContentForNotebook();
const insertionPosition = 2; // Cell number 5 is the position where the python variable setting statements need to be inserted in this.wizardInfo.notebook.
try {
await this.notebookService.launchNotebookWithEdits(this.wizardInfo.notebook, variableValueStatements, insertionPosition);
await this.notebookService.openNotebookWithEdits(this.wizardInfo.notebook, variableValueStatements, insertionPosition);
} catch (error) {
vscode.window.showErrorMessage(error);
}

View File

@@ -144,7 +144,7 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, WizardP
const variableValueStatements = this.model.getCodeCellContentForNotebook(this.toolsService.toolsForCurrentProvider);
const insertionPosition = 5; // Cell number 5 is the position where the python variable setting statements need to be inserted in this.wizardInfo.notebook.
try {
await this.notebookService.launchNotebookWithEdits(this.wizardInfo.notebook, variableValueStatements, insertionPosition);
await this.notebookService.openNotebookWithEdits(this.wizardInfo.notebook, variableValueStatements, insertionPosition);
} catch (error) {
vscode.window.showErrorMessage(getErrorMessage(error));
}

View File

@@ -105,7 +105,7 @@ export class DeploymentInputDialog extends DialogBase {
const notebook = Array.isArray(this.dialogInfo.notebook) ?
this.dialogInfo.notebook.find(nb => nb.type === model.getStringValue(NotebookTypeVariableName))?.path :
this.dialogInfo.notebook;
this.notebookService.launchNotebook(notebook!).catch(error => {
this.notebookService.openNotebook(notebook!).catch(error => {
vscode.window.showErrorMessage(error);
});
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as loc from '../../localizedConstants';
import { INotebookService, Notebook } from '../../services/notebookService';
import { IToolsService } from '../../services/toolsService';
import { Model } from '../model';
@@ -14,8 +14,6 @@ import { IPlatformService } from './../../services/platformService';
import { NotebookWizardAutoSummaryPage } from './notebookWizardAutoSummaryPage';
import { NotebookWizardPage } from './notebookWizardPage';
const localize = nls.loadMessageBundle();
export class NotebookWizard extends WizardBase<NotebookWizard, NotebookWizardPage, Model> {
private _inputComponents: InputComponents = {};
@@ -40,7 +38,8 @@ export class NotebookWizard extends WizardBase<NotebookWizard, NotebookWizardPag
if (this._wizardInfo.codeCellInsertionPosition === undefined) {
this._wizardInfo.codeCellInsertionPosition = 0;
}
this.wizardObject.doneButton.label = _wizardInfo.actionText || this.wizardObject.doneButton.label;
this.wizardObject.doneButton.label = _wizardInfo.doneAction?.label || loc.deployNotebook;
this.wizardObject.generateScriptButton.label = _wizardInfo.scriptAction?.label || loc.scriptToNotebook;
}
public get deploymentType(): DeploymentType | undefined {
@@ -49,17 +48,37 @@ export class NotebookWizard extends WizardBase<NotebookWizard, NotebookWizardPag
protected initialize(): void {
this.setPages(this.getPages());
this.wizardObject.generateScriptButton.hidden = true;
this.wizardInfo.actionText = this.wizardInfo.actionText || localize('notebookWizard.ScriptToNotebook', "Script to Notebook");
this.wizardObject.doneButton.label = this.wizardInfo.actionText;
}
protected onCancel(): void {
}
protected async onGenerateScript(): Promise<void> {
try {
const notebook = await this.prepareNotebookAndEnvironment();
await this.openNotebook(notebook);
} catch (error) {
vscode.window.showErrorMessage(error);
}
}
protected async onOk(): Promise<void> {
try {
const notebook = await this.prepareNotebookAndEnvironment();
const openedNotebook = await this.openNotebook(notebook);
openedNotebook.runAllCells();
} catch (error) {
vscode.window.showErrorMessage(error);
}
}
private async openNotebook(notebook: Notebook) {
const notebookPath = this.notebookService.getNotebookPath(this.wizardInfo.notebook);
return await this.notebookService.openNotebookWithContent(notebookPath, JSON.stringify(notebook, undefined, 4));
}
private async prepareNotebookAndEnvironment() {
await setModelValues(this.inputComponents, this.model);
const env: NodeJS.ProcessEnv = {};
const env: NodeJS.ProcessEnv = process.env;
this.model.setEnvironmentVariables(env, (varName) => {
const isPassword = !!this.inputComponents[varName]?.isPassword;
return isPassword;
@@ -85,17 +104,7 @@ export class NotebookWizard extends WizardBase<NotebookWizard, NotebookWizardPag
execution_count: 0
}
);
try {
if (this.wizardInfo.runNotebook) {
this.notebookService.backgroundExecuteNotebook(this.wizardInfo.taskName, notebook, 'deploy', this.platformService, env);
} else {
Object.assign(process.env, env);
const notebookPath = this.notebookService.getNotebookPath(this.wizardInfo.notebook);
await this.notebookService.launchNotebookWithContent(notebookPath, JSON.stringify(notebook, undefined, 4));
}
} catch (error) {
vscode.window.showErrorMessage(error);
}
return notebook;
}
private getPages(): NotebookWizardPage[] {

View File

@@ -9,6 +9,7 @@ import * as nls from 'vscode-nls';
import { NotebookWizardPageInfo } from '../../interfaces';
import { initializeWizardPage, InputComponentInfo, setModelValues, Validator } from '../modelViewUtils';
import { WizardPageBase } from '../wizardPageBase';
import { WizardPageInfo } from '../wizardPageInfo';
import { NotebookWizard } from './notebookWizard';
const localize = nls.loadMessageBundle();
@@ -32,6 +33,20 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
);
}
/**
* If the return value is true then done button should be visible to the user
*/
private get isDoneButtonVisible(): boolean {
return !!this.wizard.wizardInfo.doneAction;
}
/**
* If the return value is true then generateScript button should be visible to the user
*/
private get isGenerateScriptButtonVisible(): boolean {
return !!this.wizard.wizardInfo.scriptAction;
}
public initialize(): void {
initializeWizardPage({
container: this.wizard.wizardObject,
@@ -64,7 +79,17 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
});
}
public async onEnter(): Promise<void> {
public async onEnter(pageInfo: WizardPageInfo): Promise<void> {
if (pageInfo.isLastPage) {
// on the last page either one or both of done button and generateScript button are visible depending on configuration of 'runNotebook' in wizard info
this.wizard.wizardObject.doneButton.hidden = !this.isDoneButtonVisible;
this.wizard.wizardObject.generateScriptButton.hidden = !this.isGenerateScriptButtonVisible;
} else {
//on any page but the last page doneButton and generateScriptButton are hidden
this.wizard.wizardObject.doneButton.hidden = true;
this.wizard.wizardObject.generateScriptButton.hidden = true;
}
if (this.pageInfo.isSummaryPage) {
await setModelValues(this.wizard.inputComponents, this.wizard.model);
}

View File

@@ -6,9 +6,10 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { WizardPageBase } from './wizardPageBase';
import { Model } from './model';
import { IToolsService } from '../services/toolsService';
import { Model } from './model';
import { WizardPageBase } from './wizardPageBase';
import { WizardPageInfo } from './wizardPageInfo';
const localize = nls.loadMessageBundle();
export abstract class WizardBase<T, P extends WizardPageBase<T>, M extends Model> {
@@ -21,37 +22,55 @@ export abstract class WizardBase<T, P extends WizardPageBase<T>, M extends Model
return this._model;
}
constructor(private title: string, name: string, private _model: M, public toolsService: IToolsService) {
protected get useGenerateScriptButton(): boolean {
return this._useGenerateScriptButton;
}
constructor(private title: string, name: string, private _model: M, public toolsService: IToolsService, private _useGenerateScriptButton: boolean = false) {
this.wizardObject = azdata.window.createWizard(title, name);
}
public async open(): Promise<void> {
this.initialize();
this.wizardObject.generateScriptButton.hidden = true; // by default generateScriptButton stays hidden.
this.wizardObject.customButtons = this.customButtons;
this.toDispose.push(this.wizardObject.onPageChanged(async (e) => {
let previousPage = this.pages[e.lastPage];
let newPage = this.pages[e.newPage];
await previousPage.onLeave();
await newPage.onEnter();
//if we are changing to the first page from no page before, essentially when we load the wizard for the first time, e.lastPage is -1 and previousPage is undefined.
await previousPage?.onLeave(new WizardPageInfo(e.lastPage, this.pages.length));
if (this.useGenerateScriptButton) {
if (newPage === this.pages.slice(-1)[0]) {
// if newPage is the last page
this.wizardObject.generateScriptButton.hidden = false; //un-hide generateScriptButton on last page
} else {
// if newPage is not the last page
this.wizardObject.generateScriptButton.hidden = true; //hide generateScriptButton if it is not the last page
}
}
await newPage.onEnter(new WizardPageInfo(e.newPage, this.pages.length));
}));
this.toDispose.push(this.wizardObject.doneButton.onClick(async () => {
await this.onOk();
this.dispose();
}));
this.toDispose.push(this.wizardObject.generateScriptButton.onClick(async () => {
await this.onGenerateScript();
this.dispose();
this.wizardObject.close(); // close the wizard. This is already hooked up into doneButton, so it is not needed for that button above.
}));
this.toDispose.push(this.wizardObject.cancelButton.onClick(() => {
this.onCancel();
this.dispose();
}));
await this.wizardObject.open();
if (this.pages && this.pages.length > 0) {
await this.pages[0].onEnter();
}
}
protected abstract initialize(): void;
protected abstract async onOk(): Promise<void>;
protected async onGenerateScript(): Promise<void> { }
protected abstract onCancel(): void;
public addButton(button: azdata.window.Button) {
@@ -62,11 +81,15 @@ export abstract class WizardBase<T, P extends WizardPageBase<T>, M extends Model
this.wizardObject!.pages = pages.map(p => p.pageObject);
this.pages = pages;
this.pages.forEach((page) => {
page.pageObject.onValidityChanged((isValid: boolean) => {
// generateScriptButton is enabled only when the page is valid.
this.wizardObject.generateScriptButton.enabled = isValid;
});
page.initialize();
});
}
private dispose() {
protected dispose() {
let errorOccurred = false;
this.toDispose.forEach((disposable: vscode.Disposable) => {
try {

View File

@@ -5,8 +5,10 @@
import * as azdata from 'azdata';
import { Validator } from './modelViewUtils';
import { WizardPageInfo } from './wizardPageInfo';
export abstract class WizardPageBase<T> {
private _page: azdata.window.WizardPage;
private _validators: Validator[] = [];
@@ -23,9 +25,9 @@ export abstract class WizardPageBase<T> {
return this._wizard;
}
public async onEnter(): Promise<void> { }
public async onEnter(_pageInfo?: WizardPageInfo): Promise<void> { }
public async onLeave(): Promise<void> { }
public async onLeave(_pageInfo?: WizardPageInfo): Promise<void> { }
public abstract initialize(): void;

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class WizardPageInfo {
public get pageCount(): number {
return this._pageCount;
}
public get currentPageId(): number {
return this._currentPageId;
}
public get isFirstPage(): boolean {
return this._currentPageId === 0;
}
public get isLastPage(): boolean {
return this._currentPageId === this._pageCount - 1;
}
constructor(private _currentPageId: number, private _pageCount: number) {
}
}