diff --git a/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts b/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts index ce594382f6..27da41925f 100644 --- a/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts +++ b/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts @@ -73,7 +73,7 @@ export class ModelViewInput extends EditorInput { private createDialogPane(): void { this._dialogPaneContainer = DOM.$('div.model-view-container'); - this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService); + this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService, false); this._dialogPane.createBody(this._dialogPaneContainer); } diff --git a/src/sql/platform/dialog/dialogContainer.component.ts b/src/sql/platform/dialog/dialogContainer.component.ts index 5abd0f0ea6..f562346ffd 100644 --- a/src/sql/platform/dialog/dialogContainer.component.ts +++ b/src/sql/platform/dialog/dialogContainer.component.ts @@ -9,26 +9,36 @@ import 'vs/css!./media/dialogModal'; import { Component, AfterContentInit, ViewChild, Input, Inject, forwardRef, ElementRef } from '@angular/core'; import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component'; import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; +import { DialogPane } from 'sql/platform/dialog/dialogPane'; +import { ComponentEventType } from 'sql/parts/modelComponents/interfaces'; import { Event, Emitter } from 'vs/base/common/event'; -import { ComponentEventType } from '../../parts/modelComponents/interfaces'; export interface DialogComponentParams extends IBootstrapParams { modelViewId: string; validityChangedCallback: (valid: boolean) => void; onLayoutRequested: Event; + dialogPane: DialogPane; } @Component({ selector: 'dialog-modelview-container', providers: [], template: ` - - +
+
+
Step {{_dialogPane.pageNumber}}
+

{{_dialogPane.title}}

