Display page number, title, and description in wizard page headers (#1766)

This commit is contained in:
Matt Irvine
2018-06-27 16:26:26 -07:00
committed by GitHub
parent ffe27f5bde
commit 5cf85a0361
14 changed files with 119 additions and 29 deletions

View File

@@ -73,7 +73,7 @@ export class ModelViewInput extends EditorInput {
private createDialogPane(): void { private createDialogPane(): void {
this._dialogPaneContainer = DOM.$('div.model-view-container'); 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); this._dialogPane.createBody(this._dialogPaneContainer);
} }

View File

@@ -9,26 +9,36 @@ import 'vs/css!./media/dialogModal';
import { Component, AfterContentInit, ViewChild, Input, Inject, forwardRef, ElementRef } from '@angular/core'; import { Component, AfterContentInit, ViewChild, Input, Inject, forwardRef, ElementRef } from '@angular/core';
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component'; import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; 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 { Event, Emitter } from 'vs/base/common/event';
import { ComponentEventType } from '../../parts/modelComponents/interfaces';
export interface DialogComponentParams extends IBootstrapParams { export interface DialogComponentParams extends IBootstrapParams {
modelViewId: string; modelViewId: string;
validityChangedCallback: (valid: boolean) => void; validityChangedCallback: (valid: boolean) => void;
onLayoutRequested: Event<string>; onLayoutRequested: Event<string>;
dialogPane: DialogPane;
} }
@Component({ @Component({
selector: 'dialog-modelview-container', selector: 'dialog-modelview-container',
providers: [], providers: [],
template: ` template: `
<div class="dialogContainer">
<div class="dialogModal-wizardHeader" *ngIf="_dialogPane && _dialogPane.displayPageTitle">
<div *ngIf="_dialogPane.pageNumber" class="wizardPageNumber">Step {{_dialogPane.pageNumber}}</div>
<h1 class="wizardPageTitle">{{_dialogPane.title}}</h1>
<div *ngIf="_dialogPane.description">{{_dialogPane.description}}</div>
</div>
<modelview-content [modelViewId]="modelViewId"> <modelview-content [modelViewId]="modelViewId">
</modelview-content> </modelview-content>
</div>
` `
}) })
export class DialogContainer implements AfterContentInit { export class DialogContainer implements AfterContentInit {
private _onResize = new Emitter<void>(); private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event; public readonly onResize: Event<void> = this._onResize.event;
private _dialogPane: DialogPane;
public modelViewId: string; public modelViewId: string;
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent; @ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
@@ -41,6 +51,7 @@ export class DialogContainer implements AfterContentInit {
this.layout(); this.layout();
} }
}); });
this._dialogPane = this._params.dialogPane;
} }
ngAfterContentInit(): void { ngAfterContentInit(): void {

View File

@@ -115,7 +115,7 @@ export class DialogModal extends Modal {
}); });
this._dialogPane = new DialogPane(this._dialog.title, this._dialog.content, 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); this._dialogPane.createBody(body);
} }

View File