+
{{_dialogPane.description}}
+
+ + +
` }) export class DialogContainer implements AfterContentInit { private _onResize = new Emitter(); public readonly onResize: Event = this._onResize.event; + private _dialogPane: DialogPane; public modelViewId: string; @ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent; @@ -41,6 +51,7 @@ export class DialogContainer implements AfterContentInit { this.layout(); } }); + this._dialogPane = this._params.dialogPane; } ngAfterContentInit(): void { diff --git a/src/sql/platform/dialog/dialogModal.ts b/src/sql/platform/dialog/dialogModal.ts index 17e0554575..b869174c23 100644 --- a/src/sql/platform/dialog/dialogModal.ts +++ b/src/sql/platform/dialog/dialogModal.ts @@ -115,7 +115,7 @@ export class DialogModal extends Modal { }); this._dialogPane = new DialogPane(this._dialog.title, this._dialog.content, - valid => this._dialog.notifyValidityChanged(valid), this._instantiationService); + valid => this._dialog.notifyValidityChanged(valid), this._instantiationService, false); this._dialogPane.createBody(body); } diff --git a/src/sql/platform/dialog/dialogPane.ts b/src/sql/platform/dialog/dialogPane.ts index ca77b27ce0..e308e569f1 100644 --- a/src/sql/platform/dialog/dialogPane.ts +++ b/src/sql/platform/dialog/dialogPane.ts @@ -10,12 +10,11 @@ import 'vs/css!./media/dialogModal'; import { NgModuleRef } from '@angular/core'; import { IModalDialogStyles } from 'sql/base/browser/ui/modal/modal'; -import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes'; +import { DialogTab } from 'sql/platform/dialog/dialogTypes'; import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel'; import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService'; import { DialogModule } from 'sql/platform/dialog/dialog.module'; import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.component'; -import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes'; import * as DOM from 'vs/base/browser/dom'; import { Builder } from 'vs/base/browser/builder'; @@ -31,24 +30,21 @@ export class DialogPane extends Disposable implements IThemable { // Validation private _modelViewValidityMap = new Map(); - // HTML Elements private _body: HTMLElement; - private _tabBar: HTMLElement; - private _tabs: HTMLElement[]; - private _tabContent: HTMLElement[]; private _selectedTabIndex: number = 0; //TODO: can be an option private _onTabChange = new Emitter(); private _selectedTabContent: string; + public pageNumber?: number; constructor( - private _title: string, + public title: string, private _content: string | DialogTab[], private _validityChangedCallback: (valid: boolean) => void, - private _instantiationService: IInstantiationService + private _instantiationService: IInstantiationService, + public displayPageTitle: boolean, + public description?: string, ) { super(); - this._tabs = []; - this._tabContent = []; } public createBody(container: HTMLElement): HTMLElement { @@ -73,7 +69,7 @@ export class DialogPane extends Disposable implements IThemable { }); this._tabbedPanel.pushTab({ title: tab.title, - identifier: 'dialogPane.' + this._title + '.' + tabIndex, + identifier: 'dialogPane.' + this.title + '.' + tabIndex, view: { render: (container) => { if (tabContainer.parentElement === this._body) { @@ -119,7 +115,8 @@ export class DialogPane extends Disposable implements IThemable { tab.notifyValidityChanged(valid); } }, - onLayoutRequested: this._onTabChange.event + onLayoutRequested: this._onTabChange.event, + dialogPane: this } as DialogComponentParams, undefined, (moduleRef) => { diff --git a/src/sql/platform/dialog/dialogTypes.ts b/src/sql/platform/dialog/dialogTypes.ts index 9c21640447..ed1e39a9ba 100644 --- a/src/sql/platform/dialog/dialogTypes.ts +++ b/src/sql/platform/dialog/dialogTypes.ts @@ -135,6 +135,7 @@ export class DialogButton implements sqlops.window.modelviewdialog.Button { export class WizardPage extends DialogTab { public customButtons: DialogButton[]; private _enabled: boolean; + private _description: string; private _onUpdate: Emitter = new Emitter(); public readonly onUpdate: Event = this._onUpdate.event; @@ -150,6 +151,15 @@ export class WizardPage extends DialogTab { this._enabled = enabled; this._onUpdate.fire(); } + + public get description(): string { + return this._description; + } + + public set description(description: string) { + this._description = description; + this._onUpdate.fire(); + } } export class Wizard { @@ -171,6 +181,7 @@ export class Wizard { private _onMessageChange = new Emitter(); public readonly onMessageChange = this._onMessageChange.event; private _message: DialogMessage; + public displayPageTitles: boolean; constructor(public title: string) { } diff --git a/src/sql/platform/dialog/media/dialogModal.css b/src/sql/platform/dialog/media/dialogModal.css index a3b9d91c14..d8dc42b2ed 100644 --- a/src/sql/platform/dialog/media/dialogModal.css +++ b/src/sql/platform/dialog/media/dialogModal.css @@ -30,3 +30,19 @@ .footer-button.dialogModal-hidden { margin: 0; } + +.dialogModal-wizardHeader { + padding: 10px 30px; +} + +.dialogModal-wizardHeader h1 { + margin-top: 10px; + margin-bottom: 3px; + font-size: 1.5em; + font-weight: lighter; +} + +.dialogContainer { + display: flex; + flex-direction: column +} diff --git a/src/sql/platform/dialog/wizardModal.ts b/src/sql/platform/dialog/wizardModal.ts index bbb21d2f76..fdc964e630 100644 --- a/src/sql/platform/dialog/wizardModal.ts +++ b/src/sql/platform/dialog/wizardModal.ts @@ -133,18 +133,28 @@ export class WizardModal extends Modal { }); this._wizard.onPageAdded(page => { this.registerPage(page); + this.updatePageNumbers(); this.showPage(this._wizard.currentPage, false); }); this._wizard.onPageRemoved(page => { let dialogPane = this._dialogPanes.get(page); this._dialogPanes.delete(page); + this.updatePageNumbers(); this.showPage(this._wizard.currentPage, false); dialogPane.dispose(); }); + this.updatePageNumbers(); + } + + private updatePageNumbers(): void { + this._wizard.pages.forEach((page, index) => { + let dialogPane = this._dialogPanes.get(page); + dialogPane.pageNumber = index + 1; + }); } private registerPage(page: WizardPage): void { - let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService); + let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService, this._wizard.displayPageTitles, page.description); dialogPane.createBody(this._body); this._dialogPanes.set(page, dialogPane); page.onUpdate(() => this.setButtonsForPage(this._wizard.currentPage)); diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 7c5cc6b34b..f9dd4617dc 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -756,13 +756,18 @@ declare module 'sqlops' { * able to advance to it. Defaults to true. */ enabled: boolean; + + /** + * An optional description for the page. If provided it will be displayed underneath the page title. + */ + description: string; } export interface Wizard { /** * The title of the wizard */ - title: string, + title: string; /** * The wizard's pages. Pages can be added/removed while the dialog is open by using @@ -808,6 +813,12 @@ declare module 'sqlops' { */ customButtons: Button[]; + /** + * When set to false page titles and descriptions will not be displayed at the top + * of each wizard page. The default is true. + */ + displayPageTitles: boolean; + /** * Event fired when the wizard's page changes, containing information about the * previous page and the new page diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index 30efdc1e93..612ebead31 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -199,6 +199,7 @@ export interface IModelViewWizardPageDetails { content: string; enabled: boolean; customButtons: number[]; + description: string; } export interface IModelViewWizardDetails { @@ -212,6 +213,7 @@ export interface IModelViewWizardDetails { backButton: number; customButtons: number[]; message: DialogMessage; + displayPageTitles: boolean; } export enum MessageLevel { diff --git a/src/sql/workbench/api/node/extHostModelViewDialog.ts b/src/sql/workbench/api/node/extHostModelViewDialog.ts index 94f7755807..6ce7b86315 100644 --- a/src/sql/workbench/api/node/extHostModelViewDialog.ts +++ b/src/sql/workbench/api/node/extHostModelViewDialog.ts @@ -195,6 +195,7 @@ class ButtonImpl implements sqlops.window.modelviewdialog.Button { class WizardPageImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.WizardPage { public customButtons: sqlops.window.modelviewdialog.Button[]; private _enabled: boolean = true; + private _description: string; constructor(public title: string, _extHostModelViewDialog: ExtHostModelViewDialog, _extHostModelView: ExtHostModelViewShape) { super('modelViewWizardPage', _extHostModelViewDialog, _extHostModelView); @@ -216,6 +217,15 @@ class WizardPageImpl extends ModelViewPanelImpl implements sqlops.window.modelvi public set content(content: string) { this._modelViewId = content; } + + public get description(): string { + return this._description; + } + + public set description(description: string) { + this._description = description; + this._extHostModelViewDialog.updateWizardPage(this); + } } export enum WizardPageInfoEventType { @@ -242,6 +252,7 @@ class WizardImpl implements sqlops.window.modelviewdialog.Wizard { public readonly onPageChanged = this._pageChangedEmitter.event; private _navigationValidator: (info: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean | Thenable; private _message: sqlops.window.modelviewdialog.DialogMessage; + private _displayPageTitles: boolean = true; constructor(public title: string, private _extHostModelViewDialog: ExtHostModelViewDialog) { this.doneButton = this._extHostModelViewDialog.createButton(DONE_LABEL); @@ -267,6 +278,15 @@ class WizardImpl implements sqlops.window.modelviewdialog.Wizard { this._extHostModelViewDialog.updateWizard(this); } + public get displayPageTitles(): boolean { + return this._displayPageTitles; + } + + public set displayPageTitles(value: boolean) { + this._displayPageTitles = value; + this._extHostModelViewDialog.updateWizard(this); + } + public addPage(page: sqlops.window.modelviewdialog.WizardPage, index?: number): Thenable { return this._extHostModelViewDialog.updateWizardPage(page).then(() => { this._extHostModelViewDialog.addPage(this, page, index); @@ -510,7 +530,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { content: page.content, customButtons: page.customButtons ? page.customButtons.map(button => this.getHandle(button)) : undefined, enabled: page.enabled, - title: page.title + title: page.title, + description: page.description }); } @@ -535,7 +556,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { doneButton: this.getHandle(wizard.doneButton), nextButton: this.getHandle(wizard.nextButton), customButtons: wizard.customButtons ? wizard.customButtons.map(button => this.getHandle(button)) : undefined, - message: wizard.message + message: wizard.message, + displayPageTitles: wizard.displayPageTitles }); } diff --git a/src/sql/workbench/api/node/mainThreadModelViewDialog.ts b/src/sql/workbench/api/node/mainThreadModelViewDialog.ts index 94f7c807db..e7bf51fb6e 100644 --- a/src/sql/workbench/api/node/mainThreadModelViewDialog.ts +++ b/src/sql/workbench/api/node/mainThreadModelViewDialog.ts @@ -141,6 +141,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape page.title = details.title; page.content = details.content; page.enabled = details.enabled; + page.description = details.description; if (details.customButtons !== undefined) { page.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle)); } @@ -165,6 +166,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape } wizard.title = details.title; + wizard.displayPageTitles = details.displayPageTitles; wizard.pages = details.pages.map(handle => this.getWizardPage(handle)); if (details.currentPage !== undefined) { wizard.setCurrentPage(details.currentPage); diff --git a/src/sqltest/platform/dialog/dialogPane.test.ts b/src/sqltest/platform/dialog/dialogPane.test.ts index 0c9110977c..0c8b86759f 100644 --- a/src/sqltest/platform/dialog/dialogPane.test.ts +++ b/src/sqltest/platform/dialog/dialogPane.test.ts @@ -42,7 +42,7 @@ suite('Dialog Pane Tests', () => { bootstrapCalls++; }); dialog.content = modelViewId; - let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined); + let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined, false); dialogPane.createBody(container); assert.equal(bootstrapCalls, 1); }); @@ -56,7 +56,7 @@ suite('Dialog Pane Tests', () => { bootstrapCalls++; }); dialog.content = [new DialogTab('', modelViewId)]; - let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined); + let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined, false); dialogPane.createBody(container); assert.equal(bootstrapCalls, 1); }); @@ -72,7 +72,7 @@ suite('Dialog Pane Tests', () => { let modelViewId1 = 'test_content_1'; let modelViewId2 = 'test_content_2'; dialog.content = [new DialogTab('tab1', modelViewId1), new DialogTab('tab2', modelViewId2)]; - let dialogPane = new DialogPane(dialog.title, dialog.content, valid => dialog.notifyValidityChanged(valid), undefined); + let dialogPane = new DialogPane(dialog.title, dialog.content, valid => dialog.notifyValidityChanged(valid), undefined, false); dialogPane.createBody(container); let validityChanges: boolean[] = []; diff --git a/src/sqltest/workbench/api/extHostModelViewDialog.test.ts b/src/sqltest/workbench/api/extHostModelViewDialog.test.ts index 540e8de0f4..a37b04b156 100644 --- a/src/sqltest/workbench/api/extHostModelViewDialog.test.ts +++ b/src/sqltest/workbench/api/extHostModelViewDialog.test.ts @@ -179,7 +179,8 @@ suite('ExtHostModelViewDialog Tests', () => { return details.title === page2Title; })), Times.atLeastOnce()); mockProxy.verify(x => x.$setWizardDetails(It.isAny(), It.is(details => { - return details.title === wizardTitle && details.pages.length === 2 && details.customButtons.length === 2; + return details.title === wizardTitle && details.pages.length === 2 && details.customButtons.length === 2 && + details.displayPageTitles === true; })), Times.atLeastOnce()); mockProxy.verify(x => x.$openWizard(It.isAny()), Times.once()); }); diff --git a/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts b/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts index 0750b4a04c..1f13a972ac 100644 --- a/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts +++ b/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts @@ -137,13 +137,15 @@ suite('MainThreadModelViewDialog Tests', () => { title: 'page1', content: 'content1', enabled: true, - customButtons: [] + customButtons: [], + description: 'description1' }; page2Details = { title: 'page2', content: 'content2', enabled: true, - customButtons: [button1Handle, button2Handle] + customButtons: [button1Handle, button2Handle], + description: 'description2' }; wizardDetails = { backButton: backButtonHandle, @@ -155,7 +157,8 @@ suite('MainThreadModelViewDialog Tests', () => { title: 'wizard_title', customButtons: [], pages: [page1Handle, page2Handle], - message: undefined + message: undefined, + displayPageTitles: false }; // Register the buttons, tabs, and dialog @@ -247,18 +250,21 @@ suite('MainThreadModelViewDialog Tests', () => { assert.equal(openedWizard.customButtons.length, 0); assert.equal(openedWizard.pages.length, 2); assert.equal(openedWizard.currentPage, 0); + assert.equal(openedWizard.displayPageTitles, wizardDetails.displayPageTitles); let page1 = openedWizard.pages[0]; assert.equal(page1.title, page1Details.title); assert.equal(page1.content, page1Details.content); assert.equal(page1.enabled, page1Details.enabled); assert.equal(page1.valid, true); assert.equal(page1.customButtons.length, 0); + assert.equal(page1.description, page1Details.description); let page2 = openedWizard.pages[1]; assert.equal(page2.title, page2Details.title); assert.equal(page2.content, page2Details.content); assert.equal(page2.enabled, page2Details.enabled); assert.equal(page2.valid, true); assert.equal(page2.customButtons.length, 2); + assert.equal(page2.description, page2Details.description); }); test('The extension host gets notified when wizard page change events occur', () => { @@ -292,7 +298,8 @@ suite('MainThreadModelViewDialog Tests', () => { title: 'page_3', content: 'content_3', customButtons: [], - enabled: true + enabled: true, + description: undefined }; // If I open the wizard and then add a page