@@ -10,12 +10,11 @@ import 'vs/css!./media/dialogModal';
import { NgModuleRef } from '@angular/core'; import { NgModuleRef } from '@angular/core';
import { IModalDialogStyles } from 'sql/base/browser/ui/modal/modal'; 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 { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel';
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService'; import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
import { DialogModule } from 'sql/platform/dialog/dialog.module'; import { DialogModule } from 'sql/platform/dialog/dialog.module';
import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.component'; 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 * as DOM from 'vs/base/browser/dom';
import { Builder } from 'vs/base/browser/builder'; import { Builder } from 'vs/base/browser/builder';
@@ -31,24 +30,21 @@ export class DialogPane extends Disposable implements IThemable {
// Validation // Validation
private _modelViewValidityMap = new Map<string, boolean>(); private _modelViewValidityMap = new Map<string, boolean>();
// HTML Elements
private _body: HTMLElement; private _body: HTMLElement;
private _tabBar: HTMLElement;
private _tabs: HTMLElement[];
private _tabContent: HTMLElement[];
private _selectedTabIndex: number = 0; //TODO: can be an option private _selectedTabIndex: number = 0; //TODO: can be an option
private _onTabChange = new Emitter<string>(); private _onTabChange = new Emitter<string>();
private _selectedTabContent: string; private _selectedTabContent: string;
public pageNumber?: number;
constructor( constructor(
private _title: string, public title: string,
private _content: string | DialogTab[], private _content: string | DialogTab[],
private _validityChangedCallback: (valid: boolean) => void, private _validityChangedCallback: (valid: boolean) => void,
private _instantiationService: IInstantiationService private _instantiationService: IInstantiationService,
public displayPageTitle: boolean,
public description?: string,
) { ) {
super(); super();
this._tabs = [];
this._tabContent = [];
} }
public createBody(container: HTMLElement): HTMLElement { public createBody(container: HTMLElement): HTMLElement {
@@ -73,7 +69,7 @@ export class DialogPane extends Disposable implements IThemable {
}); });
this._tabbedPanel.pushTab({ this._tabbedPanel.pushTab({
title: tab.title, title: tab.title,
identifier: 'dialogPane.' + this._title + '.' + tabIndex, identifier: 'dialogPane.' + this.title + '.' + tabIndex,
view: { view: {
render: (container) => { render: (container) => {
if (tabContainer.parentElement === this._body) { if (tabContainer.parentElement === this._body) {
@@ -119,7 +115,8 @@ export class DialogPane extends Disposable implements IThemable {
tab.notifyValidityChanged(valid); tab.notifyValidityChanged(valid);
} }
}, },
onLayoutRequested: this._onTabChange.event onLayoutRequested: this._onTabChange.event,
dialogPane: this
} as DialogComponentParams, } as DialogComponentParams,
undefined, undefined,
(moduleRef) => { (moduleRef) => {

View File

@@ -135,6 +135,7 @@ export class DialogButton implements sqlops.window.modelviewdialog.Button {
export class WizardPage extends DialogTab { export class WizardPage extends DialogTab {
public customButtons: DialogButton[]; public customButtons: DialogButton[];
private _enabled: boolean; private _enabled: boolean;
private _description: string;
private _onUpdate: Emitter<void> = new Emitter<void>(); private _onUpdate: Emitter<void> = new Emitter<void>();
public readonly onUpdate: Event<void> = this._onUpdate.event; public readonly onUpdate: Event<void> = this._onUpdate.event;
@@ -150,6 +151,15 @@ export class WizardPage extends DialogTab {
this._enabled = enabled; this._enabled = enabled;
this._onUpdate.fire(); this._onUpdate.fire();
} }
public get description(): string {
return this._description;
}
public set description(description: string) {
this._description = description;
this._onUpdate.fire();
}
} }
export class Wizard { export class Wizard {
@@ -171,6 +181,7 @@ export class Wizard {
private _onMessageChange = new Emitter<DialogMessage>(); private _onMessageChange = new Emitter<DialogMessage>();
public readonly onMessageChange = this._onMessageChange.event; public readonly onMessageChange = this._onMessageChange.event;
private _message: DialogMessage; private _message: DialogMessage;
public displayPageTitles: boolean;
constructor(public title: string) { } constructor(public title: string) { }

View File

@@ -30,3 +30,19 @@
.footer-button.dialogModal-hidden { .footer-button.dialogModal-hidden {
margin: 0; 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
}

View File

@@ -133,18 +133,28 @@ export class WizardModal extends Modal {
}); });
this._wizard.onPageAdded(page => { this._wizard.onPageAdded(page => {
this.registerPage(page); this.registerPage(page);
this.updatePageNumbers();
this.showPage(this._wizard.currentPage, false); this.showPage(this._wizard.currentPage, false);
}); });
this._wizard.onPageRemoved(page => { this._wizard.onPageRemoved(page => {
let dialogPane = this._dialogPanes.get(page); let dialogPane = this._dialogPanes.get(page);
this._dialogPanes.delete(page); this._dialogPanes.delete(page);
this.updatePageNumbers();
this.showPage(this._wizard.currentPage, false); this.showPage(this._wizard.currentPage, false);
dialogPane.dispose(); 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 { 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); dialogPane.createBody(this._body);
this._dialogPanes.set(page, dialogPane); this._dialogPanes.set(page, dialogPane);
page.onUpdate(() => this.setButtonsForPage(this._wizard.currentPage)); page.onUpdate(() => this.setButtonsForPage(this._wizard.currentPage));

View File

@@ -756,13 +756,18 @@ declare module 'sqlops' {
* able to advance to it. Defaults to true. * able to advance to it. Defaults to true.
*/ */
enabled: boolean; enabled: boolean;
/**
* An optional description for the page. If provided it will be displayed underneath the page title.
*/
description: string;
} }
export interface Wizard { export interface Wizard {
/** /**
* The title of the 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 * 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[]; 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 * Event fired when the wizard's page changes, containing information about the
* previous page and the new page * previous page and the new page

View File

@@ -199,6 +199,7 @@ export interface IModelViewWizardPageDetails {
content: string; content: string;
enabled: boolean; enabled: boolean;
customButtons: number[]; customButtons: number[];
description: string;
} }
export interface IModelViewWizardDetails { export interface IModelViewWizardDetails {
@@ -212,6 +213,7 @@ export interface IModelViewWizardDetails {
backButton: number; backButton: number;
customButtons: number[]; customButtons: number[];
message: DialogMessage; message: DialogMessage;
displayPageTitles: boolean;
} }
export enum MessageLevel { export enum MessageLevel {

View File

@@ -195,6 +195,7 @@ class ButtonImpl implements sqlops.window.modelviewdialog.Button {
class WizardPageImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.WizardPage { class WizardPageImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.WizardPage {
public customButtons: sqlops.window.modelviewdialog.Button[]; public customButtons: sqlops.window.modelviewdialog.Button[];
private _enabled: boolean = true; private _enabled: boolean = true;
private _description: string;
constructor(public title: string, _extHostModelViewDialog: ExtHostModelViewDialog, _extHostModelView: ExtHostModelViewShape) { constructor(public title: string, _extHostModelViewDialog: ExtHostModelViewDialog, _extHostModelView: ExtHostModelViewShape) {
super('modelViewWizardPage', _extHostModelViewDialog, _extHostModelView); super('modelViewWizardPage', _extHostModelViewDialog, _extHostModelView);
@@ -216,6 +217,15 @@ class WizardPageImpl extends ModelViewPanelImpl implements sqlops.window.modelvi
public set content(content: string) { public set content(content: string) {
this._modelViewId = content; 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 { export enum WizardPageInfoEventType {
@@ -242,6 +252,7 @@ class WizardImpl implements sqlops.window.modelviewdialog.Wizard {
public readonly onPageChanged = this._pageChangedEmitter.event; public readonly onPageChanged = this._pageChangedEmitter.event;
private _navigationValidator: (info: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean | Thenable<boolean>; private _navigationValidator: (info: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean | Thenable<boolean>;
private _message: sqlops.window.modelviewdialog.DialogMessage; private _message: sqlops.window.modelviewdialog.DialogMessage;
private _displayPageTitles: boolean = true;
constructor(public title: string, private _extHostModelViewDialog: ExtHostModelViewDialog) { constructor(public title: string, private _extHostModelViewDialog: ExtHostModelViewDialog) {
this.doneButton = this._extHostModelViewDialog.createButton(DONE_LABEL); this.doneButton = this._extHostModelViewDialog.createButton(DONE_LABEL);
@@ -267,6 +278,15 @@ class WizardImpl implements sqlops.window.modelviewdialog.Wizard {
this._extHostModelViewDialog.updateWizard(this); 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<void> { public addPage(page: sqlops.window.modelviewdialog.WizardPage, index?: number): Thenable<void> {
return this._extHostModelViewDialog.updateWizardPage(page).then(() => { return this._extHostModelViewDialog.updateWizardPage(page).then(() => {
this._extHostModelViewDialog.addPage(this, page, index); this._extHostModelViewDialog.addPage(this, page, index);
@@ -510,7 +530,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
content: page.content, content: page.content,
customButtons: page.customButtons ? page.customButtons.map(button => this.getHandle(button)) : undefined, customButtons: page.customButtons ? page.customButtons.map(button => this.getHandle(button)) : undefined,
enabled: page.enabled, 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), doneButton: this.getHandle(wizard.doneButton),
nextButton: this.getHandle(wizard.nextButton), nextButton: this.getHandle(wizard.nextButton),
customButtons: wizard.customButtons ? wizard.customButtons.map(button => this.getHandle(button)) : undefined, customButtons: wizard.customButtons ? wizard.customButtons.map(button => this.getHandle(button)) : undefined,
message: wizard.message message: wizard.message,
displayPageTitles: wizard.displayPageTitles
}); });
} }

View File

@@ -141,6 +141,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
page.title = details.title; page.title = details.title;
page.content = details.content; page.content = details.content;
page.enabled = details.enabled; page.enabled = details.enabled;
page.description = details.description;
if (details.customButtons !== undefined) { if (details.customButtons !== undefined) {
page.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle)); page.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle));
} }
@@ -165,6 +166,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
} }
wizard.title = details.title; wizard.title = details.title;
wizard.displayPageTitles = details.displayPageTitles;
wizard.pages = details.pages.map(handle => this.getWizardPage(handle)); wizard.pages = details.pages.map(handle => this.getWizardPage(handle));
if (details.currentPage !== undefined) { if (details.currentPage !== undefined) {
wizard.setCurrentPage(details.currentPage); wizard.setCurrentPage(details.currentPage);

View File

@@ -42,7 +42,7 @@ suite('Dialog Pane Tests', () => {
bootstrapCalls++; bootstrapCalls++;
}); });
dialog.content = modelViewId; 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); dialogPane.createBody(container);
assert.equal(bootstrapCalls, 1); assert.equal(bootstrapCalls, 1);
}); });
@@ -56,7 +56,7 @@ suite('Dialog Pane Tests', () => {
bootstrapCalls++; bootstrapCalls++;
}); });
dialog.content = [new DialogTab('', modelViewId)]; 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); dialogPane.createBody(container);
assert.equal(bootstrapCalls, 1); assert.equal(bootstrapCalls, 1);
}); });
@@ -72,7 +72,7 @@ suite('Dialog Pane Tests', () => {
let modelViewId1 = 'test_content_1'; let modelViewId1 = 'test_content_1';
let modelViewId2 = 'test_content_2'; let modelViewId2 = 'test_content_2';
dialog.content = [new DialogTab('tab1', modelViewId1), new DialogTab('tab2', modelViewId2)]; 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); dialogPane.createBody(container);
let validityChanges: boolean[] = []; let validityChanges: boolean[] = [];

View File

@@ -179,7 +179,8 @@ suite('ExtHostModelViewDialog Tests', () => {
return details.title === page2Title; return details.title === page2Title;
})), Times.atLeastOnce()); })), Times.atLeastOnce());
mockProxy.verify(x => x.$setWizardDetails(It.isAny(), It.is(details => { 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()); })), Times.atLeastOnce());
mockProxy.verify(x => x.$openWizard(It.isAny()), Times.once()); mockProxy.verify(x => x.$openWizard(It.isAny()), Times.once());
}); });

View File

@@ -137,13 +137,15 @@ suite('MainThreadModelViewDialog Tests', () => {
title: 'page1', title: 'page1',
content: 'content1', content: 'content1',
enabled: true, enabled: true,
customButtons: [] customButtons: [],
description: 'description1'
}; };
page2Details = { page2Details = {
title: 'page2', title: 'page2',
content: 'content2', content: 'content2',
enabled: true, enabled: true,
customButtons: [button1Handle, button2Handle] customButtons: [button1Handle, button2Handle],
description: 'description2'
}; };
wizardDetails = { wizardDetails = {
backButton: backButtonHandle, backButton: backButtonHandle,
@@ -155,7 +157,8 @@ suite('MainThreadModelViewDialog Tests', () => {
title: 'wizard_title', title: 'wizard_title',
customButtons: [], customButtons: [],
pages: [page1Handle, page2Handle], pages: [page1Handle, page2Handle],
message: undefined message: undefined,
displayPageTitles: false
}; };
// Register the buttons, tabs, and dialog // Register the buttons, tabs, and dialog
@@ -247,18 +250,21 @@ suite('MainThreadModelViewDialog Tests', () => {
assert.equal(openedWizard.customButtons.length, 0); assert.equal(openedWizard.customButtons.length, 0);
assert.equal(openedWizard.pages.length, 2); assert.equal(openedWizard.pages.length, 2);
assert.equal(openedWizard.currentPage, 0); assert.equal(openedWizard.currentPage, 0);
assert.equal(openedWizard.displayPageTitles, wizardDetails.displayPageTitles);
let page1 = openedWizard.pages[0]; let page1 = openedWizard.pages[0];
assert.equal(page1.title, page1Details.title); assert.equal(page1.title, page1Details.title);
assert.equal(page1.content, page1Details.content); assert.equal(page1.content, page1Details.content);
assert.equal(page1.enabled, page1Details.enabled); assert.equal(page1.enabled, page1Details.enabled);
assert.equal(page1.valid, true); assert.equal(page1.valid, true);
assert.equal(page1.customButtons.length, 0); assert.equal(page1.customButtons.length, 0);
assert.equal(page1.description, page1Details.description);
let page2 = openedWizard.pages[1]; let page2 = openedWizard.pages[1];
assert.equal(page2.title, page2Details.title); assert.equal(page2.title, page2Details.title);
assert.equal(page2.content, page2Details.content); assert.equal(page2.content, page2Details.content);
assert.equal(page2.enabled, page2Details.enabled); assert.equal(page2.enabled, page2Details.enabled);
assert.equal(page2.valid, true); assert.equal(page2.valid, true);
assert.equal(page2.customButtons.length, 2); assert.equal(page2.customButtons.length, 2);
assert.equal(page2.description, page2Details.description);
}); });
test('The extension host gets notified when wizard page change events occur', () => { test('The extension host gets notified when wizard page change events occur', () => {
@@ -292,7 +298,8 @@ suite('MainThreadModelViewDialog Tests', () => {
title: 'page_3', title: 'page_3',
content: 'content_3', content: 'content_3',
customButtons: [], customButtons: [],
enabled: true enabled: true,
description: undefined
}; };
// If I open the wizard and then add a page // If I open the wizard and then add a page